Skip to content

What is static code analysis?

Static code analysis is checking source code for bugs and security flaws without running it, usually with linters, type checkers, and security scanners.

Last updated , 8 min read

What is static code analysis?

Static code analysis is a method that checks source code without running it, using rules and type information to flag likely bugs and security flaws. It is also called static analysis or static program analysis. The tools are static analyzers, and each finding names a rule and a line, so a developer can fix it before the code runs.

The International Software Testing Qualifications Board (ISTQB) defines static analysis as an automated process that evaluates a component or system without executing it. The Wikipedia article says the term usually means analysis by a tool. When a person reads the code, the activity is called program comprehension or code review.

Static analysis is the counterpart of dynamic analysis, which observes a program while it runs. An analyzer reads all of the code, including error paths that no test reaches, and it needs no test data or running app. Teams usually run it before code review, so reviewers can spend their time on design and intent.

How does static analysis work?

A static analyzer checks code in five steps:

  1. The analyzer reads the source files and parses each one into a syntax tree, a structured form of the code.
  2. The analyzer builds a model of the program from the trees, e.g. the type of each value.
  3. Each enabled rule searches the trees or the model for a pattern, e.g. a variable that is assigned and never read.
  4. The analyzer records each match as a finding with a rule ID, a severity level, a message, and a location.
  5. The analyzer prints the findings or saves them to a file, and usually exits with a nonzero code on an error.
How a static analyzer turns code into findings Source files never run Parser syntax trees Program model types and paths Check each rule Findings rule, file, line Rule set configured Report terminal or SARIF
The code is read, never run. The rules decide what counts as a finding, so a problem that no rule describes produces no finding.

Static checks come in five main kinds:

  • Lint rules. Tools for linting apply style and correctness rules to each file, e.g. ESLint for JavaScript.
  • Type checks. Type checking confirms that each value is used according to its declared or inferred type, e.g. no call to a method the type lacks.
  • Data flow analysis. The analyzer follows a value through the program, e.g. from a URL parameter into a page's HTML. Tracing untrusted input this way is called taint analysis, and security tools use it for static application security testing (SAST).
  • Code metrics. The tool measures the structure of the code, e.g. cyclomatic complexity, which counts the linearly independent paths through a function.
  • Formal analysis. Some analyzers use abstract interpretation, which approximates every possible run, to show that a class of error cannot occur, e.g. division by zero. The cost is more false alarms.

Common open-source static analysis tools differ by language:

  • JavaScript and TypeScript. ESLint applies lint rules, and the TypeScript compiler checks types.
  • Python. Ruff applies lint rules, and mypy checks types.
  • Go. The go vet command reports suspicious constructs, e.g. a format string that does not match its arguments.
  • Rust. Clippy adds lint rules to the checks that the Rust compiler runs.

What is an example of static analysis?

Here is an illustrative example. Acme Co. sells furniture online. A developer asks a coding agent to "Let customers edit their delivery address during checkout." The agent writes this TypeScript function:

import type { Address, Order } from "./order";

export async function updateAddress(order: Order, address: Address) {
  await order.setDeliveryAddress(address);
  return order;
}

The TypeScript compiler, run with npx tsc --noEmit, exits with a nonzero code and reports error TS2339 at line 4, Property 'setDeliveryAddress' does not exist on type 'Order'. The Order type in order.ts has no such method, and the checker read both files to find the mismatch.

Many analyzers can save findings as SARIF, the Static Analysis Results Interchange Format. SARIF is a JSON format that the Organization for the Advancement of Structured Information Standards (OASIS) publishes as a standard. Code hosts and editors use it to show findings from any tool that writes it. The TypeScript compiler needs a converter to produce SARIF. The same finding as a shortened SARIF result looks like this:

{
  "ruleId": "TS2339",
  "level": "error",
  "message": {
    "text": "Property 'setDeliveryAddress' does not exist on type 'Order'."
  },
  "locations": [{
    "physicalLocation": {
      "artifactLocation": { "uri": "src/checkout/address.ts" },
      "region": { "startLine": 4, "startColumn": 15 }
    }
  }]
}

The agent replaces the call with saveAddress() and a reload of the order, and the type check passes. A customer with 2 items in the cart then edits the address. The address saved, but the cart emptied. The reload rebuilt the order without its items, and no rule describes what a cart should hold after a reload.

This example is simplified. A real project would also run a security scan and a checkout test.

What changes when a coding agent writes the code?

A coding agent can run the analyzer after each edit and read the findings as its next input. The findings arrive before any test runs and name the exact line. A type checker in that loop catches some AI hallucinations, e.g. the invented method in the Acme example.

An agent in that loop can also clear some type errors without fixing their cause, e.g. by changing "strict": true to false in tsconfig.json. That setting turns off a group of type checks for the whole project, e.g. strict null checks, though not the TS2339 error in the example.

A team can run the same checks in a pre-commit hook and as a quality gate on each change, from configuration files the agent does not edit. A locked configuration does not stop a suppression comment in the code, e.g. // @ts-expect-error. A lint rule can flag those comments, e.g. @typescript-eslint/ban-ts-comment. It also does not stop a rewrite that passes the check and empties the cart, as in the Acme example.

A scan of the files in an agent's change also reports findings that were there before. A SARIF file can mark each result as new, unchanged, updated, or absent compared with a baseline run, so reviewers can pick out the findings that the change added.

What can static analysis not find?

Static analysis finds problems that are visible in the text of the code. It misses three kinds of problem:

  • Bugs that depend on runtime state. The values a program reads while it runs, e.g. the records a database returns, are not in the source code. The Acme failure depended on the order that the reload rebuilt.
  • Code that does the wrong thing without breaking a rule. Code that passes the type checker can still do something other than what was asked, and a missing requirement leaves nothing to flag.
  • Properties that no analyzer can decide. No analyzer can decide a nontrivial property of program behavior correctly for every program, a result known as Rice's theorem. Each tool balances false positives, findings that are not real problems, against false negatives, real bugs it misses. Teams turn off rules that report too many harmless findings.

An analyzer run with no findings does not show that the code works. Running code finds failures that reading it misses, e.g. the emptied cart.

How is static analysis different from linting?

Linting is one kind of static analysis. A linter usually checks one file at a time against style and correctness rules, and it runs fast enough for each save. Other static analysis needs more of the program, e.g. the type check in the Acme example, which needed two files. The two overlap in lint rules that use type information. The typescript-eslint rule no-unnecessary-condition reads types from the TypeScript compiler and flags a condition that the types show is always truthy or always falsy.

FAQs

What is SARIF?

SARIF, the Static Analysis Results Interchange Format, is an OASIS standard JSON format for the findings of static analysis tools. Each result carries a message and usually names a rule, a level, and a location, so code hosts and editors can show findings from any tool that writes SARIF.

Is static analysis the same as SAST?

Static analysis is not the same as SAST, although SAST is one use of it. SAST applies static analysis to security, e.g. tracing untrusted input from a URL parameter into a page's HTML. Static analysis also covers checks that have nothing to do with security, e.g. lint rules for style.

Does static analysis produce false positives?

Static analysis produces false positives, because a tool that never runs the code has to approximate which values and paths are possible. Teams usually turn off rules that report too many harmless findings and compare each run with a baseline, so reviewers can pick out the findings that a change added.

Can static analysis replace code review?

Static analysis cannot replace code review. An analyzer checks code against fixed rules, while a reviewer judges whether the change does what was asked and fits the design. Running static analysis first lets the reviewer spend time on design and intent instead of problems a tool can flag.