
Crea e interroga una rappresentazione a database grafico del codice sorgente.
Analizza il codice sorgente in grafi interrogabili di funzioni, classi, chiamate e annotazioni semantiche per l'analisi di sicurezza.
Trailmark utilizza tree-sitter per il parsing dell'AST indipendente dal linguaggio e rustworkx per l'attraversamento dei grafi ad alte prestazioni. La visione a lungo termine è quella di combinare questo grafo con il mutation testing e il fuzzing guidato dalla copertura per identificare lacune tra le assunzioni e la copertura dei test che sono raggiungibili dall'input dell'utente.
Trailmark opera in tre fasi: parse, index e query.
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
Un parser specifico per linguaggio attraversa la directory, analizza ogni file in un AST di tree-sitter ed estrae:
| Linguaggio | Estensioni | Costrutti principali |
|---|---|---|
| Python | .py | funzioni, classi, metodi |
| JavaScript | .js, .jsx, .mjs, .cjs | funzioni, classi, arrow function |
| TypeScript | .ts, .tsx | funzioni, classi, interfacce, enum |
| PHP | .php | funzioni, classi, interfacce, trait |
| Ruby | .rb | metodi, classi, moduli |
| C | .c, .h | funzioni, struct, enum |
| C++ | .cpp, .hpp, .cc, .hh, .cxx, .hxx | funzioni, classi, struct, namespace |
| C# | .cs | metodi, classi, interfacce, struct, enum, namespace |
| Java | .java | metodi, classi, interfacce, enum |
| Go | .go | funzioni, metodi, struct, interfacce |
| Rust | .rs | funzioni, struct, trait, enum, blocchi impl, blocchi verus! |
| Solidity | .sol | contratti, interfacce, librerie, funzioni, modifier, struct, enum |
| Cairo | .cairo | funzioni, trait, struct, enum, blocchi impl, contratti StarkNet |
| Circom | .circom | template, funzioni, segnali, componenti |
| Haskell | .hs | funzioni, tipi di dati, classi di tipo, istanze |
| Erlang | .erl | funzioni, record, behaviour, moduli |
| Miden Assembly | .masm | procedure, entrypoint, costanti, invocazioni |
| Swift | .swift | funzioni, classi, struct, enum, protocolli, estensioni |
| Objective-C | .m, .mm, .h | funzioni C, classi, metodi (denominazione basata su selettori) |
| Kotlin | .kt, .kts | funzioni, classi, interfacce, data class, oggetti, metodi |
| Dart | .dart | funzioni, classi, classi astratte, metodi, costruttori |
| Move | .move | moduli, funzioni, import, chiamate dirette |
| Tact | .tact | contratti, struct, receiver, funzioni |
| Func | .fc, .func | funzioni, include, chiamate dirette |
| Sway | .sw | interfacce ABI, struct, metodi impl, funzioni |
| Rego | .rego | pacchetti, import, regole di policy, chiamate a regole |
| Proto | .proto | servizi, RPC, messaggi, campi, enum |
| Thrift | .thrift | servizi, funzioni, struct, campi, enum |
| GraphQL | .graphql, .gql | tipi di oggetto, operazioni root, campi, enum |
| SQL | .sql | schemi, tabelle, viste, funzioni, procedure |
Il parser Rust estrae le funzioni ordinarie, spec e proof con i relativi corpi
dai blocchi verus! { ... }. La grammatica Verus attualmente supporta solo
blocchi delimitati da parentesi graffe; verus!(...) e verus![...] vengono saltati con un avviso.
I moduli inline mantengono gli ID qualificati (ad esempio, file.inner:function), e
i metodi mantengono il loro impl o trait di appartenenza. Ogni blocco viene analizzato in modo indipendente;
gli errori di sintassi producono un avviso con il file e la posizione del blocco.
Sono supportate anche le invocazioni qualificate come vstd::prelude::verus!.
Le funzioni di supporto locali mantengono la funzione che le racchiude nei loro ID (ad esempio,
file:outer.inner). Le chiamate a queste funzioni di supporto e gli import relativi come
use super::* vengono risolti verso le dichiarazioni presenti nel file.
I nomi locali ripetuti in blocchi diversi ricevono un suffisso di offset in byte per mantenere
i loro nodi distinti, e le chiamate rispettano l'ambito del blocco.
Anche le struct, enum, trait e i moduli inline locali mantengono la funzione che li racchiude:
ad esempio, file:outer.Guard.drop e file:outer.local.helper.
I loro corpi hanno chiamate e complessità separate. Le chiamate esplicite a tipi e moduli
locali noti vengono risolte all'interno del file. Le chiamate semplici cercano negli ambiti delle funzioni e dei moduli;
gli elementi associati usano percorsi come Type::method. Le chiamate con punto vengono risolte
solo quando il ricevente indica una struct o enum nota. Gli altri riceventi di valore,
incluso self.method(), rimangono non risolti. Le chiamate implicite ai distruttori e l'inferenza
del tipo del ricevente non sono modellate. Queste regole si applicano anche al Rust ordinario oltre che
ai blocchi Verus. Modificare il codice prima di una dichiarazione con un suffisso di offset cambia
il suo ID, quindi trailmark diff può segnalare quella dichiarazione come rimossa e aggiunta.
Trailmark non espande macro arbitrarie. Le invocazioni Verus letterali all'interno di
macro opache o corpi di funzione vengono saltate con un avviso. Le definizioni macro_rules!
non invocate non vengono espanse. Le firme di funzione senza corpi
e le chiamate nelle clausole requires/ensures non vengono estratte.
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
Gli ID dei nodi seguono lo schema module:function, module:Class o module:Class.method per una ricerca non ambigua. L'analisi delle directory risolve le chiamate semplici tra file quando esiste una definizione univoca; le chiamate tra file ambigue vengono lasciate al loro target originale best-effort e contrassegnate come uncertain. L'affidabilità degli archi è etichettata come certain (chiamate dirette, self.method()), inferred (accesso ad attributi su oggetti non-self) o uncertain (dispatch dinamico o risoluzione ambigua).
Il GraphStore carica il CodeGraph in un PyDiGraph di rustworkx e costruisce mappature bidirezionali ID/indice per un attraversamento rapido.
Il QueryEngine fornisce un'API di alto livello sul grafo indicizzato: