STIG Help

Analysis harness

Source-code analysis runs a selected activity boundary, not the full target program. The analysis harness supplies the entry point, framework stand-ins, and interface marker calls that make this boundary explicit in LLVM IR.

What The Harness Provides

An activity harness normally provides:

  • an entry function configured as the analysis entry point;

  • calls into the real activity lifecycle or kernel code selected for analysis;

  • stubs for framework APIs that would otherwise require the full runtime;

  • source marker calls for subscribed input fields;

  • sink marker calls for published output fields.

The harness should be as small as possible. It should drive the same activity paths that production code uses, for example subscriber and publisher registration paths, and avoid duplicating payload transfer logic manually in the entry point.

stigGenerateAnalysisHarness is the cacheable generator. Configure a reviewed analyzerInput.harness.lifecycleProfile and the implementation/topics inputs; STIG generates the C++ wrapper, harness_interface_points.h, and harness_contract.json under the element's build/stig/stigGenerateAnalysisHarness/harness/ directory. P5 consumes those outputs directly, so stigGenerateHtml schedules generation when enabled analysis needs the harness. The same-name root aggregate generates harnesses for all configured elements.

Generated harness files are not committed. Input changes invalidate the generator and affected analysis tasks; valid up-to-date and build-cache outputs remain reusable. There is no separate synchronization step or previous-output approval baseline. Explicit authored harness fragments, models, and stubs remain supported through their DSL inputs.

stigConfigureAnalysis creates or reconciles analysis configuration as an explicit source-editing command. Its preview and bootstrap validation use a separate temporary candidate directory under build/stig/stigConfigureAnalysis/; they do not own the report generator's output. See STIG workflows.

The generated harness is an output, not a scanner input. The cacheable per-element stigExtractAnalysisInterfaceInventory task runs the compiled C++ analysis-interface scanner first, using the activity's exact P3 compile command, implementation header/source, topics JSON, annotations, and pinned Clang toolchain. Its versioned inventory under build/stig/stigExtractAnalysisInterfaceInventory/ retains endpoint direction, exact qualified topic identity, public declared payload spelling, canonical/desugared payload identity, public field paths, and the reconciled ASIL-bearing harness leaves. The KTE-based harness renderer and KotlinPoet-based context renderer consume that same artifact independently. A successful inventory task is therefore never followed by a second scan inside stigGenerateAnalysisHarness.

Clang indexes payload record shapes by fully qualified C++ type. Generated opb::app and msg::app records may therefore use the same short name while exposing their own member sets. STIG reports a conflicting record definition only when the same qualified type resolves to more than one public-member shape in the selected compile context; such a conflict indicates a genuine include, macro, or schema-version inconsistency that must be corrected at its producer.

Optimization And The Generated Call Boundary

Every P5 compiler input uses the common StigLlvmToolchain.analysisFlags() recipe, including the generated harness, custom models, and selected native compilation units. Do not add -fno-inline to the harness compile command. The harness includes the activity implementation header, so a flag applied to the whole compilation unit also disables inlining in function bodies emitted from that header and materially changes the analyzed activity code.

The generated lifecycle harness instead applies Clang's statement-level [[clang::noinline]] attribute only to the compound statement that constructs the activity and invokes its reviewed lifecycle. This retains those direct call edges from harness::Entry::Execute() without changing optimization inside the called constructor or lifecycle definitions. Calls nested inside included-header definitions may still inline under the normal -O2 and inliner threshold policy. A post-link transform must not rely on a nested symbol that this common policy legitimately inlines; reconcile that transform or introduce a reviewed, narrower analysis boundary instead of changing the complete harness compilation unit.

When an explicit cycle needs a narrow piece of object-state initialization but the activity's complete ExecuteInit() would add unrelated runtime behavior, configure the optional analyzerInput.harness.preCycleInitializationMethod with one reviewed, unqualified, no-argument C++ method name. The generated harness invokes that method after construction and optional ExecuteInit(), but before InitSubscribers(). The lifecycle profile keeps its established meaning; this property records the additional method and its exact place in the reviewed call sequence.

The generator also writes the transient harness_contract.json beside the generated C++ and interface-point header under build/stig/. The contract distinguishes the generated lifecycle entry from the configured analyzer entries and is consumed directly as a task-exchange artifact; it is not committed. After linking and transforms, stigAnalysisP5ApplyBitcodeTransforms checks the final module.ll, and stigAnalysisP5GenerateAnalyzerRunPlan checks the P5 interface inventory and analyzer run plan. They hard-fail unless:

  • harness::Entry::Execute() remains a noinline definition referenced by llvm.compiler.used;

  • its direct constructor, optional ExecuteInit, optional pre-cycle initialization wrapper, subscriber, step, publisher, and destructor calls remain in the configured lifecycle order;

  • the optional pre-cycle wrapper directly calls the configured concrete activity method; and

  • the source/sink marker definitions and marker calls exactly match the generated marker inventory;

  • every configured analyzer entry remains a definition retained through llvm.compiler.used; and

  • every generated analyzer run uses exactly those configured entries.

This is the postcondition for the narrow attributes above. A successful C++ compile alone is insufficient because a later link, optimization, or transform could otherwise remove an analysis boundary.

Source And Sink Marker Functions

In InterfacePointMode.LLVM_HARNESS_DISCOVERY, STIG discovers source and sink symbols from the compiled analysis LLVM module (module.ll). It demangles LLVM symbols and matches only the hard-coded harness marker prefixes:

  • harness::SourceGroup<

  • harness::SourceLeaf<

  • harness::SinkGroup<

  • harness::SinkLeaf<

The harness exposes those symbols by calling marker functions in the harness namespace. harness::SourceLeaf<...> and harness::SinkLeaf<...> provide field-level interface points; harness::SourceGroup<...> and harness::SinkGroup<...> provide group-level interface points.

For a leaf path that crosses a fixed-size std::array or native C++ array, the generated adapter visits every array element at that path segment. Nested fixed arrays are traversed at every annotated segment. All elements reuse the same topic/field tag and therefore remain one logical source or sink identity; optimized LLVM may represent the traversal as several calls to that marker or as one call site inside a loop. Scalar path segments are visited once. The generated harness must not select a representative element such as index zero because a production activity can write safety- relevant values only at another index.

For leaf markers, STIG parses the template arguments:

  • template argument 1 is the topic type;

  • template argument 2 is the harness::tags::... field path.

The annotation JSON is not used to discover marker symbols. It only attaches ASIL and other metadata to marker symbols that already exist in module.ll. Therefore every source or sink that should participate in analysis must be materialized by the harness or an activity-specific stub/model source.

Activity-Specific Code

Generic stubs should stay activity-independent. Authored activity-specific harness fragments, source seeding, sink observation, and native models should live with the activity resources and be selected explicitly in the DSL. Generated wrappers and interface headers remain in the generator's build directory.

When the activity or its interface metadata changes, the next report build regenerates affected harness markers. Maintain any explicit authored marker code alongside the implementation it models. Inspect the generated component_interface_points_llvm_symbols.json to confirm STIG discovered the expected source and sink symbols.

Relationship to P3, P4, and source selection

A generated analysis harness is a synthesized source compilation unit. An authored custom replacement .cpp is a P5-provided source compilation unit. Both enter P5 and must not be added to the native P3 provenanceBuild set. Native compilation-unit selection remains explicit Kotlin DSL:

P3 = softwareUnderAnalysis + transform-owner provenance compilation units P4 = softwareUnderAnalysis { include(...) } P5 = P4 + P5-provided and synthesized source compilation units, then active activity transform rules

stigConfigureAnalysis persists compilation-unit selections and activity transform-rule use(...) entries in the activity Gradle build script. It preserves existing explicit selections unless command options override them. Proposed function models and their activity use(...) entries require engineering review; a successful link alone does not confirm their semantics. stigCheckAnalysis --configuration-only derives and reviews that boundary; stigCheckP5SymbolCompleteness enforces the absence of missing required P5 definitions. stigClassifyP5MissingSymbols can classify remaining symbols into inactive real-unit, other-element-rule, or unresolved candidates without editing configuration. Any JSON produced by these tasks remains diagnostic output under build/stig/.

Use the element's stigConfigureAnalysis when changing its source selection, native target, lifecycle, or external function models. --preview writes candidates and findings under build/; the normal invocation edits the owned configuration regions. Review the changes to activity use(...) entries and project registry rule definitions, then start a new Gradle invocation for the report. Ordinary C++ or interface changes regenerate affected build outputs without a separate configuration command. See STIG workflows for command scope and Gradle task API for exact options.

23 October 2026