[clang] [clang-tools-extra] [clang][Analysis] Handle const-qualified pointer refs in `ExprMutatonAnalyzer` (PR #190421)
Daniil Dudkin via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 3 15:35:07 PDT 2026
https://github.com/unterumarmung created https://github.com/llvm/llvm-project/pull/190421
Teach ExprMutationAnalyzer to recognize references to const-qualified pointer objects, such as `T *const &`, as non-const pointee sinks when the pointee type itself is non-const.
Fixes #190218
Assisted by Codex.
>From 1c53035beb976e32307a352a4aa733eb35c2e054 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sat, 4 Apr 2026 01:33:29 +0300
Subject: [PATCH] [clang][Analysis] Handle const-qualified pointer refs in
`ExprMutationAnalyzer`
Teach ExprMutationAnalyzer to recognize references to const-qualified
pointer objects, such as `T *const &`, as non-const pointee sinks when the
pointee type itself is non-const.
Fixes #190218
Assisted by Codex.
---
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
.../const-correctness-pointer-as-pointers.cpp | 55 +++++++++++++++++++
clang/lib/Analysis/ExprMutationAnalyzer.cpp | 24 +++++++-
.../Analysis/ExprMutationAnalyzerTest.cpp | 33 +++++++++++
4 files changed, 112 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 36e311341f336..473d58035f58e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -317,6 +317,9 @@ Changes in existing checks
- Fixed false positive where a pointer used with placement new was
incorrectly diagnosed as allowing the pointee to be made ``const``.
+ - Fixed false positives when pointers were later passed or bound through
+ const-qualified pointer references.
+
- Improved :doc:`misc-multiple-inheritance
<clang-tidy/checks/misc/multiple-inheritance>` by avoiding false positives when
virtual inheritance causes concrete bases to be counted more than once.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-pointer-as-pointers.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-pointer-as-pointers.cpp
index 4c42743af8f11..6d91de9c6fa2b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-pointer-as-pointers.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-pointer-as-pointers.cpp
@@ -10,6 +10,8 @@
// RUN: }}' \
// RUN: -- -fno-delayed-template-parsing
+#include <vector>
+
void pointee_to_const() {
int a[] = {1, 2};
int *p_local0 = &a[0];
@@ -123,3 +125,56 @@ void pointer_in_emplacement_new() {
// CHECK-NOT: warning
new(ptr) int {123};
}
+
+void takesConstPointerRef(int *const &);
+void takesConstPointerRRef(int *const &&);
+using IntPtrAlias = int *;
+typedef int *IntPtrTypedef;
+void takesAliasConstPointerRef(IntPtrAlias const &);
+void takesTypedefConstPointerRef(IntPtrTypedef const &);
+
+void ignore_const_pointer_reference_sinks() {
+ int value = 0;
+
+ int *p_local0 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local0'
+ // CHECK-FIXES: int *p_local0 = &value;
+ takesConstPointerRef(p_local0);
+
+ int *p_local1 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local1'
+ // CHECK-FIXES: int *p_local1 = &value;
+ int *const &ref = p_local1;
+ (void)ref;
+
+ IntPtrAlias p_local2 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local2'
+ // CHECK-FIXES: IntPtrAlias p_local2 = &value;
+ takesAliasConstPointerRef(p_local2);
+
+ int *p_local3 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local3'
+ // CHECK-FIXES: int *p_local3 = &value;
+ takesConstPointerRRef(static_cast<int *const &&>(p_local3));
+
+ IntPtrTypedef p_local4 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local4'
+ // CHECK-FIXES: IntPtrTypedef p_local4 = &value;
+ takesTypedefConstPointerRef(p_local4);
+
+ int *p_local5 = &value;
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'p_local5'
+ // CHECK-FIXES: int *p_local5 = &value;
+ int *volatile &volatile_ref = p_local5;
+ (void)volatile_ref;
+}
+
+void ignore_range_for_pointer_reference_sink() {
+ int value = 0;
+ std::vector<int *> const source = {&value};
+ std::vector<int *> sink;
+ // CHECK-FIXES: for (int *element : source)
+ for (int *element : source)
+ // CHECK-MESSAGES-NOT: warning: pointee of variable 'element'
+ sink.push_back(element);
+}
diff --git a/clang/lib/Analysis/ExprMutationAnalyzer.cpp b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
index 5def6ba3cac5a..224df4d2e9be2 100644
--- a/clang/lib/Analysis/ExprMutationAnalyzer.cpp
+++ b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
@@ -145,9 +145,20 @@ class ExprPointeeResolve {
// explicit cast will be checked in `findPointeeToNonConst`
const CastKind kind = ICE->getCastKind();
if (kind == CK_LValueToRValue || kind == CK_DerivedToBase ||
- kind == CK_UncheckedDerivedToBase ||
- (kind == CK_NoOp && (ICE->getType() == ICE->getSubExpr()->getType())))
+ kind == CK_UncheckedDerivedToBase)
return resolveExpr(ICE->getSubExpr());
+ if (kind == CK_NoOp) {
+ // Binding `T *` to `T *const &` only adds top-level qualifiers to the
+ // pointer object, so this `CK_NoOp` still refers to the same pointer.
+ const QualType CastType =
+ ICE->getType().getLocalUnqualifiedType().getCanonicalType();
+ const QualType SubExprType = ICE->getSubExpr()
+ ->getType()
+ .getLocalUnqualifiedType()
+ .getCanonicalType();
+ if (CastType == SubExprType)
+ return resolveExpr(ICE->getSubExpr());
+ }
return false;
}
@@ -227,6 +238,12 @@ const auto nonConstReferenceType = [] {
referenceType(pointee(unless(isConstQualified()))));
};
+const auto referenceToPointerWithNonConstPointeeType = [] {
+ return hasUnqualifiedDesugaredType(
+ referenceType(pointee(hasUnqualifiedDesugaredType(
+ pointerType(pointee(unless(isConstQualified())))))));
+};
+
const auto nonConstPointerType = [] {
return hasUnqualifiedDesugaredType(
pointerType(pointee(unless(isConstQualified()))));
@@ -767,7 +784,8 @@ ExprMutationAnalyzer::Analyzer::findPointeeMemberMutation(const Expr *Exp) {
const Stmt *
ExprMutationAnalyzer::Analyzer::findPointeeToNonConst(const Expr *Exp) {
const auto NonConstPointerOrNonConstRefOrDependentType = type(
- anyOf(nonConstPointerType(), nonConstReferenceType(), isDependentType()));
+ anyOf(nonConstPointerType(), nonConstReferenceType(),
+ referenceToPointerWithNonConstPointeeType(), isDependentType()));
// assign
const auto InitToNonConst =
diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
index c63479dc26e0b..0304d2f527ca2 100644
--- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
+++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
@@ -1756,6 +1756,14 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByInitToNonConst) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code =
+ "void f() { int* x = nullptr; int* const& b = x; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByAssignToNonConst) {
@@ -1801,6 +1809,31 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsArgument) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code =
+ "void b(int *const &); void f() { int* x = nullptr; b(x); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
+ {
+ const std::string Code =
+ "void b(int *const &&); void f() { int* x = nullptr; "
+ "b(static_cast<int *const &&>(x)); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
+ {
+ const std::string Code = "using IntPtr = int *; void b(IntPtr const &); "
+ "void f() { int* x = nullptr; b(x); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
{
const std::string Code =
"namespace std { typedef decltype(sizeof(int)) size_t; }"
More information about the cfe-commits
mailing list