STIG Help

LLVM/analyzer toolchain

Building stig-code-analyzer and generating consumer evidence involve four different toolchain concerns:

Job

What

Pinning rule

1

Host C++ compiler/linker that builds PhASAR and the analyzer

The OS default clang is used. Fedora and Ubuntu can differ. Native-object-only builds use the host BFD linker.

2

LLVM libraries linked by PhASAR and used to read bitcode

LLVM 18, pinned by LLVM_MAJOR in stig-code-analyzer/scripts/pins.env.

3

Compiler that emits the analyzed bitcode

Must emit LLVM 18-compatible bitcode.

4

Tools that inspect user source or IR and affect evidence

Use the versioned LLVM 18 binaries declared by StigLlvmToolchain.

Jobs 2 and 3 must agree on the LLVM IR version. Job 1 does not need to be the same compiler version, but it must be a modern clang supported by the local platform. Job 4 is pinned for reproducible semantics, not only file-format compatibility: different LLVM releases can legitimately produce different measurements and symbol-derived selections from the same input.

Analysis tools are part of the evidence definition

Any Clang/LLVM tool whose output changes selected compilation units, transformed IR, symbol names, metrics, diagrams, or generated evidence must default to the matching constant in stig-engine/src/main/kotlin/com/stigway/engine/types/StigLlvmToolchain.kt. This includes clang, clang++, clang-tidy, llvm-link, llvm-dis, llvm-as, llvm-extract, llvm-nm, opt, and llvm-cxxfilt. Do not replace a missing pinned binary with an unversioned executable from PATH or with another installed LLVM major. An explicit task/test override is still allowed where the API provides one, but it must remain deliberate rather than acting as automatic recovery.

clang-tidy is a concrete example of why IR compatibility alone is not enough. LLVM commit 1b5d6ec68553 deliberately changed readability-function-size so Clang 22 counts constructor initializers as statements by default. Clang 18 skips an empty-body constructor after observing zero statements; Clang 22 can report the same constructor with one statement per base/member initializer. Both results match their respective implementations, but they produce different function counts and metrics for identical C++ source. The LLVM major is therefore part of the meaning of the generated metric, not merely an installation detail.

The intentionally unversioned host-tool cases are narrower:

  • the host clang/clang++ that compiles PhASAR and stig-code-analyzer itself; and

  • the host ld.bfd that links the analyzer only when PhASAR LTO is disabled and every PhASAR archive member is a native object; and

  • host clang-format, clang-tidy, and run-clang-tidy used to format or lint the analyzer's own C++ sources against the compile database and system headers produced by that host compiler.

Those operations verify or build STIG; they do not measure consumer code or generate consumer evidence. In particular, the analyzer-source lint task's unversioned clang-tidy does not justify using an unversioned clang-tidy in the P7 function-metrics task.

The required root ciToolchainCheck runs the shared build/test/static-check gate in the content-addressed Fedora 44 CI image. Its analyzer-source lint uses that image's host ClangTidy 22 and matching system headers. The focused ciToolchainTidyCheck runs only the analyzer build/tests and lint prerequisites in the same image. Neither changes consumer-analysis tool selection: LLVM 18 remains pinned for analyzed bitcode and linked libraries. preReleaseCheck requires the full container pass without an opt-in property; a host-only pass cannot satisfy it. Ubuntu 24.04 remains a release-only build/test compatibility environment, without a second formatting or lint policy.

Analysis compile recipe and inlining boundaries

STIG recompiles every selected analysis input with its own LLVM 18 compile policy. This policy applies to selected native code, generated harnesses, models/stubs, and test fixtures regardless of which compiler, language dialect, or optimization options produced the original application. The production compile database still supplies source-specific inputs such as include paths, preprocessor definitions, and target options, but it does not select STIG's compiler, language standard, or analysis-owned optimization flags.

The current analysis compile recipe is:

Input language

Compiler/toolchain

Required options

Analysis reason

C

clang-18/ LLVM 18

-std=c17

The analyzed C source must be ISO C17-compatible. Current consumer cases and strict-C integration coverage do not establish a need for GNU extensions.

C++

clang++-18/ LLVM 18

-std=c++17

The analyzed C++ source must be C++17-compatible. Current consumer headers require C++17 library features.

Both

LLVM 18

-O2

Produces the inlined accessor, smart-pointer, and memory-operation shapes required by the current PhASAR analyses.

Both

LLVM 18

-g

Retains source locations used in analysis evidence.

Both

LLVM 18

-fstandalone-debug

Retains complete compiler type layouts for named member transfers, including functions entering P5 as declarations.

Both

LLVM 18

-fno-discard-value-names

Retains readable LLVM SSA names for diagnostics.

Both

LLVM 18

-mllvm -inline-threshold=100000

Keeps the high inlining budget paired with -O2 so relevant trivial/template operations become analyzable IR.

Both

LLVM 18

-fno-vectorize -fno-slp-vectorize

Prevents vector-store shapes that the current PhASAR field-sensitive analyses do not interpret correctly.

These are analysis compatibility requirements, not claims about the production compiler configuration and not a request to reproduce it exactly. A source may compile successfully in the production build and still be outside the current STIG analysis boundary if it requires another language dialect. Change this recipe only when a concrete consumer requirement and corresponding test coverage justify the change. Environment-specific standard-library compatibility options, such as the staged GCC toolchain used on affected Fedora hosts, may be appended without changing the LLVM major or language policy.

P5 replacement protection and required helpers

P5 runs the raw compiler stage even when no function rules are configured. Clang receives -Xclang -disable-llvm-passes; STIG then removes selected original definitions and obsolete declaration/call attributes before running LLVM optimization. Supplied replacement bodies retain noinline optnone. Kotlin-generated bodies are installed at the existing subsequent P5 transform stage.

The compiler and final verifier share LlvmRequiredInlineHelper. For recognized array, smart-pointer, address, copy/fill and harness helpers, P5 uses this order:

  1. Remove helper noinline, optnone and alwaysinline attributes, allowing ordinary O2 optimization and inlining.

  2. Run the normal O2 policy. Helper bodies and ordinary callers simplify while protected models retain their own attributes.

  3. Mark the optimized helpers alwaysinline and run always-inline,verify.

  4. Link compilation units and repeat always-inline,verify to resolve calls across units.

Optimizing helper bodies before expanding them avoids freezing their unoptimized local storage and nested calls inside an optnone caller. A high ordinary inline threshold alone does not provide that guarantee. Helpers must remain available for ordinary inlining during O2: temporarily making all of them noinline can prevent copy-idiom recognition in callers and leave pointer loops instead of llvm.memcpy. An explicit NEVER/protected-body rule on a required helper is a configuration conflict; it is not silently overridden. Explicit ALWAYS rules on recognized helpers follow the same optimization-before-expansion order. Source/sink markers and the generated entry retain their separate retention contracts.

Compiler evidence records each exact helper symbol, its classification and policy actions separately from user rules, together with optimization/inlining commands and artifact hashes. Final function contracts still reject remaining required-helper calls and unsupported address uses. Compiler/native fixtures must also establish that nested helper calls became visible address/load operations and preserve expected data-flow dependencies.

StigLlvmToolchain.analysisFlags() owns the common options in this recipe. A generated harness must not add -fno-inline for the whole compilation unit (or an equivalent inliner-disable option). Includes are textual: when the harness includes the activity implementation header, such a flag also changes every inline or template function body emitted from that header. It therefore changes the analyzed activity, not only the generated entry.

Retain only call identities that carry analysis semantics, at the narrowest source location:

  • harness::Entry::Execute() is noinline, used, so the configured entry remains a definition;

  • the generated construction/lifecycle compound statement uses statement-level [[clang::noinline]], preserving its direct calls without disabling optimization inside the called header-defined bodies;

  • source/sink marker functions use explicit noinline, used anchor attributes because PhASAR attaches source/sink semantics to their call identities; and

  • ordinary generated helpers keep the common optimization policy, while plumbing whose data-flow semantics requires inlining may explicitly use always_inline.

