Skip to content

Reduce ConstParameterNotModified false positives by fixing IR-to-source parameter binding and tightening mutation analysis #57

Description

@SizzleUnrlsd

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::appendNodethis mutates ir_.nodes
Internal cache mutation missed ~1 StackEscapeRuleMatcher::modelSaysNoEscapeArg mutates namesCache
this / artificial parameter not filtered ~2 AnalysisPipeline::run constthis mapped to Module&
Unnamed parameter poorly attributed ~3 applySmtBackendOption — parameter '' with string& type

Proposed fix

Staged approach, highest-yield generic fixes first:

  1. 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.

  2. 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.

  3. 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.

  4. Add Argument::onlyReadsMemory() fast-path — skip mutation analysis entirely for parameters already proven readonly by LLVM.

  5. Introduce ParameterDebugBinding model — carry name, type, line, column, confidence, isArtificial, isAnonymous so ConstParamIssue consumes structured binding data instead of rebuilding partial state inline.

  6. 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.

  7. 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.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions