https://github.com/davidmenggx created
https://github.com/llvm/llvm-project/pull/216050
`ExprPointeeResolve` did not resolve through `=`, so a write such as `*(p = q)
= 0` was not recognized as mutating `p`'s pointee and the check suggested a
`const` pointee that does not compile:
```cpp
void func(int *ptr1) {
int *ptr2 = ptr1;
int *ptr3 = ptr1;
*(ptr2 = ptr3) = 0; // warning: pointee of 'ptr2' can be declared 'const'
}
```
Godbolt: https://godbolt.org/z/3o8qnEKs3
An assignment yields an lvalue for the LHS holding the value of the RHS, so
the dereferenced object is reachable through both operands. Resolve through
both, like the existing additive case.
The RHS is already covered indirectly by AssignToNonConst, so the fix is the
LHS.
Fixes https://github.com/llvm/llvm-project/issues/216048.
>From e356fe3eb02f72d8879ee0a8856874034dd565a7 Mon Sep 17 00:00:00 2001
From: David Meng <[email protected]>
Date: Thu, 13 Aug 2026 06:43:50 -0700
Subject: [PATCH] [clang-tidy] Fix `misc-const-correctness` false positive on
writes through assigned pointers
`ExprPointeeResolve` did not resolve through `=`, so a write such as
`*(p = q) = 0` was not recognized as mutating `p`'s pointee and the check
suggested a `const` pointee that does not compile:
```cpp
void func(int *ptr1) {
int *ptr2 = ptr1;
int *ptr3 = ptr1;
*(ptr2 = ptr3) = 0; // warning: pointee of 'ptr2' can be declared 'const'
}
An assignment yields an lvalue for the LHS holding the value of the RHS, so
the dereferenced object is reachable through both operands. Resolve through
both, like the existing additive case.
The RHS is already covered indirectly by AssignToNonConst, so the fix is the
LHS.
Fixes https://github.com/llvm/llvm-project/issues/216048.
---
clang-tools-extra/docs/ReleaseNotes.md | 11 +++--
.../const-correctness-pointer-as-pointers.cpp | 43 +++++++++++++++++
clang/lib/Analysis/ExprMutationAnalyzer.cpp | 2 +
.../Analysis/ExprMutationAnalyzerTest.cpp | 48 +++++++++++++++++++
4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.md
b/clang-tools-extra/docs/ReleaseNotes.md
index ef3e6c49ce172..04596d1415e97 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -126,9 +126,14 @@ infrastructure are described first, followed by
tool-specific sections.
<clang-tidy/checks/cppcoreguidelines/use-enum-class>` check by omitting
unnamed enums from the `enum class` requirement, as previously the check
suggested users an ill-formed fix.
- Improved {doc}`misc-const-correctness
- <clang-tidy/checks/misc/const-correctness>` check by fixing false positives
- when the pointee is written through a pointer that is incremented,
- decremented or adjusted with `+=` or `-=`, such as `*p++ = 0`.
+ <clang-tidy/checks/misc/const-correctness>` check:
+
+ - Fixed false positives when the pointee is written through a pointer that
+ is incremented, decremented or adjusted with `+=` or `-=`, such as
+ `*p++ = 0`.
+
+ - Fixed false positives when the pointee is written through a pointer
+ assignment, such as `*(p = q) = 0`.
- Improved {doc}`misc-redundant-expression
<clang-tidy/checks/misc/redundant-expression>` by fixing false positives in
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 639ea0933c10b..9b6d8d320d4cf 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
@@ -136,6 +136,20 @@ void write_through_add_assign_subscript(int *p) {
(p_local0 += 1)[0] = 0;
}
+void write_through_plain_assign(int *p) {
+ int *p_local0 = p;
+ int *p_local1 = p;
+ // CHECK-NOT: warning
+ *(p_local0 = p_local1) = 0;
+}
+
+void write_through_plain_assign_subscript(int *p) {
+ int *p_local0 = p;
+ int *p_local1 = p;
+ // CHECK-NOT: warning
+ (p_local0 = p_local1)[0] = 0;
+}
+
void alias_through_increment(int *p) {
int *p_local0 = p;
// CHECK-NOT: warning
@@ -150,6 +164,14 @@ void alias_through_add_assign(int *p) {
*q = 0;
}
+void alias_through_plain_assign(int *p) {
+ int *p_local0 = p;
+ int *p_local1 = p;
+ // CHECK-NOT: warning
+ int *q = (p_local0 = p_local1);
+ *q = 0;
+}
+
template <class T>
void template_write_through_pointer_arithmetic() {
T a[] = {1, 2};
@@ -165,6 +187,19 @@ void instantiate_write_through_pointer_arithmetic() {
template_write_through_pointer_arithmetic<int>();
}
+template <class T>
+void template_write_through_plain_assign() {
+ T a[] = {1, 2};
+ T *p_local0 = &a[0];
+ T *p_local1 = &a[0];
+ // CHECK-NOT: warning
+ *(p_local0 = p_local1) = 0;
+}
+
+void instantiate_write_through_plain_assign() {
+ template_write_through_plain_assign<int>();
+}
+
void read_through_increment(int *p) {
int *p_local0 = p;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: pointee of variable 'p_local0' of
type 'int *' can be declared 'const'
@@ -179,6 +214,14 @@ void read_through_add_assign(int *p) {
int i = *(p_local0 += 1);
}
+void read_through_plain_assign(int *p) {
+ int *p_local0 = p;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: pointee of variable 'p_local0' of
type 'int *' can be declared 'const'
+ // CHECK-FIXES: int const*p_local0 = p;
+ int *p_local1 = p;
+ int i = *(p_local0 = p_local1);
+}
+
void function_pointer_basic() {
void (*const fp)() = nullptr;
fp();
diff --git a/clang/lib/Analysis/ExprMutationAnalyzer.cpp
b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
index 436308f558b66..d7a26ec33b4dc 100644
--- a/clang/lib/Analysis/ExprMutationAnalyzer.cpp
+++ b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
@@ -129,6 +129,8 @@ class ExprPointeeResolve {
return (resolveExpr(BO->getLHS()) || resolveExpr(BO->getRHS()));
if (BO->getOpcode() == BO_AddAssign || BO->getOpcode() == BO_SubAssign)
return resolveExpr(BO->getLHS());
+ if (BO->getOpcode() == BO_Assign)
+ return (resolveExpr(BO->getLHS()) || resolveExpr(BO->getRHS()));
if (BO->isCommaOp())
return resolveExpr(BO->getRHS());
return false;
diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
index 0f4245a323f84..743bce785fb7d 100644
--- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
+++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
@@ -2240,6 +2240,54 @@ TEST(ExprMutationAnalyzerTest,
}
}
+TEST(ExprMutationAnalyzerTest, PointeeMutatedByPointerAssignment) {
+ for (const std::string Deref : {"*(x = y)", "(x = y)[0]"}) {
+ const std::string Code = "void f() { int* x; int* y; " + Deref + " = 0; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto ResultsX =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(ResultsX, AST.get())) << Code;
+ auto ResultsY =
+ match(withEnclosingCompound(declRefTo("y")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(ResultsY, AST.get())) << Code;
+ }
+ for (const std::string Deref : {"*(x = y)", "(x = y)[0]"}) {
+ const std::string Code =
+ "void f() { int* x; int* y; int z = " + Deref + "; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get())) << Code;
+ }
+ {
+ // The assigned pointer escapes into a non-const pointer, which can be used
+ // to mutate the pointee.
+ const std::string Code = R"(
+ void f() {
+ int* x;
+ int* y;
+ int* z = (x = y);
+ })";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
+ {
+ // Assigning the pointer itself does not mutate the pointee of the LHS.
+ const std::string Code = R"(
+ void f() {
+ int* x;
+ int* y;
+ x = y;
+ })";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
+ }
+}
+
TEST(ExprMutationAnalyzerTest, PointeeMutatedByConditionOperator) {
const std::string Code = R"(
void f() {
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits