← Brain index

insight/static-analysis-is-repair-infrastructure · working · tags: static analysis

Static Analysis Is Repair Infrastructure

Static analysis has moved from "run a linter before merge" toward an infrastructure layer for repair loops. The useful unit is no longer just a warning in a terminal. It is a structured fact about the repository: a rule ID, span, evidence, precision level, and machine-readable path back to the policy. That matters more as AI coding agents write more code, because agents need external feedback that is deterministic, local, scoped, and cheap to rerun.

Background

Static analysis is a broad family, not one technique. CodeQL describes an analysis flow as database creation, query execution, and result interpretation; its databases include language-specific relational representations of AST, data-flow graph, and control-flow graph facts. Semgrep describes rules as pattern matching plus data-flow analysis. ESLint exposes a JavaScript visitor API over ESTree nodes. SootUp exposes call-graph algorithms such as CHA, RTA, and VTA. These are all "static analysis", but they sit on different rungs of a ladder.

LayerTypical factExample policy
Text and filespath, glob, generated/vendor statusDo not edit generated code.
Syntaximports, literals, JSX attributes, declarationsDo not use raw colors.
Metricsfile size, function size, complexityFlag code that grew beyond a review threshold.
Module graphresolved imports, package boundariesUI must not import persistence modules.
Symbolsdefinitions, references, exportsMigration is not complete until old API calls are gone.
Typeschecker facts, public API shapesUse generated SDK types instead of ad hoc JSON.
Callscaller -> callee edgesProduction roots must not reach raw admin APIs.
Control flowbranches, guards, cleanup orderOpen transaction must be cleaned up.
Data flowsource -> sink paths, barriersRequest data must not reach shell execution.

The central design question is not "which analyzer is best?" It is "which facts does this policy need, and how much approximation can the team tolerate?"

What "State Of The Art" Actually Means

There is no single state of the art because static analysis is a stack of methods. A modern engine is usually state of the art in one or two layers, not all of them.

LayerMature methodsCurrent production directionMain limit
Parsingnative parsers, tree-sitter, compiler frontendsmulti-language normalized factssyntax is not semantics
Local factsAST visitors, symbol tables, metricstyped fact stores with spansfragile across languages
Data flowmonotone worklists, SSA, IFDS/IDEsparse value-flow and path queriesaliasing and summaries
CallsCHA, RTA, VTA, points-tolanguage-specific dispatch modelsdynamic features and callbacks
Memoryalias analysis, MemorySSAsparse memory def/use graphsprecision vs. cost
Whole-programDatalog, graph queries, relational DBsdatabase-backed and incremental analysiscache invalidation and schema complexity
Taintsource/sink/sanitizer modelslabels, exactness, interprocedural tracesmodel completeness
Agent loopsJSON/SARIF, focused rerunsdeterministic repair feedbackfalse-positive repair churn

This is why "language agnostic" has to be phrased carefully. The policy surface can be language agnostic. The semantic providers underneath are necessarily language specific.

Current State

The ecosystem is specialized. ESLint custom rules are JavaScript modules with a meta object and a create(context) visitor factory. That model is excellent for JavaScript syntax policies, but the docs explicitly warn that core rules are not a public API for extension, which means teams copy logic when they need something core-like but local.

Semgrep occupies a different authoring point: YAML rules with pattern matching and taint mode. In taint mode, authors define sources, propagators, sanitizers, and sinks. Semgrep also documents important semantic details that rule authors must understand: source and sanitizer exactness changes whether subexpressions are tainted or sanitized; taint findings include a trace; interprocedural and interfile analysis increase power and memory cost.

CodeQL is deeper again. It extracts a database, then runs QL queries over language-specific schemas. CodeQL path queries require sources, sinks, and a path graph; the result is not only "there is a bug" but a displayable route from source to sink. This is the mature form of static analysis as evidence.

polint sits in a newer niche. It is not trying to replace ESLint, Ruff, Biome, golangci-lint, or CodeQL. Its README positions it as a Rust framework for repo-local static-analysis rules: the team owns the policies, while the framework supplies file discovery, parsers, typed facts, diagnostics, caching, CI output, and an SDK.

How The Major Families Differ Internally

The tools that appear similar in a CI log often have very different internal models.

