Skip to content

stack-usage-analyzer: detect parameters that can be const (pointer/reference const-correctness) #13

@SizzleUnrlsd

Description

@SizzleUnrlsd

Summary

Add a static diagnostic to stack-usage-analyzer that flags function parameters (pointers and references) that are not modified and therefore can be made const. This improves type safety, catches accidental mutations at compile time, and documents intent for maintainers.

Background / Motivation

Const-correctness is a key type-safety mechanism. In function signatures, the most valuable const qualification is the one that applies to the pointee / referred object, not merely to the pointer variable itself.

For a pointer X *ptr, const can appear in multiple places:
• const X *ptr / X const *ptr: the pointed-to X cannot be modified through ptr
• X * const ptr: the pointer variable cannot be re-assigned, but the pointed-to X is still mutable

This rule focuses on:
• T* parameters that can be turned into const T*
• T& parameters that can be turned into const T&
• Optionally, in C++: T** → T* const* / const T** cases are tricky and should be handled conservatively (see “Edge cases”).

Problem

Today, the analyzer does not report when a parameter is used read-only, e.g.:

void myfunc (      int * param1,  // object is modified
             const int * param2,
                   int * param3, // should be const int *
             int * const param4) // const is on pointer, not on pointee
{
  *param1 = *param2 + *param3 + *param4;
}

or:

void increment (int & value,
                int & increment) // should be const int &
{
  value += increment;
}

Expected Behavior

When a function parameter is not used to modify the object it points/refers to, emit a diagnostic suggesting a stronger type:
• int *param3 → const int *param3
• int * const param4 → const int *param4 (or additionally keep pointer-const if useful locally)
• int &increment → const int &increment

The diagnostic should include:
• function name
• parameter name
• current type and suggested type
• a short reason: “parameter is never used to modify the pointed/referred object”

Repro / Test Cases

Test 1: pointer parameter only read

#include <stdint.h>

void myfunc(int32_t *out, const int32_t *in, int32_t *param3, int32_t * const param4)
{
    *out = *in + *param3 + *param4;
}

Expected diagnostics
• param3: suggest const int32_t *
• param4: suggest const int32_t * (note: int32_t * const is not helpful for API type-safety)

Test 2: reference parameter only read (C++)

void increment(int &value, int &inc)
{
    value += inc;
}

Expected diagnostics
• inc: suggest const int &

Test 3: noncompliant because it is modified

void set_zero(int *p)
{
    *p = 0;
}

Expected diagnostics
• none

Detection Specification

Scope
• C and C++ functions (as supported by stack-usage-analyzer)
• Only parameters (not locals), for API-quality/type-safety diagnostics

What counts as “modifying the parameter’s pointee/referent”

A pointer parameter T* p is considered “modifiable” (cannot become const T*) if any of the following occurs on any path:
• Store through p (e.g., *p = …, p[i] = …)
• Passing p (or *p / derived pointer) to a callee that can mutate pointee (see interprocedural rules below)
• Taking a non-const reference to *p (C++) or exposing it to unknown aliasing

A reference parameter T& r is considered “modifiable” (cannot become const T&) if:
• Any store to r
• Passed as non-const reference to another function
• Its address is taken and used in a mutating way

Interprocedural behavior (pragmatic)

To avoid false positives, treat calls conservatively:

If p is passed to a function and the callee signature is known:
• If the corresponding parameter is const T* / const T&, safe (read-only)
• If it is T* / T&, assume it may modify ⇒ do not suggest const
• If it is variadic / unknown / function pointer / external without prototype, assume may modify

If the callee signature is unknown at analysis time, assume may modify.

How to treat int * const param4

T * const on a parameter only makes the local copy of the pointer non-reassignable; it does not protect the pointee. Therefore:
• If the pointee is read-only, recommend changing to const T * (and optionally mention that T * const does not improve API safety).

Edge Cases / Constraints
• Double pointers (T**): recommending const is subtle and easy to get wrong (e.g., const T** is not generally compatible with T**).
Recommendation: phase 1 should ignore or warn-only for T** / T*& unless you implement correct variance rules.
• Pointers to function / void*: likely skip or treat as unknown.
• Volatile: do not suggest adding const if it changes semantics or conflicts (volatile T* etc.). At minimum, preserve existing qualifiers.
• Macros: ensure diagnostics map back to user-visible locations reasonably (use debug info / source ranges where available).

Proposed Output Format

Example diagnostic:
• Severity: Info (or Low)
• Code: CT-SA-CONST-001
• Message:
parameter 'param3' in 'myfunc' is never used to modify the pointed object; consider 'const int32_t *param3'

For param4:
parameter 'param4' is declared 'int32_t * const' but the pointed object is never modified; consider 'const int32_t *param4' for API const-correctness

Implementation Ideas (LLVM/Clang-based)

Option A: Clang AST-based (recommended if you already parse AST)
•	For each ParmVarDecl with pointer/reference type:
•	Track uses within the function body:
•	writes to deref (UnaryOperator *, ArraySubscriptExpr in LHS, etc.)
•	passing to callees (inspect callee FunctionDecl param types when resolvable)
•	If no mutating use: suggest const-qualified pointee/referent type
Option B: LLVM IR-based (if your analyzer runs post-frontend)
•	Identify function arguments (llvm::Argument)
•	Track derived pointers (GEP/bitcast) and detect store instructions into memory pointed to by those derived pointers
•	For call sites, use function type metadata when available; otherwise conservative
•	Emit diagnostic using debug locations (DILocalVariable for args can be inconsistent; AST is usually cleaner for signatures)

Acceptance Criteria

•	Detects and reports read-only T* parameters as candidates for const T*
•	Detects and reports read-only T& parameters as candidates for const T&
•	Does not report when the pointee/referent is modified (directly or via a non-const callee)
•	Provides stable, actionable diagnostics with correct source locations and suggested type strings
•	Includes unit tests for the repro cases above (and at least one negative test)

Notes

This diagnostic is complementary to stack usage analysis: it improves API correctness and reduces mutation-related bugs, and it can be implemented as an additional rule in the analyzer’s static checks suite.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions