STIG Help

Stub derivation guide

How to derive C++ analysis stubs for a STIG activity element. The function-model and validation contracts live in Bitcode rewrite modeling; this page adds source placement and implementation notes.

Overview

STIG source-code analysis links a focused LLVM module from the selected activity compilation units, harness files, stub headers, optional stub sources, and bitcode transform rules. Symbols outside that scope need either:

  • a transform rule modeling no operation, no-return behavior, dependency transfers, or a zero-initialized global definition;

  • a source-providing rule compiling a native model source for behavior that must be visible to PhASAR;

  • a local upstream stub overlay, for machines without the full upstream source tree.

Do not add broad, global C++ stub files unless the missing symbol genuinely cannot be modeled through a project registry rule definition and an activity transform-rule use(qualifiedFunctionName = "...") entry in analyzerInput.bitcodeTransforms.

Where Stubs Live

Use the narrowest location that matches the stub's intent.

Intent

Location

Wiring

Framework/runtime NOP or no-return behavior, or a zero-initialized global

project registry, selected with activity use(...) entries

rewriteFunctionToNop, rewriteFunctionToNoReturn, rewriteGlobalToZero

Analysis C/C++ replacement

STIG project stubs/<component-or-repository>/src/<original-relative-path>

Shared addFunctionImplementation/rewriteFunctionWithImplementation, selected by activity use(...)

Stub-checkout runtime stand-ins

local customer project checkout under third_party/stig_stubs/...

customer project stub-overlay headers / BUILD targets

Stub headers that shadow upstream headers

stig-project/stubs/include/...

stubs { iQuotePath(...) }

Analysis replacements belong to the STIG consumer, preserving original-relative paths under its established stub root. A skeleton checkout's compilation stand-ins serve a different purpose and cannot replace product evidence. Preserve the file-level softwareUnderAnalysis selection: do not mix original functions and replacements from an excluded original file through addNativeFunctionImplementation.

P5 compiles a harness with the recorded compiler context of its activity implementation and a C/C++ implementation source with the context identified by its rule's originalCompileUnit. This preserves the recorded working directory, definitions, include paths, and native input snapshot. The appended source selects its own C/C++ language. The original source directory is added as a quoted-header search path, after any configured analysis header overlays.

Missing context or several original compilation units for one implementation source require an explicit choice. Configure appendCompileContextUnits on both stigAnalysisP5SelectAnalyzerInputCompileCommands and stigSelectConfiguredAnalyzerInputCompileCommands: each key is the appended source's absolute path and each value is a single-element list containing the native compilation unit's logical path. If that unit has several recorded compiler actions, set the source's appendCompileContextActionKeys value to the exact selected action key. These task inputs also provide compilation context for standalone stub sources without a registry rule. No unrelated compiler command is borrowed when the context is missing or ambiguous. Each explicit source selection overrides only that source; the plugin's inferredAppendCompileContextUnits remains available for the other harness and implementation sources.

Prerequisites

  1. Run the focused analysis build:

    ./gradlew :activities:<activity>:stigAnalysisP5ApplyBitcodeTransforms
  2. Inspect undefined symbols from the analyzer step if P6 has run:

    activities/<activity>/build/stig/stigAnalysisP6RunAnalyzer/unresolved_application_symbols.json
  3. Inspect P5 bitcode directly when needed:

    llvm-nm-18 --undefined-only activities/<activity>/build/stig/stigAnalysisP5ApplyBitcodeTransforms/module.bc llvm-cxxfilt-18 _ZN3app...
  4. Inspect the original definition and relevant callers in llvm/full/module.ll. Source, if available, is a secondary cross-check. Compare the concrete P5 behavior against that reference before confirming the rule.

Classification

Each hand-written C++ model function should document exactly one @stub_behavior value.

Value

Meaning

Implementation

nop

Always returns. No taint-relevant data passes through.

Empty body, default value, or nullptr as appropriate.

noreturn

Never returns normally.

__builtin_trap();

pass_through

Input data unconditionally flows to an output or return value.

Actual copy/load/store logic matching the real signature.

conditional

Data propagation depends on input or state.

Case-specific modeled logic with the condition documented.

Decision tree:

Does the original function return normally? |- no: consider a no-return rule, including relevant preceding effects `- yes: Does it have relevant return, memory, initialization, lifetime or control effects? |- no: consider a NOP, justified against the activity's sinks and lifecycle `- yes: simple transfers/constant writes, or an explicit C/C++ stub

Common classifications:

Pattern

Classification

Reason

Constructor or destructor

Case-specific

Initialization, ownership, dispatch and cleanup can affect later sinks.

Getter returning an opaque framework handle

Case-specific

Preserve required pointer identity and non-null storage.

Assertion failure handler

noreturn

It terminates the path.

transform(input, output)

pass_through or conditional

Depends on real data-flow behavior.

Communication Send or Receive framework internals

nop

Sources and sinks are modeled at the harness/interface boundary.

Choosing The Mechanism

Prefer Bitcode Transforms For Runtime Noise

Author common framework/runtime project registry rule definitions once, then select them with activity use(...) entries:

bitcodeTransformRegistry { rewriteFunctionToNop(qualifiedFunctionName = "app::logging::v1::RegisterLoggingContext") rewriteFunctionToNoReturn(qualifiedFunctionName = "mem::detail::assertion_failed") rewriteGlobalToZero(globalNamePattern = "vtable for app::*") } analyzerInput { bitcodeTransforms += { use(qualifiedFunctionName = "app::logging::v1::RegisterLoggingContext") use(qualifiedFunctionName = "mem::detail::assertion_failed") useGlobal(globalNamePattern = "vtable for app::*") } }