Tool familyInternal shapeWhat it is good atWhat this implies for polint
ESLint-style visitortraverse ESTree and report nodesfast syntax-local JavaScript rulesgood inspiration for simple rule ergonomics, not enough for multi-language policy
Semgrep-style pattern/taintpatterns plus source/sink/sanitizer modelsgrep-like rules that can grow into taintexactness and model knobs should be explicit
CodeQL-style databaseextracted relational DB plus QL queriesvariant analysis, path problems, security queriesfacts and queries should be separable and cacheable
Joern CPGcode property graph plus traversalsexploratory slicing across syntax, CFG, PDGgraph exploration is powerful but needs bounded APIs
SVF/LLVMIR, pointer analysis, MemorySSA, sparse value flowprecise compiled-language data/value flowsparse internal graphs can sit behind simple policy queries
Souffle/Dooprecursive Datalog relationswhole-program fixed-point analysesrelational facts are a serious implementation option
MLIR data flowlattice states over IR anchors and use-def subscriptionsreusable compiler analysesdependency-driven propagation should be an engine primitive

The shared trend is not "everything becomes one tool." It is fact separation: parse once, derive stable facts, query those facts, and attach evidence.

The Algorithmic Center Has Not Changed

The foundational ideas are old and still central:

text
while facts keep changing:
apply transfer functions
join results at merge points
enqueue dependents

Modern systems differ in what the "facts" are and how dependencies are represented:

Engine styleFactsDependency edge
Dense CFG solverstate at every basic blockCFG predecessor/successor
SSA sparse solverstate at SSA valuedef-use edge
MemorySSA solvermemory access versionsdefining access / clobber chain
IFDS/IDE solver(point, fact) exploded nodesrealizable interprocedural edge
Datalog enginerelationsrule body to derived head
Code databaseextracted tablesquery dependency and result provenance
Incremental enginecached factsinvalidation dependency graph

The implementation details are different, but the intellectual shape is still fixed-point computation plus approximation control.

The Current Frontier Is Incremental, Explainable, And Bounded

Modern static analysis has already learned how to find deep facts. The pressure now is running those facts cheaply and making them actionable.

  1. Incremental analysis: CodeQL's public direction combines cached base databases with changed-code analysis for pull requests. Research prototypes show that fully incremental query evaluation can make small updates fast, but initial indexing and memory can be very expensive. The engineering lesson is to design cache keys and invalidation before the engine gets large.
  2. Explainable paths: CodeQL path queries, Semgrep taint traces, and Joern reachableByFlows all point at the same UX requirement: a warning without a path is hard to repair.
  3. Policy-level knobs: Semgrep's exactness, CodeQL barriers/additional flow steps, and polint's planned budgets all expose analysis semantics as part of the rule.
  4. Sparse representations: SSA, MemorySSA, SVF-style value-flow graphs, and MLIR sparse analyses avoid traversing irrelevant CFG edges when a value graph is enough.
  5. Honest uncertainty: dynamic dispatch, reflection, unresolved imports, framework callbacks, and missing summaries should produce precision labels or unknowns, not silent clean results.

This is the context in which polint is interesting: it can make these ideas available for repo-local policies without requiring every team to operate a full variant-analysis stack.

Hard Data That Shapes The Current State

The strongest lesson from production and research data is that "more static analysis" is not one variable. Different engines hit different walls: tuple invalidation, memory blowups, timeouts, source/sink modeling, graph size, context explosion, and benchmark drift.

Tool / methodHard dataWhat it actually proves
CodeQL incremental production scansGitHub reported average PR scan speedups over seven days: JavaScript/TypeScript 29/47/70% and Python 11/57/70% for <3 min, 3-7 min, and >7 min baseline buckets in March 2026; later C/C++ and Go data reported C/C++ 17/34/46% and Go 9/16/25%.Incrementality is now production-relevant, but numbers are query-suite/setup-specific.
CodeQL incremental research prototypeFSE 2023 paper reported full incremental initialization around 66-67 minutes and 70-72 GB memory on two Ruby projects; hybrid initialization around 14-15 minutes and 21-23 GB.Small updates can be fast, but stable IDs and memory are central blockers.
Semgrep CEOfficial docs describe CE data flow as intraprocedural; default CLI guardrails include 5 s per rule/file timeout, timeout threshold 3, and 1 MB max target size.Default scans are bounded products, not proofs that every file/path was analyzed.
FlowDroidPLDI 2014 reported 93% recall and 86% precision on DroidBench 1.0; real-app analysis in that paper was often under 1 minute for top Google Play apps.Deep Android taint can work, but benchmark/configuration/lifecycle/source-sink choices matter.
SouffleCAV 2016 OpenJDK analysis: context-insensitive points-to 35 s / 8.5 GB; context-sensitive points-to 6:44:08 / 206.4 GB; security analysis 14:45:01 / 75.3 GB.Datalog can be fast, but context sensitivity and relation size can dominate memory.
Joern / CPGIEEE S&P 2014 Linux kernel CPG: about 52M nodes, 87M edges, 110 min import, 14 GB graph plus 14 GB index; four traversals found 18 previously unknown kernel vulnerabilities.Graph queries can support expert vulnerability hunting, but graph construction/storage is a large artifact.
LLVM MemorySSALLVM docs frame MemorySSA as replacing many MemoryDependenceAnalysis uses because careless dependency scans can become quadratic; MemorySSA uses one memory variable and walkers/AA for clobbers.Sparse memory representation is a performance design, not a complete alias solution.

