Dev ToolingDev Tooling
Static Code Analysis Tools: Where to Start
Dev Tooling

Static Code Analysis Tools: Where to Start

Lewis SmithArticle by Lewis Smith

A static analyser reports a fault on a branch no test in the suite will ever take, and it reaches that conclusion without running the program once. The same analyser returns an identical list for the same commit on every run, and OWASP's directory of source code analysis tools notes that the class identifies only a relatively small percentage of the security flaws present in the code it analyses.

Both statements describe one program. How much of your code the tool can reach is fixed by the model it builds from the text. What it says about the code it reaches is fixed by the rules it checks against that model. Every choice you make about static code analysis tools is a choice about one of those two layers, so the model comes first.

How a static analysis tool models source text

An analyser does not read your intent. A static analysis tool analyses text, converts the text into a structure, and asks questions about the structure. Oligo Security's guide to static code analysis sets out seven methods, and each one builds a different model of the same file.

  1. Lexical analysis splits source text into tokens. It catches an unclosed string or an invalid character before any tree exists.
  2. Syntax analysis builds a parse tree against the grammar of the language. It catches an unmatched bracket or a misplaced semicolon.
  3. Semantic analysis attaches meaning to the tree: one type per expression, one scope per identifier. It catches arithmetic between incompatible types.
  4. Control flow analysis builds a graph of the paths through a function. It catches a branch that cannot be reached and a loop that cannot terminate.
  5. Data flow analysis follows each value from its definition to its use. It catches a variable read before it is written, and a null dereference that depends on one branch.
  6. Symbolic execution evaluates a function over symbolic inputs, exploring many paths in a single pass. It finds the assertion that fails for a narrow combination of inputs.
  7. Pattern-based analysis matches the tree against known bad shapes. It catches the insecure API call and the query assembled by string concatenation.

Two consequences carry the rest of this page. The analyser can report on code that has never executed, because the control flow graph contains paths the test suite never takes. The analyser cannot tell you whether the program is correct, because a graph of paths is not a record of behaviour.

NASA's Software Engineering Handbook documents 100% statement coverage for static testing, along with all possible execution paths and variable values, and notes that the method can begin as soon as the first line of code exists. Dynamic testing observes behaviour during execution and finds the defects whose cause is too complex for a model to carry. The handbook treats the two as complements and expects a project to run both.

Determinism is the property a merge gate can use

Rule-based analysis returns the same findings for the same input. Codacy's guide to static code analysis tools explains that a deterministic analyser produces identical results on every run, and that each finding names the rule that produced it, which makes the output reproducible, explicable and auditable. The same guide notes that AI code review analyses a change the way a person would, so its results vary between runs on an unchanged input. That variation settles where the gate goes: the model comment is a second opinion, and the deterministic check is the one that blocks the merge.

A gate that returns a different verdict on an unchanged commit cannot be argued with, cannot be audited, and gets switched off. A gate that names a rule can be discussed, suppressed with a reason, or fixed. Codacy's five criteria for judging a tool follow from this: rule depth for the languages and frameworks you actually use, quality and security findings from one scan under one policy, a single organisation-wide gate scored on new code, a documented route for dismissing a false positive, and the same rules available in the IDE, on the command line and at the merge check.

What the model never contains

The model is built from source text, so anything that is not source text is outside it. OWASP's page shows what that costs. The category struggles with authentication problems, access control issues and insecure use of cryptography. It cannot see configuration errors, because configuration is frequently not represented in the code at all. It cannot analyse code that will not compile, which in practice means it needs the correct libraries, the compilation instructions and all of the required source. It often cannot prove that a finding is a real vulnerability. False positives run high.

A finding is therefore a question, and the question needs a person. A rule says that this shape of code has been wrong before. A tool determines whether it is wrong here.

Where the rules come from

Most rules are not invented by the vendor. They encode a published standard, and the standard was written by people who had already seen the failure. Parasoft's guide to static analysis under ISO 26262 gives the example of MISRA C:2023 Directive 4.12, which exists to prevent dynamic memory allocation in automotive software:

The identifiers 'calloc', 'malloc', 'realloc', 'aligned_alloc' and 'free' shall not be used and no macro with one of these names shall be expanded.

When you view that as a rule, the issue of false positives no longer appears to be a flaw in the tool. A line of deliberate, tested C can contradict the directive, and the analyser is doing exactly what it was configured to do. Parasoft's framing is that the rule stays and the deviation is recorded, with a human accepting or rejecting the exception and the decision documented. ISO 26262 names control flow analysis and data flow analysis as verification activities, and the tools that perform them also report complexity and quality metrics into an audit trail.

NASA turns the same idea into a task identifier. The handbook cites NASA-STD-8739.8, which requires a project to define and set software quality objectives or threshold levels for static code analysis defects, and to confirm for each software release that the code has been scanned for security defects and coding standard compliance. It also advises defining, acquiring and configuring the analysers before coding begins. Where a rule set is an audit record, switching a rule off costs more than a configuration commit.

Five instruments, five different objects of analysis

No single tool analyses everything, and the fastest way to waste a budget is to buy one expecting it to. Codacy's taxonomy splits the field by the object each instrument studies.

A linter analyses one language at a time and enforces style, formatting and a narrow class of simple bugs. It is the cheapest to adopt and the least likely to find a security defect.

A SAST scanner analyses your own source and traces how external input moves through it. It follows data from a form field or a URL parameter to a database query or a shell command, and that trace is what distinguishes it from a pattern matcher. Codacy reports that Opengrep, one of the scanners in this class, covers more than 30 languages. Language count is the wrong headline to buy on; rule depth for your framework is the right one.

A secret scanner examines every file in the repository, including the files no build touches, looking for keys, tokens and credentials.

An IaC scanner analyses Terraform, CloudFormation and Kubernetes manifests as source, and reports misconfiguration in place of code defects.

An SCA scanner analyses your manifest and your lock file, and reports known vulnerabilities and licence conflicts in the packages you depend on. The tool analyses the packages, but your own source is out of its scope.

How to configure a static analysis setup

A static analysis setup is a rule file in the repository, at a pinned version, with the analyser version pinned next to it. Audacia's write-up on Roslyn analyzers shows the shape in C#.

# .editorconfig
# Multiple enum values are placed on the same line of code.
dotnet_diagnostic.SA1136.severity = none
# Two sibling elements which each start on their own line have different levels of indentation.
dotnet_diagnostic.SA1137.severity = suggestion
# Use literal suffix notation instead of casting.
dotnet_diagnostic.SA1139.severity = warning

Each line names a rule and a severity. The file is committed with the code it governs, so one commit produces one set of findings on a laptop and on the build server. Audacia's account describes a custom analyser that enforces one house rule, private fields prefixed with an underscore, and the matching code fix that inserts the underscore. The rule and its repair are in the repository next to the rule file.

Five steps make that arrangement work.

  1. Pin the analyser version and commit the rule file next to the source. A rule change then arrives as a reviewable diff.
  2. Run one full pass over the current codebase and freeze the output as a baseline. The gate scores new findings only from that point, which keeps the build green on the day you switch it on.
  3. Tag every rule with the job it does: security, correctness or style. A reviewer then knows which findings can stop a merge and which ones wait for a cleanup pass.
  4. Route each new finding to the change that introduced it. A finding with an author and a commit has an owner.
  5. Put an expiry date on every suppression. A suppressed finding with no expiry becomes permanent in practice.

Steps 2 and 5 do most of the work on noise. Codacy's guide recommends severity levels for ranking fixes, a dismissal route that logs the reason for the dismissal, and baselining so that pre-existing issues raise once and then stay quiet. Uproot Security's guide to static code analysis tools adds the configuration point: a default rule set generates noise, tuning it is the work, and findings need ranking by security and business impact. The tool is cheap; the policy around its output is what costs you.

How to start the first four days

  1. Day one: install the analyser for your main language, run it, and count the findings.
  2. Day two: write the rule file, turn off every rule you cannot defend in one sentence, and commit it.
  3. Day three: create the baseline and wire the check into the merge.
  4. Day four: read the first ten findings the gate blocks and classify each one as a real defect, a rule that needs tuning, or a suppression with an expiry.

The tooling side of this is settled. The first C checker, Lint, was written by Stephen Johnson at Bell Labs in 1978, according to the guide published on DEV Community, and it flagged constructs that were syntactically valid and probably wrong. The constructs have changed since; the job has not. You pick the model, you pick the rules, and you keep the file.

Sources

The figures behind this

Static testing statement coverage
100% (NASA)
Automotive software standard
MISRA C:2023
Static analysis methods
7 (Oligo)

Related guides

What Code Quality Tools Actually Do
What Code Quality Tools Actually Do
Continuous Deployment Tools That Earn Their Place
Continuous Deployment Tools That Earn Their Place
Getting Started With Dependency Management Tools
Getting Started With Dependency Management Tools

← All Guides