The initial LLVM module stays untransformed. P3 retains the transform-owner provenance compilation units needed for ownership evidence, while P5 applies the active activity transform rules.

Use Configured Transfers Before Native Models

For a taint-relevant function whose behavior is just a dependency from one parameter to another parameter or return value, prefer a configured transfer:

bitcodeTransformRegistry { rewriteFunctionWithTransfer( qualifiedFunctionName = "app::GetFusedObjectDLTs", from = parameter(0), to = returnValue, reason = "DLT extraction copies selected fields from the obstacle polyline list into the returned logging payload", ) }

Add the matching activity transform-rule use(...) entry in each activity whose analysis boundary requires the model.

Large C++ return values may be lowered through a hidden sret output pointer. The DSL still uses returnValue and source-level parameter(N) numbering. When scalar source and return LLVM types differ, the transfer materializer emits the required integer-width, integer/floating-point, or float/double conversion while preserving the configured data dependency. Inspect helper implementations rather than judging their call-site size. Do not introduce Kotlin models that duplicate C/C++ algorithms. When simple transfers are insufficient, keep a clear C/C++ stub under the STIG project's component/repository stub directory and wire it with addFunctionImplementation or rewriteFunctionWithImplementation. Use addFunctionImplementation only when the final module otherwise has no definition for that symbol. Use rewriteFunctionWithImplementation when a real definition exists but must be replaced for analysis.

Put Skeleton Stand-Ins In The Upstream Stub Overlay

Skeleton/stub checkouts should provide type-compatible stand-ins from their own stub overlay:

source-repo/third_party/stig_stubs/<upstream include path> source-repo/third_party/stig_stubs/BUILD

Prefer header-only no-op/predicate bodies when the activity already includes the shadow header. If a real upstream implementation is also available, the analysis configuration should use that upstream repo instead of layering an additional project-local skeleton source over it.

C++ Model Rules

  1. Prefer demangled C++ signatures over extern "C" mangled symbols when the relevant types can be declared accurately. This makes signature drift fail at link or symbol-verification time.

  2. Use extern "C" and a mangled name only when C++ declarations would be less reliable than the exact symbol.

  3. Keep replacement sources under the STIG project's component/repository directory, mirroring original paths.

  4. Use strong definitions. Duplicate definitions should reveal configuration mistakes.

  5. Stub headers must be hermetic: do not include real upstream framework headers from the shadow include tree.

  6. pass_through and conditional models must contain real load/store or copy logic that PhASAR can see.

  7. Add layout static_asserts when the model declares upstream data structures by hand.

Implementation Patterns

Nop

/// @brief Emits diagnostics only; no configured sink or lifecycle effect is retained by this example's contract. /// @stub_behavior nop /// @see source-repo/framework/diagnostics/counter.cpp void app::diagnostics::ReportCounter(std::uint32_t) { }

Noreturn

/// @brief Assertion failure handler. Unconditionally terminates the path. /// @stub_behavior noreturn /// @see source-repo/framework/assert.cpp [[noreturn]] void mem::detail::assertion_failed( mem::handler_parameters const&, mem::detail::assert_system_handler_tag) { __builtin_trap(); }

Aggregate Output Transfer

// Extracts DLT values from a polyline list. // stub_behavior: pass through source argument to aggregate return storage. // see: source-repo/activities/example_activity/logging/output_dlt_logging.cpp rewriteFunctionWithTransfer( qualifiedFunctionName = "app::GetFusedObjectDLTs", from = parameter(0), to = returnValue, reason = "DLT extraction copies header, polyline indices, and attributes from the obstacle polyline list into the returned logging payload", )

Per-Field Transfer Model

/// @brief Models selected fan-to-polyline field propagation. /// @stub_behavior pass_through /// @see source-repo/domains/example/converters/input_to_output.cpp void app::perception::obstacles::ConvertInputToOutput( InputToOutputSettings const&, opb::app::SensorObservationBatch_V2_0& fan, mem::pmr::memory_resource*, opb::app::FusedObjectList_V1_0& out) { out.header.time_stamp = fan.header.time_stamp; out.points[0].x = fan.borders[0].position_x; out.points[0].y = fan.borders[0].position_y; }

Workflow

  1. Audit the available evidence and identify the affected rule and P5 behavior.

  2. Inspect its original definition, callers and required effects in Full LLVM; cross-check source if available.

  3. Preserve the file-level analysis boundary. Choose simple transfers or a STIG-owned C/C++ stub.

  4. Add the smallest model in the correct location.

  5. Add its project registry rule definition and select it with an activity transform-rule use(...) entry, or wire a required stub-overlay header.

  6. Validate the affected P5 result:

    ./gradlew :activities:<activity>:stigAnalysisP5ApplyBitcodeTransforms
  7. Compare P5 to Full LLVM through every configured logical sink. P6 and report alignment are a subsequent, separately authorized phase; passing compilation does not establish semantic agreement.

Maintenance

  • Re-check models when upstream signatures or layouts change.

  • Keep framework/runtime rule definitions in the project registry and select them only for activities whose analysis boundary requires them.

  • Keep C/C++ analysis replacements local to the STIG project, grouped by component/repository and original path.

  • Never reintroduce deleted global activity-specific files such as stubs/src/custom_taint_models.cpp; split sources by component and original path and wire them through bitcode transforms.

23 October 2026