Do not mark all generated functions noinline. That would retain non-semantic boundaries, change the IR and suppress the accessor/smart-pointer/memory-operation inlining that the analysis recipe requires. Required entry, marker, and lifecycle-call retention belongs in postconditions over the final P5 LLVM module. The generated harness_contract.json requires the entry to remain a noinline definition in llvm.compiler.used, requires its direct lifecycle calls in CFG execution order, and requires an exact source/sink marker definition and call inventory. A compiler-coalesced base destructor is accepted only when the implementation class's own vtable selects that exact symbol for a non-deleting destructor slot. See Analysis harness for the generated boundary.

The generated harness contract also establishes a closed-world module root. P5 internalizes public definitions before post-transform global dead-code elimination; llvm.compiler.used retains the generated entry and marker inventory, while public generated-library functions that are unreachable from that boundary no longer keep irrelevant undefined serialization dependencies alive. Modules without a generated harness contract retain their original linkage.

Referenced project or framework functions do not receive global symbol-verifier exemptions. Each one must have a real definition in the final module or an active activity transform rule. Unused function declarations are removed from the verifier inventory through LLVM use-def analysis, while a referenced declaration without a definition or active model remains an unresolved application symbol and produces a HIGH finding. With failAt=CRITICAL, P6/P7 can continue with explicitly provisional results.

P2 replaces native debug modes such as -g0, -ggdb3, and -gline-tables-only, including repeated occurrences, with the canonical StigLlvmToolchain.DEBUG_INFO_FLAG (-g) in the converted database. It also replaces native debug-type limits with StigLlvmToolchain.DEBUG_TYPES_FLAG (-fstandalone-debug). This retains field names and complete layouts needed by named member transfers; older incomplete metadata cannot support those rewrites. Complete types can increase debug metadata size, while the executable operations remain governed by the same optimization policy. Debug format and source-path options remain intact; P1 preserves the original native arguments. This conversion also applies to build-tool actions retained in P1/P2 before the full LLVM inventory excludes isTool=true configurations. Conflicting debug modes in P2 output indicate a conversion defect; clearing the native Bazel cache does not repair those commands.

P2, P3, and final P5 hard-fail unless every emitted structured compile-command entry is effectively equivalent to this policy for STIG-owned option families. .c inputs use pinned clang-18 with canonical ISO C17; .cc, .cpp, and .cxx inputs use pinned clang++-18 with canonical C++17. An explicit supported -x mode takes precedence over the suffix. The check also requires one optimization level, the exact paired inliner threshold, full debug/value-name behavior, and both vectorizer disables. It rejects unknown languages, duplicates, later contradictory family values, and any -fno-inline... option for the whole compilation unit while permitting unrelated Bazel/user flags. StigBuildBitcodeTask repeats the same check on the final command immediately before each compiler launch, so the consumed command—not only the earlier database artifact—is covered.

Shared Bazel execroot access

Activity subprojects in one Gradle build use the same Bazel execution root, including its generated outputs and external/ symlink tree. StigBazelExecrootAccessCoordinator is one build-scoped Gradle shared service with maxParallelUsages(1). Every STIG task that invokes Bazel or runs standalone Clang/LLVM over paths in that execroot must declare usesService(...). This serializes those tasks across activity subprojects, including native builds, compile command extraction, harness schema inventory, P3/P5 bitcode compilation, and P7 description and metric collection.

The coordinator is an exclusive-access lock plus a per-workspace cache for bazel info execution_root. It does not edit or repair execroot files, and it does not coordinate independent Gradle invocations or Bazel processes started outside the current Gradle build. Consumer configuration-file edits use the separate contract documented in Concurrency handling.

What setup.sh Installs

stig-code-analyzer/scripts/setup.sh installs the OS packages needed by the native analyzer build and checks:

  • the host Clang compiler and matching ClangFormat/ClangTidy

  • LLVM/Clang 18 and matching LLVM development libraries for the linked analyzer and emitted bitcode

  • Z3

  • CMake and Ninja

  • libcurl and standard C++ build tools

Full scripts/setup.sh invokes this installer so a new development machine has the native prerequisites before running cleanup or native builds. Its JS-only, cleanup-tools-only and Graphify-only modes do not install them. The Gradle task stigInstallDeps also wraps this setup for normal local use. It is an OS dependency installer only; analyzer source builds still go through stigBuildAnalyzer/build-analyzer.sh.

