
trailmark v0.5.0
Erstellen und Abfragen einer Graphdatenbank-Repräsentation von Quellcode
Trailmark
Quellcode in abfragbare Graphen aus Funktionen, Klassen, Aufrufen und semantischen Annotationen für die Sicherheitsanalyse parsen.
Trailmark verwendet tree-sitter für sprachunabhängiges AST-Parsing und rustworkx für leistungsstarke Graph-Traversierung. Die langfristige Vision besteht darin, diesen Graphen mit Mutation Testing und coverage-gesteuertem Fuzzing zu kombinieren, um Lücken zwischen Annahmen und Testabdeckung zu identifizieren, die von Benutzereingaben aus erreichbar sind.
Funktionsweise
Trailmark arbeitet in drei Phasen: Parsen, Indizieren und Abfragen.
flowchart TD
A["Source Files"] --> B["tree-sitter Parser"]
B --> C["CodeGraph (nodes + edges)"]
C --> D["rustworkx GraphStore"]
D --> E["QueryEngine"]
E --> F["JSON / Summary / Hotspots"]
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
classDef query fill:#ffc10726,stroke:#e6a817,color:#e6a817
class A src
class B parse
class C,D data
class E,F query
1. Parsen
Ein sprachspezifischer Parser durchläuft das Verzeichnis, parst jede Datei in einen tree-sitter-AST und extrahiert:
- Knoten — Funktionen, Methoden, Klassen, Structs, Interfaces, Traits, Enums, Module, Namespaces
- Kanten — Aufrufe, Vererbung, Implementierung, Containment, Importe
- Metadaten — Typannotationen, zyklomatische Komplexität, Verzweigungen, Docstrings, Ausnahmetypen
Unterstützte Sprachen
| Sprache | Erweiterungen | Zentrale Konstrukte |
|---|---|---|
| Python | .py | Funktionen, Klassen, Methoden |
| JavaScript | .js, .jsx, .mjs, .cjs | Funktionen, Klassen, Arrow-Funktionen |
| TypeScript | .ts, .tsx | Funktionen, Klassen, Interfaces, Enums |
| PHP | .php | Funktionen, Klassen, Interfaces, Traits |
| Ruby | .rb | Methoden, Klassen, Module |
| C | .c, .h | Funktionen, Structs, Enums |
| C++ | .cpp, .hpp, .cc, .hh, .cxx, .hxx | Funktionen, Klassen, Structs, Namespaces |
| C# | .cs | Methoden, Klassen, Interfaces, Structs, Enums, Namespaces |
| Java | .java | Methoden, Klassen, Interfaces, Enums |
| Go | .go | Funktionen, Methoden, Structs, Interfaces |
| Rust | .rs | Funktionen, Structs, Traits, Enums, Impl-Blöcke, verus!-Blöcke |
| Solidity | .sol | Contracts, Interfaces, Libraries, Funktionen, Modifier, Structs, Enums |
| Cairo | .cairo | Funktionen, Traits, Structs, Enums, Impl-Blöcke, StarkNet-Contracts |
| Circom | .circom | Templates, Funktionen, Signale, Komponenten |
| Haskell | .hs | Funktionen, Datentypen, Typklassen, Instanzen |
| Erlang | .erl | Funktionen, Records, Behaviours, Module |
| Miden Assembly | .masm | Prozeduren, Entrypoints, Konstanten, Aufrufe |
| Swift | .swift | Funktionen, Klassen, Structs, Enums, Protokolle, Extensions |
| Objective-C | .m, .mm, .h | C-Funktionen, Klassen, Methoden (selektorbasierte Benennung) |
| Kotlin | .kt, .kts | Funktionen, Klassen, Interfaces, Data Classes, Objekte, Methoden |
| Dart | .dart | Funktionen, Klassen, abstrakte Klassen, Methoden, Konstruktoren |
| Move | .move | Module, Funktionen, Importe, direkte Aufrufe |
| Tact | .tact | Contracts, Structs, Receiver, Funktionen |
| Func | .fc, .func | Funktionen, Includes, direkte Aufrufe |
| Sway | .sw | ABI-Interfaces, Structs, Impl-Methoden, Funktionen |
| Rego | .rego | Pakete, Importe, Policy-Regeln, Regelaufrufe |
| Proto | .proto | Services, RPCs, Messages, Felder, Enums |
| Thrift | .thrift | Services, Funktionen, Structs, Felder, Enums |
| GraphQL | .graphql, .gql | Objekttypen, Root-Operationen, Felder, Enums |
| SQL | .sql | Schemas, Tabellen, Views, Funktionen, Prozeduren |
Der Rust-Parser extrahiert gewöhnliche spec- und proof-Funktionen mit Rümpfen
aus verus! { ... }-Blöcken. Die Verus-Grammatik unterstützt derzeit nur
klammerbegrenzte Blöcke; verus!(...) und verus![...] werden mit einer Warnung übersprungen.
Inline-Module behalten qualifizierte IDs (zum Beispiel file.inner:function), und
Methoden behalten ihren Impl- oder Trait-Eigentümer. Jeder Block wird unabhängig geparst;
Syntaxfehler erzeugen eine Warnung mit der Datei- und Blockposition.
Qualifizierte Aufrufe wie vstd::prelude::verus! werden ebenfalls unterstützt.
Lokale Hilfsfunktionen behalten ihre umschließende Funktion in ihren IDs (zum Beispiel
file:outer.inner). Aufrufe dieser Hilfsfunktionen und relative Importe wie
use super::* werden zu Deklarationen aufgelöst, die in der Datei vorhanden sind.
Wiederholte lokale Namen in verschiedenen Blöcken erhalten ein Byte-Offset-Suffix, um
ihre Knoten unterscheidbar zu halten, und Aufrufe respektieren den Blockbereich.
Lokale Structs, Enums, Traits und Inline-Module behalten ebenfalls die umschließende Funktion
bei: zum Beispiel file:outer.Guard.drop und file:outer.local.helper.
Ihre Rümpfe haben separate Aufrufe und Komplexität. Explizite Aufrufe bekannter lokaler
Typen und Module werden innerhalb der Datei aufgelöst. Bloße Aufrufe durchsuchen Funktions- und Modul-
bereiche; zugehörige Elemente verwenden Pfade wie Type::method. Punktierte Aufrufe werden
nur aufgelöst, wenn der Empfänger einen bekannten Struct oder Enum benennt. Andere Wertempfänger,
einschließlich self.method(), bleiben unaufgelöst. Implizite Destruktoraufrufe und Empfänger-
Typinferenz werden nicht modelliert. Diese Regeln gelten sowohl für gewöhnliches Rust als auch für
Verus-Blöcke. Das Bearbeiten von Code vor einer Deklaration mit einem Offset-Suffix ändert
ihre ID, sodass trailmark diff diese Deklaration als entfernt und hinzugefügt melden kann.
Trailmark expandiert keine beliebigen Makros. Literale Verus-Aufrufe innerhalb
opaker Makros oder Funktionsrümpfe werden mit einer Warnung übersprungen. Nicht aufgerufene
macro_rules!-Definitionen werden nicht expandiert. Funktionssignaturen ohne Rümpfe
und Aufrufe in requires/ensures-Klauseln werden nicht extrahiert.
flowchart TD
subgraph "Per-File Parsing"
F["Source file"] --> TS["tree-sitter AST"]
TS --> EX["Extract nodes"]
TS --> EC["Extract call edges"]
TS --> EB["Count branches"]
TS --> ET["Resolve types"]
end
EX --> CG["CodeGraph"]
EC --> CG
EB --> CG
ET --> CG
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef extract fill:#ffc10726,stroke:#e6a817,color:#e6a817
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
class F src
class TS parse
class EX,EC,EB,ET extract
class CG data
Knoten-IDs folgen dem Schema module:function, module:Class oder module:Class.method für eine eindeutige Auflösung. Die Verzeichnisanalyse löst bloße dateiübergreifende Aufrufe auf, wenn eine eindeutige Definition existiert; mehrdeutige dateiübergreifende Aufrufe bleiben bei ihrem ursprünglichen Best-Effort-Ziel und werden als uncertain markiert. Die Kantenkonfidenz wird als certain (direkte Aufrufe, self.method()), inferred (Attributzugriff auf Nicht-Self-Objekte) oder uncertain (dynamischer Dispatch oder mehrdeutige Auflösung) gekennzeichnet.
2. Index
Der GraphStore lädt den CodeGraph in einen rustworkx PyDiGraph und erstellt bidirektionale ID/Index-Zuordnungen für eine schnelle Traversierung.
3. Abfrage
Die QueryEngine bietet eine High-Level-API über dem indizierten Graphen: