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 |
|
Analysis C/C++ replacement | STIG project | Shared |
Stub-checkout runtime stand-ins | local customer project checkout under | customer project stub-overlay headers / BUILD targets |
Stub headers that shadow upstream headers |
|
|
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
Run the focused analysis build:
./gradlew :activities:<activity>:stigAnalysisP5ApplyBitcodeTransformsInspect undefined symbols from the analyzer step if P6 has run:
activities/<activity>/build/stig/stigAnalysisP6RunAnalyzer/unresolved_application_symbols.jsonInspect P5 bitcode directly when needed:
llvm-nm-18 --undefined-only activities/<activity>/build/stig/stigAnalysisP5ApplyBitcodeTransforms/module.bc llvm-cxxfilt-18 _ZN3app...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 |
|---|---|---|
| Always returns. No taint-relevant data passes through. | Empty body, default value, or |
| Never returns normally. |
|
| Input data unconditionally flows to an output or return value. | Actual copy/load/store logic matching the real signature. |
| Data propagation depends on input or state. | Case-specific modeled logic with the condition documented. |
Decision tree:
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 |
| It terminates the path. |
|
| Depends on real data-flow behavior. |
Communication |
| 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:
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:
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:
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
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.Use
extern "C"and a mangled name only when C++ declarations would be less reliable than the exact symbol.Keep replacement sources under the STIG project's component/repository directory, mirroring original paths.
Use strong definitions. Duplicate definitions should reveal configuration mistakes.
Stub headers must be hermetic: do not include real upstream framework headers from the shadow include tree.
pass_throughandconditionalmodels must contain real load/store or copy logic that PhASAR can see.Add layout
static_asserts when the model declares upstream data structures by hand.
Implementation Patterns
Nop
Noreturn
Aggregate Output Transfer
Per-Field Transfer Model
Workflow
Audit the available evidence and identify the affected rule and P5 behavior.
Inspect its original definition, callers and required effects in Full LLVM; cross-check source if available.
Preserve the file-level analysis boundary. Choose simple transfers or a STIG-owned C/C++ stub.
Add the smallest model in the correct location.
Add its project registry rule definition and select it with an activity transform-rule
use(...)entry, or wire a required stub-overlay header.Validate the affected P5 result:
./gradlew :activities:<activity>:stigAnalysisP5ApplyBitcodeTransformsCompare 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.