These numbers should discipline the article. A repo-local policy engine should not promise "full static analysis." It should promise explicit capability tiers, bounded queries, machine-readable unknowns, and focused rules whose cost is proportional to the policy being asked.

Why AI Agents Change The Pressure

AI coding agents make prose instructions less sufficient. A prompt or AGENTS.md entry can say "use the generated billing client", but the agent still has to remember the rule, find the violating call, know the approved replacement, and rerun a check. Static analysis turns that into a concrete repair object.

Recent feedback-loop studies point in the same direction, while also warning against naive automation:

FindingMeasurementInterpretation
Mixed feedback beats single feedback in FeedbackEval63.6% repair successAgents benefit from combined external signals.
Compiler-only feedback in the same benchmark49.2% repair successA diagnostic signal can be useful but too narrow.
Bandit/Pylint loop on PythonSecurityEvalsecurity issues >40% -> 13%Deterministic static checks can reduce issues.
Same Bandit/Pylint loopreadability >80% -> 11%, reliability >50% -> 11%Static feedback may help general quality even more.
LLM-only iterative security refinement+37.6% critical vulnerabilities after five iterationsFeedback loops can degrade security when feedback is not grounded.

The synthesis is not "run more tools." It is: use deterministic tools as external oracles, cap iteration, and keep the feedback small enough to act on.

Repair Infrastructure Architecture

Static analysis becomes repair infrastructure when the diagnostic is designed for a loop, not only for a human reading terminal output.

text
build_repair_infrastructure(repo):
policies = load_repo_local_rules(repo)
facts = analyze_repo(repo, policies.required_capabilities)
diagnostics = run_policies(policies, facts)
for diagnostic in diagnostics:
attach:
stable rule id
precise span
path or local evidence
precision/status
suggested repair direction when known
fingerprint for baselines
write:
compact terminal summary
full JSON report
optional SARIF report
agent_repair_loop():
run check
select one rule_id or one diagnostic cluster
inspect only relevant files and evidence
edit smallest policy-preserving change
rerun focused check
stop when clean, unknown, or iteration budget reached

The engine is doing product work here. It is shaping the feedback so the repair actor, human or agent, does less guessing.

The Useful Static-Analysis Product

The durable product is a repairable diagnostic:

text
rule_id: local/no-raw-admin-reachable
severity: error
file: internal/http/routes.go
range: 42:5-42:23
message: production route reaches raw admin API
evidence:
root: POST /billing/refund
target: dangerousAdmin
path: handler -> refund -> dangerousAdmin
precision: conservative
max_depth: 8

This is different from a style warning. It is an interface between repository policy and a repair loop. The agent can filter by rule_id, inspect one file, verify the evidence, edit the call path, rerun only the relevant rule, and stop when the report is empty.

flowchart LR Policy[Local policy] --> Rule[Repo-local rule] Code[Repository code] --> Facts[Static facts] Rule --> Engine[Analysis engine] Facts --> Engine Engine --> Diagnostic[Structured diagnostic] Diagnostic --> Agent[Agent repair loop] Agent --> Code

Practical Implications

Treat static analysis as a portfolio:

NeedPrefer
Formatting and common language rulesExisting formatter/linter
Security variant analysis across many reposCodeQL or Semgrep-style engine
JavaScript syntax policyESLint or typescript-eslint rule
Architecture and import boundariesModule graph facts
Agent-facing repo conventionsRepo-local policy rules with JSON output
Source-to-sink security ruleTaint/data-flow query with explicit model
Review-only ruleDiff-gated check

For the polint article, the strongest framing is not that generic linters are weak. They are strong at their domains. The gap is local knowledge: internal APIs, migration states, security guardrails, design tokens, and review obligations that generic rule packs cannot know without becoming bespoke.

Sources