Skip to content

Commit c3ae26e

Browse files
tob-scott-aclaude
andcommitted
feat(parsers): add Kotlin language support
Handles .kt and .kts files using tree-sitter-language-pack's kotlin grammar (transitive dep, no new installs). Extracts: - Top-level functions (fun main, suspend fun fetch, ...) - Classes, interfaces, data classes, objects, enum classes - Methods inside class_body and enum_class_body - Parameters from function_value_parameters and their user_type / type_identifier / nullable_type annotations - Return types from the type node following `:` on the signature - Imports — captured as graph.dependencies Entrypoint detectors for Kotlin: - Spring MVC / WebFlux annotations (@GetMapping, @PostMapping, etc.) reusing the existing Java detector since the annotations are identical across the two languages. - Android component lifecycle methods: onCreate, onStart, onResume, onNewIntent, onActivityResult, onReceive, onBind, onHandleIntent. These are attacker-reachable when the component is exported — we over-detect on purpose and rely on the override file to tighten. Known gap (parallel to Swift, documented in parser docstring): `throw` statements share their AST node type with `return`/`break`/`continue` via `jump_expression`, so exception-type capture needs a Kotlin-specific walk filtered by the leading `throw` token. Deferred. 14 new tests: Kotlin parser, Kotlin entrypoint detection. README supported-languages and framework-coverage tables updated. Roadmap left before v0.2.0: Dart/Flutter parser, Go/Ruby/C++ entrypoint detectors (Option A), branches + docstring in JSON export (Option B). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5ea4cc4 commit c3ae26e

7 files changed

Lines changed: 540 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ A language-specific parser walks the directory, parses each file into a tree-sit
6161
| Miden Assembly | `.masm` | procedures, entrypoints, constants, invocations |
6262
| Swift | `.swift` | functions, classes, structs, enums, protocols, extensions |
6363
| Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |
64+
| Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |
6465

6566
```mermaid
6667
flowchart TD
@@ -293,6 +294,7 @@ Framework coverage:
293294
| Erlang | functions listed in `-export([...])` |
294295
| Swift | `@main` app attribute |
295296
| Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |
297+
| Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |
296298

297299
For anything the heuristics miss, declare entrypoints explicitly in `.trailmark/entrypoints.toml` at the project root:
298300

src/trailmark/analysis/entrypoints.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,27 @@
168168
}
169169
)
170170

171+
# Kotlin — Ktor routing DSL: `get("/path") { ... }`, `post`, etc.
172+
# These appear as call expressions inside a `routing { ... }` block. The
173+
# file-level heuristic matches the verb calls; without block-level context
174+
# we can't distinguish Ktor routes from bare HTTP client calls, so Ktor
175+
# detection is currently disabled in favor of the override file.
176+
177+
# Kotlin — Android Activity lifecycle overrides. Names and signatures
178+
# are stable across Android SDK versions.
179+
_KOTLIN_ANDROID_LIFECYCLE_METHODS = frozenset(
180+
{
181+
"onCreate",
182+
"onStart",
183+
"onResume",
184+
"onNewIntent",
185+
"onActivityResult",
186+
"onReceive", # BroadcastReceiver
187+
"onBind", # Service
188+
"onHandleIntent", # IntentService
189+
}
190+
)
191+
171192
_KIND_BY_NAME = {k.value: k for k in EntrypointKind}
172193
_TRUST_BY_NAME = {t.value: t for t in TrustLevel}
173194
_ASSET_BY_NAME = {a.value: a for a in AssetValue}
@@ -260,6 +281,8 @@ def _detect_for_unit(
260281
return _detect_swift(cache, unit, path)
261282
if path.endswith((".m", ".mm", ".h")):
262283
return _detect_objc(unit)
284+
if path.endswith((".kt", ".kts")):
285+
return _detect_kotlin(cache, unit, path)
263286
return None
264287

265288

@@ -652,6 +675,41 @@ def _detect_swift(
652675
return None
653676

654677

678+
def _detect_kotlin(
679+
cache: _SourceCache,
680+
unit: CodeUnit,
681+
path: str,
682+
) -> EntrypointTag | None:
683+
"""Detect Kotlin entrypoints.
684+
685+
Layers tried in order:
686+
1. Spring MVC / WebFlux annotations (``@GetMapping``, ``@PostMapping``,
687+
etc.) — shared with the Java detector because the annotations are
688+
identical across the two languages.
689+
2. Android Activity / Service / BroadcastReceiver lifecycle method
690+
names. These are attacker-reachable when the component is exported
691+
(``android:exported="true"`` in the manifest) — we over-detect
692+
here and let the override file tighten when appropriate.
693+
"""
694+
decorators = cache.decorators_above(path, unit.location.start_line)
695+
for line in decorators:
696+
if _JAVA_SPRING_HANDLER.match(line):
697+
return EntrypointTag(
698+
kind=EntrypointKind.API,
699+
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
700+
description="Spring MVC/WebFlux handler (Kotlin)",
701+
asset_value=AssetValue.HIGH,
702+
)
703+
if unit.name in _KOTLIN_ANDROID_LIFECYCLE_METHODS:
704+
return EntrypointTag(
705+
kind=EntrypointKind.API,
706+
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
707+
description="Android component lifecycle method",
708+
asset_value=AssetValue.HIGH,
709+
)
710+
return None
711+
712+
655713
def _detect_objc(unit: CodeUnit) -> EntrypointTag | None:
656714
"""Detect Objective-C entrypoints: AppDelegate selectors and extern C.
657715
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Kotlin language parser for Trailmark."""
2+
3+
from trailmark.parsers.kotlin.parser import KotlinParser
4+
5+
__all__ = ["KotlinParser"]

0 commit comments

Comments
 (0)