What build-analyzer.sh Builds

stig-code-analyzer/scripts/build-analyzer.sh builds:

  1. PhASAR, from the pinned fork commit in pins.env by default, or the explicitly selected local working tree.

  2. stig-code-analyzer, linked against that PhASAR install.

The build output lives under ~/.cache/stig/stig-code-analyzer/. The source checkout is not polluted by CMake build trees.

BUILD_ANALYZER=0 builds only the pinned PhASAR install. CI uses that mode in the prebuilt toolchain image so regular build jobs can link the analyzer quickly against a cached PhASAR.

In a STIG source checkout, :stig-code-analyzer:stigBuildAnalyzer passes --run-analyzer-tests: analyzer CMake uses BUILD_TESTING=ON, GoogleTest supplies the native cases, and CTest discovers and runs them. The prebuilt toolchain-image step uses BUILD_ANALYZER=0, so there is no analyzer executable to test there; the later STIG build_and_test job links the analyzer and runs the native gate. Every PhASAR source build enables PHASAR_BUILD_UNITTESTS=ON and PHASAR_BUILD_IR=ON, then runs all registered CTest executables before installation. CTest failures, an empty test inventory, and fixture-compilation failures stop installation. Each test executable has a 300-second timeout; the CTest report is build/phasar/phasar-unit-tests.xml under the analyzer cache root. CTest is invoked directly, without the upstream check-phasar-unittests target's distribution-specific suite exclusions. Individual upstream disabled cases and optional feature suites remain separately reportable limitations.

PhASAR test IR uses the pinned LLVM compiler. A host-header compatibility defect must be repaired or reported; disabling PhASAR tests or substituting another LLVM major is not a recovery path. The install marker includes unit-tests-v1, so an install produced under the former tests-disabled policy cannot satisfy this gate. A matching prebuilt install reuses the completed gate; it does not rerun the tests on every unchanged consumer invocation. When setup has staged the GCC 15 compatibility toolchain at ~/.cache/stig/gcc15-toolchain/usr, the build script passes its prefix as PHASAR_TEST_GCC_TOOLCHAIN. PhASAR's fixture generator applies --gcc-toolchain to its pinned Clang invocations, matching StigLlvmToolchain.hostCompatibilityFlags() for STIG fixtures and analysis inputs. The STIG native CMake fixtures receive the same prefix as STIG_TEST_GCC_TOOLCHAIN, including cases using standard-library headers. Both fixture compilers retain the pinned LLVM major. The host compiler used to build PhASAR/analyzer libraries keeps its own standard library. The selected fixture toolchain prefix is recorded in the log and install identity; an invalid explicit prefix fails configuration.

Fedora 44 developer, consumer and CI native setup all provision this prefix through stig-code-analyzer/scripts/setup-gcc15-toolchain.sh. It downloads the gcc15 and gcc15-c++ RPMs at FEDORA_GCC_COMPAT_VERSION in pins.env, verifies their signatures, and stages only their C++ headers and compiler support files in the user's cache. Both scripts/setup.sh and stig-code-analyzer/scripts/setup.sh invoke it on Fedora. For an existing host with the other prerequisites already installed, run from the STIG checkout:

bash stig-code-analyzer/scripts/setup-gcc15-toolchain.sh

This compatibility prefix is needed when pinned Clang 18 compiles C++ using Fedora 44's standard-library headers: PhASAR test fixtures, STIG native tests, and consumer source analysis can all need it. cleanupCheck also reaches PhASAR's tests while building the analyzer to obtain its compilation database for ClangTidy. Model-only consumers do not need a native toolchain. Ubuntu 24.04 uses its compatible system headers and does not run this Fedora helper. The host Clang 22 build keeps its normal system toolchain; no developer-machine cache is copied into the CI image. Local fixture, unit-test, and CMake-helper changes are inputs to stigBuildAnalyzer, alongside PhASAR implementation changes. The STIG native tests and Kotlin taint/semantic integration suites remain additional checks.

