Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ A language-specific parser walks the directory, parses each file into a tree-sit
| Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |
| Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |
| Dart | `.dart` | functions, classes, abstract classes, methods, constructors |
| Move | `.move` | modules, functions, imports, direct calls |
| Tact | `.tact` | contracts, structs, receivers, functions |
| Func | `.fc`, `.func` | functions, includes, direct calls |
| Sway | `.sw` | ABI interfaces, structs, impl methods, functions |
| Rego | `.rego` | packages, imports, policy rules, rule calls |
| Proto | `.proto` | services, RPCs, messages, fields, enums |
| Thrift | `.thrift` | services, functions, structs, fields, enums |
| GraphQL | `.graphql`, `.gql` | object types, root operations, fields, enums |

```mermaid
flowchart TD
Expand Down
143 changes: 143 additions & 0 deletions src/trailmark/analysis/entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,22 @@ def _detect_for_unit(
return _detect_kotlin(cache, unit, path)
if path.endswith(".dart"):
return _detect_dart(cache, unit, path)
if path.endswith(".move"):
return _detect_move(cache, unit, path)
if path.endswith(".tact"):
return _detect_tact(unit)
if path.endswith((".fc", ".func")):
return _detect_func(unit)
if path.endswith(".sw"):
return _detect_sway(cache, unit, path)
if path.endswith(".rego"):
return _detect_rego(unit)
if path.endswith(".proto"):
return _detect_proto(cache, unit, path)
if path.endswith(".thrift"):
return _detect_thrift(cache, unit, path)
if path.endswith((".graphql", ".gql")):
return _detect_graphql(unit)
if path.endswith(".go"):
return _detect_go(cache, unit, path)
if path.endswith(".rb"):
Expand Down Expand Up @@ -901,6 +917,133 @@ def _detect_dart(
return None


def _detect_move(
cache: _SourceCache,
unit: CodeUnit,
path: str,
) -> EntrypointTag | None:
signature = cache.signature_block(path, unit.location.start_line) or ""
if unit.kind.value == "function" and (" entry " in signature or "public" in signature):
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Move public/entry function",
asset_value=AssetValue.HIGH,
)
return None


def _detect_tact(unit: CodeUnit) -> EntrypointTag | None:
tact_role = _unit_attr(unit, "tact_role") or unit.name
if unit.kind.value == "method" and tact_role in {"init", "receive", "external", "bounced"}:
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Tact contract receiver/initializer",
asset_value=AssetValue.HIGH,
)
return None


def _detect_func(unit: CodeUnit) -> EntrypointTag | None:
if unit.name in {"recv_internal", "recv_external"} or unit.name.startswith("get_"):
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Func receiver/getter entrypoint",
asset_value=AssetValue.HIGH,
)
return None


def _detect_sway(
cache: _SourceCache,
unit: CodeUnit,
path: str,
) -> EntrypointTag | None:
signature = cache.signature_block(path, unit.location.start_line) or ""
if unit.kind.value == "function" and "pub fn" in signature:
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Sway public function",
asset_value=AssetValue.HIGH,
)
if (
unit.kind.value == "method"
and re.search(r"\bfn\s+\w+\s*\(", signature)
and ";" in signature
):
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Sway ABI method",
asset_value=AssetValue.HIGH,
)
return None


def _detect_rego(unit: CodeUnit) -> EntrypointTag | None:
if unit.name in {"allow", "deny", "violation"}:
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Rego policy decision rule",
asset_value=AssetValue.HIGH,
)
return None


def _detect_proto(
cache: _SourceCache,
unit: CodeUnit,
path: str,
) -> EntrypointTag | None:
del cache, path
if unit.kind.value == "method" and _unit_attr(unit, "schema_role") == "rpc":
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Protocol Buffers service RPC",
asset_value=AssetValue.HIGH,
)
return None


def _detect_thrift(
cache: _SourceCache,
unit: CodeUnit,
path: str,
) -> EntrypointTag | None:
del cache, path
if unit.kind.value == "method" and _unit_attr(unit, "schema_role") == "service_function":
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Thrift service function",
asset_value=AssetValue.HIGH,
)
return None


def _detect_graphql(unit: CodeUnit) -> EntrypointTag | None:
if unit.kind.value == "method" and _unit_attr(unit, "schema_role") == "root_operation":
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="GraphQL root operation field",
asset_value=AssetValue.HIGH,
)
return None


def _unit_attr(unit: CodeUnit, key: str) -> str | None:
for attr_key, attr_value in unit.attributes:
if attr_key == key and isinstance(attr_value, str):
return attr_value
return None


def _detect_objc(unit: CodeUnit) -> EntrypointTag | None:
"""Detect Objective-C entrypoints: AppDelegate selectors and extern C.

Expand Down
16 changes: 16 additions & 0 deletions src/trailmark/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
"objc": ("trailmark.parsers.objc", "ObjCParser"),
"kotlin": ("trailmark.parsers.kotlin", "KotlinParser"),
"dart": ("trailmark.parsers.dart", "DartParser"),
"move": ("trailmark.parsers.move", "MoveParser"),
"tact": ("trailmark.parsers.tact", "TactParser"),
"func": ("trailmark.parsers.func", "FuncParser"),
"sway": ("trailmark.parsers.sway", "SwayParser"),
"rego": ("trailmark.parsers.rego", "RegoParser"),
"proto": ("trailmark.parsers.proto", "ProtoParser"),
"thrift": ("trailmark.parsers.thrift", "ThriftParser"),
"graphql": ("trailmark.parsers.graphql", "GraphQLParser"),
}

# Extensions used for language auto-detection. Keep these aligned with each
Expand Down Expand Up @@ -63,6 +71,14 @@
"objc": (".m", ".mm"),
"kotlin": (".kt", ".kts"),
"dart": (".dart",),
"move": (".move",),
"tact": (".tact",),
"func": (".fc", ".func"),
"sway": (".sw",),
"rego": (".rego",),
"proto": (".proto",),
"thrift": (".thrift",),
"graphql": (".graphql", ".gql"),
}

_SUPPORTED_LANGUAGES = tuple(_PARSER_MAP.keys())
Expand Down
Loading