Problem
The ConstParameterNotModified analysis in src/analysis/ConstParamAnalysis.cpp currently produces ~48% false positives when run on the project's own codebase. Two root causes have been identified:
1. Incorrect IR-to-source parameter mapping in getParamDebugInfo() (line 291)
When a function returns an aggregate by value, Clang emits a hidden sret parameter at IR argument position 0. This shifts all Argument::getArgNo() values by 1 relative to the debug info numbering (DILocalVariable::getArg() and DISubroutineType::getTypeArray()).
The current fallback path (line 319) uses types[Arg.getArgNo() + 1] without compensating for hidden ABI parameters, producing diagnostics where the reported parameter name and type are mismatched (e.g., reporting shouldAnalyze with type IndirectTargetResolver& when it should be targetResolver).
A generic fix for this already exists in the codebase: UninitializedVarAnalysis.cpp (line 1621) uses a hiddenPrefix = irArgCount - debugParamCount pattern that handles sret and other hidden ABI parameters uniformly.
Additionally, getParamDebugInfo() relies solely on DISubprogram::retainedNodes() as primary source, which is frequently empty (observed as !{}). The DbgVariableRecord / findDVRDeclares API — already used by LocationResolver.cpp (line 102) and UninitializedVarAnalysis.cpp (line 368) — provides more reliable parameter-to-debug-info binding but is not used in ConstParamAnalysis.
2. Overly simplistic mutation model in valueMayBeWrittenThrough() (line 419)
The current analysis is a boolean DFS that misses indirect mutations through reference members (e.g., ConstraintIrBuilder::ir_ — a reference member whose pointee is mutated) and internal cache mutations (e.g., StackEscapeRuleMatcher::namesCache). It also does not leverage Argument::onlyReadsMemory() as a fast-path exit for parameters already proven readonly by LLVM attributes.
Observed false positive categories
| Category |
Count |
Example |
| Name/type shifted by sret offset |
~6 |
buildFunctionEscapeFacts: name shouldAnalyze emitted with type IndirectTargetResolver& |
| Mutation through reference member missed |
~2 |
ConstraintIrBuilder::appendNode — this mutates ir_.nodes |
| Internal cache mutation missed |
~1 |
StackEscapeRuleMatcher::modelSaysNoEscapeArg mutates namesCache |
this / artificial parameter not filtered |
~2 |
AnalysisPipeline::run const — this mapped to Module& |
| Unnamed parameter poorly attributed |
~3 |
applySmtBackendOption — parameter '' with string& type |
Proposed fix
Staged approach, highest-yield generic fixes first:
-
Apply hiddenPrefix normalization in getParamDebugInfo() — reuse the irArgCount - debugParamCount pattern from UninitializedVarAnalysis.cpp:1621 to fix the sret offset for both the retainedNodes path and the DISubroutineType::getTypeArray() fallback.
-
Add DbgVariableRecord as primary debug binding source — scan findDVRDeclares / findDbgUsers / findDbgDeclares on entry-block instructions before falling back to retainedNodes or getTypeArray(), consistent with LocationResolver and UninitializedVarAnalysis.
-
Filter synthetic parameters — check DINode::FlagArtificial and DINode::FlagObjectPointer (already used at UninitializedVarAnalysis.cpp:1605) to suppress diagnostics on compiler-generated this and other artificial parameters.
-
Add Argument::onlyReadsMemory() fast-path — skip mutation analysis entirely for parameters already proven readonly by LLVM.
-
Introduce ParameterDebugBinding model — carry name, type, line, column, confidence, isArtificial, isAnonymous so ConstParamIssue consumes structured binding data instead of rebuilding partial state inline.
-
Replace boolean mutation result with a small lattice — {NoWrite, MayWrite, Unknown} to distinguish proven-safe from unknown (conservatively treated as MayWrite) from proven-written, enabling future refinement of indirect mutation tracking.
-
Add emission quality gate — suppress or downgrade diagnostics when binding confidence is weak, the parameter is synthetic, the type is anonymous, or the pattern is a non-actionable forwarding reference.
Validation
cmake --build build -j$(nproc)
- Targeted self-analysis smoke check comparing info counts before/after
python3 run_test.py --analyzer ./build/stack_usage_analyzer
- Focused regression tests for: aggregate-return (
sret), methods with object pointers, anonymous parameters, and DbgVariableRecord-only bindings
Scope
In: src/analysis/ConstParamAnalysis.cpp, shared helper extraction under src/analysis/ and include/analysis/ if needed.
Out: CLI/report format changes, ad hoc suppressions for specific function names, broad const-refactors outside ConstParam, test file edits without explicit approval.
Problem
The
ConstParameterNotModifiedanalysis insrc/analysis/ConstParamAnalysis.cppcurrently produces ~48% false positives when run on the project's own codebase. Two root causes have been identified:1. Incorrect IR-to-source parameter mapping in
getParamDebugInfo()(line 291)When a function returns an aggregate by value, Clang emits a hidden
sretparameter at IR argument position 0. This shifts allArgument::getArgNo()values by 1 relative to the debug info numbering (DILocalVariable::getArg()andDISubroutineType::getTypeArray()).The current fallback path (line 319) uses
types[Arg.getArgNo() + 1]without compensating for hidden ABI parameters, producing diagnostics where the reported parameter name and type are mismatched (e.g., reportingshouldAnalyzewith typeIndirectTargetResolver&when it should betargetResolver).A generic fix for this already exists in the codebase:
UninitializedVarAnalysis.cpp(line 1621) uses ahiddenPrefix = irArgCount - debugParamCountpattern that handlessretand other hidden ABI parameters uniformly.Additionally,
getParamDebugInfo()relies solely onDISubprogram::retainedNodes()as primary source, which is frequently empty (observed as!{}). TheDbgVariableRecord/findDVRDeclaresAPI — already used byLocationResolver.cpp(line 102) andUninitializedVarAnalysis.cpp(line 368) — provides more reliable parameter-to-debug-info binding but is not used inConstParamAnalysis.2. Overly simplistic mutation model in
valueMayBeWrittenThrough()(line 419)The current analysis is a boolean DFS that misses indirect mutations through reference members (e.g.,
ConstraintIrBuilder::ir_— a reference member whose pointee is mutated) and internal cache mutations (e.g.,StackEscapeRuleMatcher::namesCache). It also does not leverageArgument::onlyReadsMemory()as a fast-path exit for parameters already proven readonly by LLVM attributes.Observed false positive categories
buildFunctionEscapeFacts: nameshouldAnalyzeemitted with typeIndirectTargetResolver&ConstraintIrBuilder::appendNode—thismutatesir_.nodesStackEscapeRuleMatcher::modelSaysNoEscapeArgmutatesnamesCachethis/ artificial parameter not filteredAnalysisPipeline::run const—thismapped toModule&applySmtBackendOption— parameter''withstring&typeProposed fix
Staged approach, highest-yield generic fixes first:
Apply
hiddenPrefixnormalization ingetParamDebugInfo()— reuse theirArgCount - debugParamCountpattern fromUninitializedVarAnalysis.cpp:1621to fix thesretoffset for both theretainedNodespath and theDISubroutineType::getTypeArray()fallback.Add
DbgVariableRecordas primary debug binding source — scanfindDVRDeclares/findDbgUsers/findDbgDeclareson entry-block instructions before falling back toretainedNodesorgetTypeArray(), consistent withLocationResolverandUninitializedVarAnalysis.Filter synthetic parameters — check
DINode::FlagArtificialandDINode::FlagObjectPointer(already used atUninitializedVarAnalysis.cpp:1605) to suppress diagnostics on compiler-generatedthisand other artificial parameters.Add
Argument::onlyReadsMemory()fast-path — skip mutation analysis entirely for parameters already proven readonly by LLVM.Introduce
ParameterDebugBindingmodel — carryname,type,line,column,confidence,isArtificial,isAnonymoussoConstParamIssueconsumes structured binding data instead of rebuilding partial state inline.Replace boolean mutation result with a small lattice —
{NoWrite, MayWrite, Unknown}to distinguish proven-safe from unknown (conservatively treated asMayWrite) from proven-written, enabling future refinement of indirect mutation tracking.Add emission quality gate — suppress or downgrade diagnostics when binding confidence is weak, the parameter is synthetic, the type is anonymous, or the pattern is a non-actionable forwarding reference.
Validation
cmake --build build -j$(nproc)python3 run_test.py --analyzer ./build/stack_usage_analyzersret), methods with object pointers, anonymous parameters, andDbgVariableRecord-only bindingsScope
In:
src/analysis/ConstParamAnalysis.cpp, shared helper extraction undersrc/analysis/andinclude/analysis/if needed.Out: CLI/report format changes, ad hoc suppressions for specific function names, broad const-refactors outside
ConstParam, test file edits without explicit approval.