|
| 1 | +# ANTLR Full AST Migration Design |
| 2 | + |
| 3 | +**Date:** 2026-03-29 |
| 4 | +**Status:** Approved |
| 5 | +**Scope:** Replace all regex-based detectors with ANTLR AST parsing for 8 non-JVM languages |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +Migrate all 91 regex-based detectors to ANTLR AST-based parsing. Full replacement, not hybrid. Every detector walks a parse tree instead of matching regex patterns. Fallback to regex with warning log when ANTLR parsing fails on malformed code. |
| 10 | + |
| 11 | +## Why |
| 12 | + |
| 13 | +- Maximum detection quality — proper scoping, type info, decorator resolution |
| 14 | +- Less boilerplate — AST walking is cleaner than regex pattern engineering |
| 15 | +- Maintainable — adding a new detector means walking a tree, not crafting fragile patterns |
| 16 | +- Future-proof — ANTLR has 200+ language grammars, adding new languages is trivial |
| 17 | +- Consistent — same pattern across all languages (JavaParser for Java, ANTLR for everything else) |
| 18 | + |
| 19 | +## Tech Stack |
| 20 | + |
| 21 | +- ANTLR 4.13.2 runtime + maven plugin |
| 22 | +- `.g4` grammar files from official grammars-v4 repository |
| 23 | +- Generated Java lexers/parsers/visitors at build time |
| 24 | +- ThreadLocal parser instances for virtual thread safety |
| 25 | + |
| 26 | +## Languages & Grammars |
| 27 | + |
| 28 | +| Language | Grammar Source | Detectors to Migrate | |
| 29 | +|---|---|---| |
| 30 | +| TypeScript/JS | grammars-v4/typescript + javascript | 14 | |
| 31 | +| Python | grammars-v4/python (Python3) | 12 | |
| 32 | +| Go | grammars-v4/golang | 4 | |
| 33 | +| C# | grammars-v4/csharp | 4 | |
| 34 | +| Rust | grammars-v4/rust | 3 | |
| 35 | +| Kotlin | grammars-v4/kotlin | 3 | |
| 36 | +| Scala | grammars-v4/scala | 2 | |
| 37 | +| C++ | grammars-v4/cpp (CPP14) | 2 | |
| 38 | +| Shell/Bash | grammars-v4/bash | 3 | |
| 39 | +| **Total** | | **47 detectors** | |
| 40 | + |
| 41 | +Note: Config/infra detectors (YAML, JSON, XML, etc.) stay as AbstractStructuredDetector — they parse data structures, not programming languages. Auth detectors that scan multiple languages stay regex (they match patterns across any language). Generic imports detector stays regex. |
| 42 | + |
| 43 | +## Directory Structure |
| 44 | + |
| 45 | +``` |
| 46 | +src/main/antlr4/io/github/randomcodespace/iq/grammar/ |
| 47 | + typescript/TypeScriptLexer.g4, TypeScriptParser.g4 |
| 48 | + python/Python3Lexer.g4, Python3Parser.g4 |
| 49 | + golang/GoLexer.g4, GoParser.g4 |
| 50 | + csharp/CSharpLexer.g4, CSharpParser.g4 |
| 51 | + rust/RustLexer.g4, RustParser.g4 |
| 52 | + kotlin/KotlinLexer.g4, KotlinParser.g4 |
| 53 | + scala/ScalaLexer.g4, ScalaParser.g4 |
| 54 | + cpp/CPP14Lexer.g4, CPP14Parser.g4 |
| 55 | + bash/BashLexer.g4, BashParser.g4 |
| 56 | +``` |
| 57 | + |
| 58 | +Generated output: `target/generated-sources/antlr4/io/github/randomcodespace/iq/grammar/` |
| 59 | + |
| 60 | +## Base Class |
| 61 | + |
| 62 | +```java |
| 63 | +public abstract class AbstractAntlrDetector extends AbstractRegexDetector { |
| 64 | + |
| 65 | + // Template method: try ANTLR, fall back to regex with warning |
| 66 | + @Override |
| 67 | + public DetectorResult detect(DetectorContext ctx) { |
| 68 | + try { |
| 69 | + ParseTree tree = parse(ctx); |
| 70 | + if (tree != null) { |
| 71 | + return detectWithAst(tree, ctx); |
| 72 | + } |
| 73 | + } catch (Exception e) { |
| 74 | + log.warn("ANTLR parse failed for {}, falling back to regex: {}", |
| 75 | + ctx.filePath(), e.getMessage()); |
| 76 | + } |
| 77 | + return detectWithRegex(ctx); |
| 78 | + } |
| 79 | + |
| 80 | + protected abstract ParseTree parse(DetectorContext ctx); |
| 81 | + protected abstract DetectorResult detectWithAst(ParseTree tree, DetectorContext ctx); |
| 82 | + protected DetectorResult detectWithRegex(DetectorContext ctx) { |
| 83 | + return DetectorResult.empty(); // Override if regex fallback needed |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +### Language-specific parser helpers |
| 89 | + |
| 90 | +```java |
| 91 | +// Per-language helper classes for common AST operations |
| 92 | +public class TypeScriptAstHelper { |
| 93 | + private static final ThreadLocal<TypeScriptParser> PARSER = ...; |
| 94 | + |
| 95 | + public static ParseTree parse(String content) { ... } |
| 96 | + public static List<ClassDecl> findClasses(ParseTree tree) { ... } |
| 97 | + public static List<FunctionDecl> findFunctions(ParseTree tree) { ... } |
| 98 | + public static List<ImportDecl> findImports(ParseTree tree) { ... } |
| 99 | + public static List<Decorator> findDecorators(ParseTree tree) { ... } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +One helper per language. Detectors for that language share the helper. |
| 104 | + |
| 105 | +## Detector Rewrite Pattern |
| 106 | + |
| 107 | +Each detector: |
| 108 | +1. Extends `AbstractAntlrDetector` |
| 109 | +2. Implements `parse()` — delegates to language helper |
| 110 | +3. Implements `detectWithAst()` — walks the AST for framework-specific patterns |
| 111 | +4. Optionally overrides `detectWithRegex()` — existing regex logic as fallback |
| 112 | + |
| 113 | +## Fallback Behavior |
| 114 | + |
| 115 | +When ANTLR parse fails: |
| 116 | +1. Log warning: `"ANTLR parse failed for {file}, falling back to regex: {error}"` |
| 117 | +2. Execute regex fallback (existing logic, unchanged) |
| 118 | +3. Result from regex is returned — never lose coverage |
| 119 | + |
| 120 | +This ensures we never produce fewer results than the current regex-only approach. |
| 121 | + |
| 122 | +## Thread Safety |
| 123 | + |
| 124 | +ANTLR parsers are NOT thread-safe. Use ThreadLocal per language: |
| 125 | + |
| 126 | +```java |
| 127 | +private static final ThreadLocal<TypeScriptLexer> LEXER = |
| 128 | + ThreadLocal.withInitial(() -> new TypeScriptLexer(null)); |
| 129 | +private static final ThreadLocal<TypeScriptParser> PARSER = |
| 130 | + ThreadLocal.withInitial(() -> new TypeScriptParser(null)); |
| 131 | +``` |
| 132 | + |
| 133 | +Reset input stream per parse call. Same pattern as JavaParser ThreadLocal. |
| 134 | + |
| 135 | +## Maven Configuration |
| 136 | + |
| 137 | +```xml |
| 138 | +<dependency> |
| 139 | + <groupId>org.antlr</groupId> |
| 140 | + <artifactId>antlr4-runtime</artifactId> |
| 141 | + <version>4.13.2</version> |
| 142 | +</dependency> |
| 143 | + |
| 144 | +<plugin> |
| 145 | + <groupId>org.antlr</groupId> |
| 146 | + <artifactId>antlr4-maven-plugin</artifactId> |
| 147 | + <version>4.13.2</version> |
| 148 | + <executions> |
| 149 | + <execution> |
| 150 | + <goals><goal>antlr4</goal></goals> |
| 151 | + </execution> |
| 152 | + </executions> |
| 153 | + <configuration> |
| 154 | + <visitor>true</visitor> |
| 155 | + <listener>true</listener> |
| 156 | + </configuration> |
| 157 | +</plugin> |
| 158 | +``` |
| 159 | + |
| 160 | +## Testing |
| 161 | + |
| 162 | +Each migrated detector keeps existing tests plus: |
| 163 | +- **AST-specific test** — verify AST path produces correct nodes/edges |
| 164 | +- **Fallback test** — malformed code triggers regex fallback + warning logged |
| 165 | +- **Parity test** — AST results >= regex results on same input |
| 166 | + |
| 167 | +## Performance |
| 168 | + |
| 169 | +- ANTLR parsing: ~2-5x slower than regex per file |
| 170 | +- Total pipeline impact: ~10-20% slower analysis time |
| 171 | +- Offset by: higher quality detection, fewer false negatives |
| 172 | +- Virtual threads absorb some overhead via parallelism |
| 173 | + |
| 174 | +## What Stays Unchanged |
| 175 | + |
| 176 | +- Java detectors — already use JavaParser (better than ANTLR for Java) |
| 177 | +- Config detectors — use AbstractStructuredDetector (YAML/JSON/XML parsing) |
| 178 | +- Auth detectors — scan multiple languages with cross-language regex patterns |
| 179 | +- Generic imports detector — simple cross-language regex |
| 180 | +- IaC detectors — Terraform/Bicep/Dockerfile use regex (no ANTLR grammar needed) |
0 commit comments