The analyzer sources packaged into a published JAR omit development-only src/test/cpp, so the lazy installer leaves BUILD_TESTING=OFF. See Local multi-repo development for the exact sibling-edit and JVM test lifecycle.

Local source mode

Edit stigSource in the consumer's gradle.properties: published (the default) uses stigVersion and its pinned PhASAR, ignoring sibling directories; local requires both STIG and PhASAR working trees and ignores stigVersion. PhASAR must be next to the canonical STIG checkout, including when the consumer's ../stig is a symlink. See Local multi-repo development for source selection and the separate cache directories.

For an explicit source-development build from STIG, run:

./gradlew :stig-code-analyzer:stigBuildLocalAnalyzer

STIG's standalone stigBuildAnalyzer instead reads STIG's own stigSource property and defaults to pinned PhASAR. The obsolete stig.localPhasar property is rejected. The low-level script receives --local-phasar <checkout> or --no-local-phasar explicitly; directory presence never selects a mode.

The binary self-reports the provenance:

~/.cache/stig/stig-code-analyzer/local/build/cmake/stig-code-analyzer --version # stig-code-analyzer version=<v> phasar=local # stig-code-analyzer version=<v> phasar=<sha>

The self-report is an enforced postcondition. The native build checks it immediately after linking. When a composite consumer selects local mode, its P6 task requires phasar=local and refuses both a stale pin and a lazy pinned fallback. P6 also verifies that every emitted analysis.json reports the exact STIG, analyzer, and PhASAR identities of the binary it executed.

Build-flag Alignment

PhASAR publishes cxx_std_20 on its library targets, so the analyzer is also built as C++20. RTTI remains enabled. The analyzer build pins a portable CPU baseline (x86-64-v2) instead of inheriting -march=native; see Build portability.

The important flags are centralized in build-analyzer.sh:

  • PHASAR_TARGET_ARCH=x86-64-v2

  • analyzer CMAKE_CXX_FLAGS includes -march=x86-64-v2

  • USE_LLVM_FAT_LIB=ON makes PhASAR and the analyzer link the pinned installation's consolidated shared LLVM target. This avoids distribution static-archive debug/index formats which an otherwise compatible linker may reject; absence of that pinned shared target is a hard configuration error.

  • PHASAR_ALLOW_LTO_IN_RELEASE_BUILD is enabled only when the unversioned host Clang major equals the pinned LLVM major. A differing host compiler emits an incompatible LLVM-bitcode archive under LTO, so that case builds ordinary native object archives instead. The host major and resulting LTO policy are part of the PhASAR install-cache marker. The matching-LTO mode links with pinned ld.lld; the native-object mode explicitly links with host ld.bfd.

  • PHASAR_USE_SVF=OFF — the external SVF dependency and backend are not built or installed; cflanders and union-find are PhASAR-native alias analyses. PhASAR's header install may still contain its small optional SVF compatibility header, but no SVF library is present or linked.

  • PHASAR_USE_Z3=ON — Z3 remains required for optional path-feasibility evidence.

  • PHASAR_BUILD_TOOLS=OFF — STIG links the PhASAR libraries into stig-code-analyzer and does not ship the unused standalone phasar-cli.

Rebuild Rules

The build marker .stig-built-from records the installed PhASAR provenance. A pin change or local PhASAR edit invalidates the Gradle inputs and rebuilds the analyzer. Local source is read directly from the sibling; only its CMake build and install products are written below ~/.cache/stig/stig-code-analyzer/.

Useful commands:

./gradlew :stig-code-analyzer:stigBuildAnalyzer ./gradlew :stig-code-analyzer:stigBuildAnalyzer --rerun-tasks

For a clean native cache:

rm -rf ~/.cache/stig/stig-code-analyzer ./gradlew :stig-code-analyzer:stigBuildAnalyzer

Pinned Inputs

  • stig-code-analyzer/scripts/pins.env — LLVM major and PhASAR fork commit

  • stig-code-analyzer/scripts/build-analyzer.sh — native build orchestration

  • stig-code-analyzer/scripts/setup.sh — OS dependency installer

  • optional sibling ../phasar — local C++ engine override

23 October 2026