[clang] [clang-tools-extra] [clang-tidy] Fix FP in misc-const-correctness (PR #206515)
Baranov Victor via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 08:55:15 PDT 2026
https://github.com/vbvictor created https://github.com/llvm/llvm-project/pull/206515
`ExprPointeeResolve` now resolves through the `int*` to `void*` bitcast when the destination pointee is non-const
Fixes https://github.com/llvm/llvm-project/issues/206429.
>From 78c3758d0eeb10213cf261cd8cff1ce0edfc3b7e Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Mon, 29 Jun 2026 18:53:12 +0300
Subject: [PATCH] [clang-tidy] Fix FP in misc-const-correctness
---
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
.../const-correctness-pointer-as-pointers.cpp | 32 +++++++++
clang/lib/Analysis/ExprMutationAnalyzer.cpp | 7 ++
.../Analysis/ExprMutationAnalyzerTest.cpp | 68 +++++++++++++++++++
4 files changed, 110 insertions(+)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 2a980cc089a65..dc10bc59804ea 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -571,6 +571,9 @@ Changes in existing checks
- Fixed false positives when pointers were later passed or bound through
``const``-qualified pointer references.
+ - Fixed false positive where a pointer returned as a non-const ``void *``
+ was incorrectly diagnosed as allowing its pointee to be made ``const``.
+
- 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 41df6c44f5bef..f6bfb98c548c3 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
@@ -56,6 +56,38 @@ void *return_non_const() {
return a;
}
+void *return_as_void_pointer() {
+ int i = 0;
+ int *p_local0 = &i;
+ // CHECK-NOT: warning
+ return p_local0;
+}
+
+const void *return_as_const_void_pointer() {
+ int i = 0;
+ int *p_local0 = &i;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: pointee of variable 'p_local0' of type 'int *' can be declared 'const'
+ // CHECK-FIXES: int const*p_local0 = &i;
+ return p_local0;
+}
+
+void take_void_pointer(void *);
+void pass_as_void_pointer() {
+ int i = 0;
+ int *p_local0 = &i;
+ // CHECK-NOT: warning
+ take_void_pointer(p_local0);
+}
+
+void take_const_void_pointer(const void *);
+void pass_as_const_void_pointer() {
+ int i = 0;
+ int *p_local0 = &i;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: pointee of variable 'p_local0' of type 'int *' can be declared 'const'
+ // CHECK-FIXES: int const*p_local0 = &i;
+ take_const_void_pointer(p_local0);
+}
+
void function_pointer_basic() {
void (*const fp)() = nullptr;
fp();
diff --git a/clang/lib/Analysis/ExprMutationAnalyzer.cpp b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
index 3709fd0af3486..66d8eacdd54e6 100644
--- a/clang/lib/Analysis/ExprMutationAnalyzer.cpp
+++ b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
@@ -160,6 +160,13 @@ class ExprPointeeResolve {
if (CastType == SubExprType)
return resolveExpr(ICE->getSubExpr());
}
+ if (kind == CK_BitCast) {
+ const QualType DestType = ICE->getType();
+ const QualType SubExprType = ICE->getSubExpr()->getType();
+ if (DestType->isPointerType() && SubExprType->isPointerType() &&
+ !DestType->getPointeeType().isConstQualified())
+ return resolveExpr(ICE->getSubExpr());
+ }
return false;
}
diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
index 1545310431fd8..c19c8b0fe0b02 100644
--- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
+++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
@@ -1788,6 +1788,21 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByInitToNonConst) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code = "void f() { int* x = nullptr; void* 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 f() { int* x = nullptr; const void* b = x; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByAssignToNonConst) {
@@ -1806,6 +1821,21 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByAssignToNonConst) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code = "void f() { int* x = nullptr; void* b; 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 f() { int* x = nullptr; const void* b; b = x; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsArgument) {
@@ -1874,6 +1904,22 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsArgument) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code =
+ "void b(void*); 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(const void*); void f() { int* x = nullptr; b(x); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
+ }
{
const std::string Code =
"namespace std { typedef decltype(sizeof(int)) size_t; }"
@@ -2157,6 +2203,28 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByReturn) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code = R"(
+ void * f() {
+ int *const x = nullptr;
+ return x;
+ })";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
+ {
+ const std::string Code = R"(
+ const void * f() {
+ int *const x = nullptr;
+ return x;
+ })";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByPointerToMemberOperator) {
More information about the cfe-commits
mailing list