[clang] [clang][StaticAnalyzer] Add enclosing Decl information to bug reports of RawPtrRef(LocalVars|Member)Checker (PR #214102)
Ziqing Luo via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 7 13:55:01 PDT 2026
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/214102
>From d18e4dc96f457d61186929aa77aacf6eff96cfc8 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Wed, 5 Aug 2026 11:20:02 -0700
Subject: [PATCH 1/7] [clang][StaticAnalyzer] Add support for variables and
fields in GetEnclosingDeclContextSignature
The `RawPtrRefLocalVarsChecker` and `RawPtrRefMemberChecker` forgot to
call `Report->setDeclWithIssue()` for some bug reports. Without the
call, the HTML reports miss the enclosing Decl and have hash collision
on distinct diagnostics.
The added
`clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp`
example is a reproducer of this kind of issue we observed in WebKit.
In addition, extend `GetEnclosingDeclContextSignature` with support of
ObjC Decls.
rdar://183700416
Assisted-by: Claude sonnet
---
clang/lib/Analysis/IssueHash.cpp | 5 +++
.../WebKit/RawPtrRefLocalVarsChecker.cpp | 1 +
.../WebKit/RawPtrRefMemberChecker.cpp | 2 ++
.../Checkers/WebKit/html-diag-dedup-ivars.mm | 33 +++++++++++++++++++
.../WebKit/html-diag-dedup-local-vars.cpp | 23 +++++++++++++
.../WebKit/html-diag-dedup-members.cpp | 31 +++++++++++++++++
.../WebKit/html-diag-dedup-parameters.cpp | 25 ++++++++++++++
7 files changed, 120 insertions(+)
create mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
create mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
create mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
create mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index e4b73d37a63f3..583286aa1d66e 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -94,6 +94,11 @@ static std::string GetEnclosingDeclContextSignature(const Decl *D) {
case Decl::Record:
case Decl::CXXRecord:
case Decl::Enum:
+ case Decl::ObjCInterface:
+ case Decl::ObjCImplementation:
+ case Decl::ObjCCategory:
+ case Decl::ObjCCategoryImpl:
+ case Decl::ObjCProtocol:
DeclName = ND->getQualifiedNameAsString();
break;
case Decl::CXXConstructor:
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index 09062df8166d3..b420ce73ae82e 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -428,6 +428,7 @@ class RawPtrRefLocalVarsChecker
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
if (Value)
Report->addRange(Value->getSourceRange());
+ Report->setDeclWithIssue(DeclWithIssue);
BR->emitReport(std::move(Report));
} else {
if (V->hasLocalStorage())
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
index 9bfc856f00dbc..7ddd2e21c47b6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
@@ -299,6 +299,8 @@ class RawPtrRefMemberChecker
BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Report->addRange(Member->getSourceRange());
+ if (auto *EnclosingCtx = Member->getDeclContext())
+ Report->setDeclWithIssue(Decl::castFromDeclContext(EnclosingCtx));
BR->emitReport(std::move(Report));
}
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
new file mode 100644
index 0000000000000..5cc7da29174fc
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
@@ -0,0 +1,33 @@
+// RUN: rm -fR %t
+// RUN: mkdir %t
+// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.NoUncountedMemberChecker \
+// RUN: -analyzer-output=html -o %t %s
+// RUN: ls %t | grep report | count 2
+
+// Two instance variables with identical spelling in different
+// @interfaces must not collide in the HTML issue hash: the enclosing
+// interface differs.
+
+#include "mock-types.h"
+
+__attribute__((objc_root_class))
+ at interface NSObject
++ (instancetype)alloc;
+- (instancetype)init;
+ at end
+
+ at interface FirstClass : NSObject {
+ RefCountable* _uncounted;
+}
+ at end
+
+ at implementation FirstClass
+ at end
+
+ at interface SecondClass : NSObject {
+ RefCountable* _uncounted;
+}
+ at end
+
+ at implementation SecondClass
+ at end
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
new file mode 100644
index 0000000000000..d2a385a79eb30
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
@@ -0,0 +1,23 @@
+// RUN: rm -fR %t
+// RUN: mkdir %t
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker \
+// RUN: -analyzer-output=html -o %t %s
+// RUN: ls %t | grep report | count 2
+
+// Two local variables with identical spelling in different functions
+// must not collide in the HTML issue hash: the enclosing function
+// differs.
+
+#include "mock-types.h"
+
+void someFunction();
+
+void foo() {
+ RefCountable *bar;
+ someFunction();
+}
+
+void baz() {
+ RefCountable *bar;
+ someFunction();
+}
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
new file mode 100644
index 0000000000000..39f4e5fd1b613
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
@@ -0,0 +1,31 @@
+// RUN: rm -fR %t
+// RUN: mkdir %t
+// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.NoUncountedMemberChecker \
+// RUN: -analyzer-output=html -o %t %s
+// RUN: ls %t | grep report | count 2
+
+// Two member variables with identical spelling in different classes
+// must not collide in the HTML issue hash: the enclosing class
+// differs.
+
+class Info {
+public:
+ void ref() const;
+ void deref() const;
+};
+
+class A {
+public:
+ A(Info& info) : m_info(info) { }
+
+private:
+ Info& m_info;
+};
+
+class B {
+public:
+ B(Info& info) : m_info(info) { }
+
+private:
+ Info& m_info;
+};
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp
new file mode 100644
index 0000000000000..960dd28f898ec
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp
@@ -0,0 +1,25 @@
+// RUN: rm -fR %t
+// RUN: mkdir %t
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker \
+// RUN: -analyzer-output=html -o %t %s
+// RUN: ls %t | grep report | count 2
+
+// Two parameters with identical spelling in different functions must
+// not collide in the HTML issue hash: the enclosing function differs.
+
+#include "mock-types.h"
+
+RefCountable *provide_ref_cntbl();
+void someFunction();
+
+void foo(RefCountable* a) {
+ a = provide_ref_cntbl();
+ someFunction();
+ a->method();
+}
+
+void baz(RefCountable* a) {
+ a = provide_ref_cntbl();
+ someFunction();
+ a->method();
+}
>From dcca915a3134758101d217f6540cc6b49b3fab06 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Wed, 5 Aug 2026 17:41:55 -0700
Subject: [PATCH 2/7] Rename the parameter of GetEnclosingDeclContextSignature
to EnclosingDecl
---
clang/lib/Analysis/IssueHash.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index 583286aa1d66e..855a02a90ba8d 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -82,11 +82,11 @@ static std::string GetSignature(const FunctionDecl *Target) {
return Signature;
}
-static std::string GetEnclosingDeclContextSignature(const Decl *D) {
- if (!D)
+static std::string GetEnclosingDeclContextSignature(const Decl *EnclosingDecl) {
+ if (!EnclosingDecl)
return "";
- if (const auto *ND = dyn_cast<NamedDecl>(D)) {
+ if (const auto *ND = dyn_cast<NamedDecl>(EnclosingDecl)) {
std::string DeclName;
switch (ND->getKind()) {
>From 2ff1d372b9363f0198bd26bd7175edb6c57e91e7 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 6 Aug 2026 13:37:39 -0700
Subject: [PATCH 3/7] add unit test
---
clang/lib/Analysis/IssueHash.cpp | 36 +--
clang/unittests/Analysis/CMakeLists.txt | 1 +
clang/unittests/Analysis/IssueHashTest.cpp | 315 +++++++++++++++++++++
3 files changed, 320 insertions(+), 32 deletions(-)
create mode 100644 clang/unittests/Analysis/IssueHashTest.cpp
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index 855a02a90ba8d..b58e2fe87b405 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -10,6 +10,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/StringRef.h"
@@ -87,39 +88,10 @@ static std::string GetEnclosingDeclContextSignature(const Decl *EnclosingDecl) {
return "";
if (const auto *ND = dyn_cast<NamedDecl>(EnclosingDecl)) {
- std::string DeclName;
-
- switch (ND->getKind()) {
- case Decl::Namespace:
- case Decl::Record:
- case Decl::CXXRecord:
- case Decl::Enum:
- case Decl::ObjCInterface:
- case Decl::ObjCImplementation:
- case Decl::ObjCCategory:
- case Decl::ObjCCategoryImpl:
- case Decl::ObjCProtocol:
- DeclName = ND->getQualifiedNameAsString();
- break;
- case Decl::CXXConstructor:
- case Decl::CXXDestructor:
- case Decl::CXXConversion:
- case Decl::CXXMethod:
- case Decl::Function:
- DeclName = GetSignature(dyn_cast_or_null<FunctionDecl>(ND));
- break;
- case Decl::ObjCMethod:
- // ObjC Methods can not be overloaded, qualified name uniquely identifies
- // the method.
- DeclName = ND->getQualifiedNameAsString();
- break;
- default:
- break;
- }
-
- return DeclName;
+ if (const auto *FD = dyn_cast<FunctionDecl>(EnclosingDecl))
+ return GetSignature(dyn_cast_or_null<FunctionDecl>(FD));
+ return ND->getQualifiedNameAsString();
}
-
return "";
}
diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt
index 33164d1f2f9f1..e302de5cb24e1 100644
--- a/clang/unittests/Analysis/CMakeLists.txt
+++ b/clang/unittests/Analysis/CMakeLists.txt
@@ -6,6 +6,7 @@ add_clang_unittest(ClangAnalysisTests
CloneDetectionTest.cpp
ExprMutationAnalyzerTest.cpp
IntervalPartitionTest.cpp
+ IssueHashTest.cpp
LifetimeSafetyTest.cpp
MacroExpansionContextTest.cpp
UnsafeBufferUsageTest.cpp
diff --git a/clang/unittests/Analysis/IssueHashTest.cpp b/clang/unittests/Analysis/IssueHashTest.cpp
new file mode 100644
index 0000000000000..204727a3ebcf3
--- /dev/null
+++ b/clang/unittests/Analysis/IssueHashTest.cpp
@@ -0,0 +1,315 @@
+//===- IssueHashTest.cpp - IssueHash unit tests --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/IssueHash.h"
+#include "clang/AST/Decl.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <string>
+
+namespace clang {
+namespace {
+
+using namespace ast_matchers;
+
+std::unique_ptr<ASTUnit> buildAST(llvm::StringRef Code,
+ std::vector<std::string> Args = {
+ "-fsyntax-only", "-std=c++20"}) {
+ return tooling::buildASTFromCodeWithArgs(Code, Args);
+}
+
+// getIssueString() joins several '$'-delimited fields together; the
+// enclosing-decl signature (computed by the internal, file-local
+// GetEnclosingDeclContextSignature()) is always the second field. Pull it
+// out in isolation so these tests don't have to hardcode the unrelated
+// column number and source-line fields.
+std::string getEnclosingDeclSignature(ASTContext &Ctx, const Decl *IssueDecl) {
+ FullSourceLoc Loc(Ctx.getSourceManager().getLocForStartOfFile(
+ Ctx.getSourceManager().getMainFileID()),
+ Ctx.getSourceManager());
+ std::string Full =
+ getIssueString(Loc, "checker", "message", IssueDecl, Ctx.getLangOpts());
+ size_t FirstDollar = Full.find('$');
+ size_t SecondDollar = Full.find('$', FirstDollar + 1);
+ return Full.substr(FirstDollar + 1, SecondDollar - FirstDollar - 1);
+}
+
+TEST(IssueHashTest, EnclosingVarDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ namespace ns {
+ int global_var;
+ }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *VD = selectFirst<VarDecl>(
+ "v", match(varDecl(hasName("global_var")).bind("v"), Ctx));
+ ASSERT_NE(VD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, VD), "ns::global_var");
+}
+
+TEST(IssueHashTest, EnclosingFieldDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ struct S {
+ int field;
+ };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *FD = selectFirst<FieldDecl>(
+ "f", match(fieldDecl(hasName("field")).bind("f"), Ctx));
+ ASSERT_NE(FD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, FD), "S::field");
+}
+
+TEST(IssueHashTest, EnclosingEnumConstantDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ enum class Color { Red };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *ECD = selectFirst<EnumConstantDecl>(
+ "e", match(enumConstantDecl(hasName("Red")).bind("e"), Ctx));
+ ASSERT_NE(ECD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, ECD), "Color::Red");
+}
+
+TEST(IssueHashTest, EnclosingFunctionDeclUsesSignatureNotQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ void foo(int);
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *Fn = selectFirst<FunctionDecl>(
+ "fn", match(functionDecl(hasName("foo")).bind("fn"), Ctx));
+ ASSERT_NE(Fn, nullptr);
+
+ // Functions (and methods/constructors/destructors) still get the full
+ // signature, not just the qualified name, so overloads don't collide.
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, Fn), "void foo(int)");
+}
+
+TEST(IssueHashTest, EnclosingCXXRecordDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ namespace ns {
+ struct Widget {};
+ }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *RD = selectFirst<CXXRecordDecl>(
+ "r", match(cxxRecordDecl(hasName("Widget")).bind("r"), Ctx));
+ ASSERT_NE(RD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, RD), "ns::Widget");
+}
+
+// The remaining tests below each cover one of the case labels that used to
+// be explicitly listed in GetEnclosingDeclContextSignature()'s switch,
+// before it was simplified to a single dyn_cast<FunctionDecl> check plus a
+// fallback to getQualifiedNameAsString() for everything else.
+
+TEST(IssueHashTest, EnclosingNamespaceDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ namespace outer {
+ namespace inner {}
+ }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *NS = selectFirst<NamespaceDecl>(
+ "n", match(namespaceDecl(hasName("inner")).bind("n"), Ctx));
+ ASSERT_NE(NS, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, NS), "outer::inner");
+}
+
+TEST(IssueHashTest, EnclosingRecordDeclUsesQualifiedName) {
+ // A plain (non-C++) 'struct' is a RecordDecl, not a CXXRecordDecl -- that
+ // distinction only exists when parsing as C++, where every struct/class
+ // is upgraded to a CXXRecordDecl. So this specifically needs C, not C++,
+ // to exercise the Decl::Record case label rather than Decl::CXXRecord.
+ auto AST = buildAST("struct S { int x; };",
+ {"-fsyntax-only", "-std=c17", "-x", "c"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *RD = selectFirst<RecordDecl>(
+ "r", match(recordDecl(hasName("S")).bind("r"), Ctx));
+ ASSERT_NE(RD, nullptr);
+ ASSERT_FALSE(isa<CXXRecordDecl>(RD));
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, RD), "S");
+}
+
+TEST(IssueHashTest, EnclosingEnumDeclUsesQualifiedName) {
+ auto AST = buildAST(R"cpp(
+ enum class Color { Red };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *ED = selectFirst<EnumDecl>(
+ "e", match(enumDecl(hasName("Color")).bind("e"), Ctx));
+ ASSERT_NE(ED, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, ED), "Color");
+}
+
+TEST(IssueHashTest, EnclosingObjCInterfaceDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ __attribute__((objc_root_class))
+ @interface Foo
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *ID = selectFirst<ObjCInterfaceDecl>(
+ "i", match(objcInterfaceDecl(hasName("Foo")).bind("i"), Ctx));
+ ASSERT_NE(ID, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, ID), "Foo");
+}
+
+TEST(IssueHashTest, EnclosingObjCImplementationDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ __attribute__((objc_root_class))
+ @interface Foo
+ @end
+ @implementation Foo
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *ImplD = selectFirst<ObjCImplementationDecl>(
+ "i", match(objcImplementationDecl(hasName("Foo")).bind("i"), Ctx));
+ ASSERT_NE(ImplD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, ImplD), "Foo");
+}
+
+TEST(IssueHashTest, EnclosingObjCCategoryDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ __attribute__((objc_root_class))
+ @interface Foo
+ @end
+ @interface Foo (Cat)
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *CatD = selectFirst<ObjCCategoryDecl>(
+ "c", match(objcCategoryDecl(hasName("Cat")).bind("c"), Ctx));
+ ASSERT_NE(CatD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, CatD), "Cat");
+}
+
+TEST(IssueHashTest, EnclosingObjCCategoryImplDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ __attribute__((objc_root_class))
+ @interface Foo
+ @end
+ @interface Foo (Cat)
+ @end
+ @implementation Foo (Cat)
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *CatImplD = selectFirst<ObjCCategoryImplDecl>(
+ "c", match(objcCategoryImplDecl(hasName("Cat")).bind("c"), Ctx));
+ ASSERT_NE(CatImplD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, CatImplD), "Cat");
+}
+
+TEST(IssueHashTest, EnclosingObjCProtocolDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ @protocol Proto
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *PD = selectFirst<ObjCProtocolDecl>(
+ "p", match(objcProtocolDecl(hasName("Proto")).bind("p"), Ctx));
+ ASSERT_NE(PD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, PD), "Proto");
+}
+
+TEST(IssueHashTest, EnclosingCXXConstructorDeclUsesSignature) {
+ auto AST = buildAST(R"cpp(
+ struct S { S(int); };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *Ctor = selectFirst<CXXConstructorDecl>(
+ "c", match(cxxConstructorDecl(ofClass(hasName("S"))).bind("c"), Ctx));
+ ASSERT_NE(Ctor, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, Ctor), "S::S(int)");
+}
+
+TEST(IssueHashTest, EnclosingCXXDestructorDeclUsesSignature) {
+ auto AST = buildAST(R"cpp(
+ struct S { ~S(); };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *Dtor = selectFirst<CXXDestructorDecl>(
+ "d", match(cxxDestructorDecl(ofClass(hasName("S"))).bind("d"), Ctx));
+ ASSERT_NE(Dtor, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, Dtor), "S::~S()");
+}
+
+TEST(IssueHashTest, EnclosingCXXConversionDeclUsesSignature) {
+ auto AST = buildAST(R"cpp(
+ struct S { operator int(); };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *Conv = selectFirst<CXXConversionDecl>(
+ "cv", match(cxxConversionDecl().bind("cv"), Ctx));
+ ASSERT_NE(Conv, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, Conv), "S::operator int()");
+}
+
+TEST(IssueHashTest, EnclosingCXXMethodDeclUsesSignature) {
+ auto AST = buildAST(R"cpp(
+ struct S { void method(int); };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *M = selectFirst<CXXMethodDecl>(
+ "m", match(cxxMethodDecl(hasName("method")).bind("m"), Ctx));
+ ASSERT_NE(M, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, M), "void S::method(int)");
+}
+
+TEST(IssueHashTest, EnclosingObjCMethodDeclUsesQualifiedName) {
+ auto AST = buildAST(R"objc(
+ @interface Foo
+ - (void)method;
+ @end
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ const auto *MD = selectFirst<ObjCMethodDecl>(
+ "m", match(objcMethodDecl(hasName("method")).bind("m"), Ctx));
+ ASSERT_NE(MD, nullptr);
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, MD), "Foo::method");
+}
+
+TEST(IssueHashTest, NullDeclProducesEmptySignature) {
+ auto AST = buildAST("");
+ ASTContext &Ctx = AST->getASTContext();
+
+ EXPECT_EQ(getEnclosingDeclSignature(Ctx, nullptr), "");
+}
+
+} // namespace
+} // namespace clang
>From 90edcec61e2975d8831200557137046d7000e0f8 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 6 Aug 2026 13:40:33 -0700
Subject: [PATCH 4/7] clean up
---
clang/lib/Analysis/IssueHash.cpp | 1 -
clang/unittests/Analysis/IssueHashTest.cpp | 12 ++++++------
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index b58e2fe87b405..8c96a1d2fe5d5 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -10,7 +10,6 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
-#include "clang/AST/DeclObjC.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/StringRef.h"
diff --git a/clang/unittests/Analysis/IssueHashTest.cpp b/clang/unittests/Analysis/IssueHashTest.cpp
index 204727a3ebcf3..d6e3c862ce01b 100644
--- a/clang/unittests/Analysis/IssueHashTest.cpp
+++ b/clang/unittests/Analysis/IssueHashTest.cpp
@@ -165,7 +165,7 @@ TEST(IssueHashTest, EnclosingObjCInterfaceDeclUsesQualifiedName) {
@interface Foo
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *ID = selectFirst<ObjCInterfaceDecl>(
"i", match(objcInterfaceDecl(hasName("Foo")).bind("i"), Ctx));
@@ -182,7 +182,7 @@ TEST(IssueHashTest, EnclosingObjCImplementationDeclUsesQualifiedName) {
@implementation Foo
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *ImplD = selectFirst<ObjCImplementationDecl>(
"i", match(objcImplementationDecl(hasName("Foo")).bind("i"), Ctx));
@@ -199,7 +199,7 @@ TEST(IssueHashTest, EnclosingObjCCategoryDeclUsesQualifiedName) {
@interface Foo (Cat)
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *CatD = selectFirst<ObjCCategoryDecl>(
"c", match(objcCategoryDecl(hasName("Cat")).bind("c"), Ctx));
@@ -218,7 +218,7 @@ TEST(IssueHashTest, EnclosingObjCCategoryImplDeclUsesQualifiedName) {
@implementation Foo (Cat)
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *CatImplD = selectFirst<ObjCCategoryImplDecl>(
"c", match(objcCategoryImplDecl(hasName("Cat")).bind("c"), Ctx));
@@ -232,7 +232,7 @@ TEST(IssueHashTest, EnclosingObjCProtocolDeclUsesQualifiedName) {
@protocol Proto
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *PD = selectFirst<ObjCProtocolDecl>(
"p", match(objcProtocolDecl(hasName("Proto")).bind("p"), Ctx));
@@ -295,7 +295,7 @@ TEST(IssueHashTest, EnclosingObjCMethodDeclUsesQualifiedName) {
- (void)method;
@end
)objc",
- {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++20"});
ASTContext &Ctx = AST->getASTContext();
const auto *MD = selectFirst<ObjCMethodDecl>(
"m", match(objcMethodDecl(hasName("method")).bind("m"), Ctx));
>From b356d338cba660b1b47851579547f2d4c84d4d11 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 6 Aug 2026 14:53:09 -0700
Subject: [PATCH 5/7] Update clang/lib/Analysis/IssueHash.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Balázs Benics <benicsbalazs at gmail.com>
---
clang/lib/Analysis/IssueHash.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index 8c96a1d2fe5d5..82e323d2ba526 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -88,7 +88,7 @@ static std::string GetEnclosingDeclContextSignature(const Decl *EnclosingDecl) {
if (const auto *ND = dyn_cast<NamedDecl>(EnclosingDecl)) {
if (const auto *FD = dyn_cast<FunctionDecl>(EnclosingDecl))
- return GetSignature(dyn_cast_or_null<FunctionDecl>(FD));
+ return GetSignature(FD);
return ND->getQualifiedNameAsString();
}
return "";
>From 9bbd4493c7b9a86350a6abc6d967098ffcecbb2e Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 6 Aug 2026 15:53:54 -0700
Subject: [PATCH 6/7] address comments
---
.../Checkers/WebKit/html-diag-dedup-ivars.mm | 33 ------------
.../WebKit/html-diag-dedup-local-vars.cpp | 23 --------
.../WebKit/html-diag-dedup-members.cpp | 31 -----------
...dup-parameters.cpp => html-diag-dedup.cpp} | 34 ++++++++++--
clang/unittests/Analysis/IssueHashTest.cpp | 54 +++++++++----------
5 files changed, 56 insertions(+), 119 deletions(-)
delete mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
delete mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
delete mode 100644 clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
rename clang/test/Analysis/Checkers/WebKit/{html-diag-dedup-parameters.cpp => html-diag-dedup.cpp} (53%)
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
deleted file mode 100644
index 5cc7da29174fc..0000000000000
--- a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-ivars.mm
+++ /dev/null
@@ -1,33 +0,0 @@
-// RUN: rm -fR %t
-// RUN: mkdir %t
-// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.NoUncountedMemberChecker \
-// RUN: -analyzer-output=html -o %t %s
-// RUN: ls %t | grep report | count 2
-
-// Two instance variables with identical spelling in different
-// @interfaces must not collide in the HTML issue hash: the enclosing
-// interface differs.
-
-#include "mock-types.h"
-
-__attribute__((objc_root_class))
- at interface NSObject
-+ (instancetype)alloc;
-- (instancetype)init;
- at end
-
- at interface FirstClass : NSObject {
- RefCountable* _uncounted;
-}
- at end
-
- at implementation FirstClass
- at end
-
- at interface SecondClass : NSObject {
- RefCountable* _uncounted;
-}
- at end
-
- at implementation SecondClass
- at end
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
deleted file mode 100644
index d2a385a79eb30..0000000000000
--- a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-local-vars.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: rm -fR %t
-// RUN: mkdir %t
-// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker \
-// RUN: -analyzer-output=html -o %t %s
-// RUN: ls %t | grep report | count 2
-
-// Two local variables with identical spelling in different functions
-// must not collide in the HTML issue hash: the enclosing function
-// differs.
-
-#include "mock-types.h"
-
-void someFunction();
-
-void foo() {
- RefCountable *bar;
- someFunction();
-}
-
-void baz() {
- RefCountable *bar;
- someFunction();
-}
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
deleted file mode 100644
index 39f4e5fd1b613..0000000000000
--- a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-members.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// RUN: rm -fR %t
-// RUN: mkdir %t
-// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.NoUncountedMemberChecker \
-// RUN: -analyzer-output=html -o %t %s
-// RUN: ls %t | grep report | count 2
-
-// Two member variables with identical spelling in different classes
-// must not collide in the HTML issue hash: the enclosing class
-// differs.
-
-class Info {
-public:
- void ref() const;
- void deref() const;
-};
-
-class A {
-public:
- A(Info& info) : m_info(info) { }
-
-private:
- Info& m_info;
-};
-
-class B {
-public:
- B(Info& info) : m_info(info) { }
-
-private:
- Info& m_info;
-};
diff --git a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup.cpp
similarity index 53%
rename from clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp
rename to clang/test/Analysis/Checkers/WebKit/html-diag-dedup.cpp
index 960dd28f898ec..b56b4875d2b34 100644
--- a/clang/test/Analysis/Checkers/WebKit/html-diag-dedup-parameters.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/html-diag-dedup.cpp
@@ -1,14 +1,14 @@
// RUN: rm -fR %t
// RUN: mkdir %t
-// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker \
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker,webkit.NoUncountedMemberChecker \
// RUN: -analyzer-output=html -o %t %s
-// RUN: ls %t | grep report | count 2
+// RUN: ls %t | grep report | count 4
+
+#include "mock-types.h"
// Two parameters with identical spelling in different functions must
// not collide in the HTML issue hash: the enclosing function differs.
-#include "mock-types.h"
-
RefCountable *provide_ref_cntbl();
void someFunction();
@@ -23,3 +23,29 @@ void baz(RefCountable* a) {
someFunction();
a->method();
}
+
+// Two member variables with identical spelling in different classes
+// must not collide in the HTML issue hash: the enclosing class
+// differs.
+
+class Info {
+public:
+ void ref() const;
+ void deref() const;
+};
+
+class A {
+public:
+ A(Info& info) : m_info(info) { }
+
+private:
+ Info& m_info;
+};
+
+class B {
+public:
+ B(Info& info) : m_info(info) { }
+
+private:
+ Info& m_info;
+};
diff --git a/clang/unittests/Analysis/IssueHashTest.cpp b/clang/unittests/Analysis/IssueHashTest.cpp
index d6e3c862ce01b..cd4d68ba5eb73 100644
--- a/clang/unittests/Analysis/IssueHashTest.cpp
+++ b/clang/unittests/Analysis/IssueHashTest.cpp
@@ -1,4 +1,4 @@
-//===- IssueHashTest.cpp - IssueHash unit tests --------------------------===//
+//===- IssueHashTest.cpp - IssueHash unit tests ---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -17,9 +17,9 @@
#include <memory>
#include <string>
-namespace clang {
namespace {
+using namespace clang;
using namespace ast_matchers;
std::unique_ptr<ASTUnit> buildAST(llvm::StringRef Code,
@@ -34,14 +34,13 @@ std::unique_ptr<ASTUnit> buildAST(llvm::StringRef Code,
// out in isolation so these tests don't have to hardcode the unrelated
// column number and source-line fields.
std::string getEnclosingDeclSignature(ASTContext &Ctx, const Decl *IssueDecl) {
- FullSourceLoc Loc(Ctx.getSourceManager().getLocForStartOfFile(
- Ctx.getSourceManager().getMainFileID()),
- Ctx.getSourceManager());
- std::string Full =
+ SourceManager &SM = Ctx.getSourceManager();
+ FullSourceLoc Loc(SM.getLocForStartOfFile(SM.getMainFileID()), SM);
+ std::string HashableStr =
getIssueString(Loc, "checker", "message", IssueDecl, Ctx.getLangOpts());
- size_t FirstDollar = Full.find('$');
- size_t SecondDollar = Full.find('$', FirstDollar + 1);
- return Full.substr(FirstDollar + 1, SecondDollar - FirstDollar - 1);
+ StringRef HashableStrRef = HashableStr;
+
+ return HashableStrRef.split('$').second.split('$').first.str();
}
TEST(IssueHashTest, EnclosingVarDeclUsesQualifiedName) {
@@ -53,7 +52,7 @@ TEST(IssueHashTest, EnclosingVarDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *VD = selectFirst<VarDecl>(
"v", match(varDecl(hasName("global_var")).bind("v"), Ctx));
- ASSERT_NE(VD, nullptr);
+ ASSERT_TRUE(VD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, VD), "ns::global_var");
}
@@ -67,7 +66,7 @@ TEST(IssueHashTest, EnclosingFieldDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *FD = selectFirst<FieldDecl>(
"f", match(fieldDecl(hasName("field")).bind("f"), Ctx));
- ASSERT_NE(FD, nullptr);
+ ASSERT_TRUE(FD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, FD), "S::field");
}
@@ -79,7 +78,7 @@ TEST(IssueHashTest, EnclosingEnumConstantDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *ECD = selectFirst<EnumConstantDecl>(
"e", match(enumConstantDecl(hasName("Red")).bind("e"), Ctx));
- ASSERT_NE(ECD, nullptr);
+ ASSERT_TRUE(ECD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, ECD), "Color::Red");
}
@@ -91,7 +90,7 @@ TEST(IssueHashTest, EnclosingFunctionDeclUsesSignatureNotQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *Fn = selectFirst<FunctionDecl>(
"fn", match(functionDecl(hasName("foo")).bind("fn"), Ctx));
- ASSERT_NE(Fn, nullptr);
+ ASSERT_TRUE(Fn != nullptr);
// Functions (and methods/constructors/destructors) still get the full
// signature, not just the qualified name, so overloads don't collide.
@@ -107,7 +106,7 @@ TEST(IssueHashTest, EnclosingCXXRecordDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *RD = selectFirst<CXXRecordDecl>(
"r", match(cxxRecordDecl(hasName("Widget")).bind("r"), Ctx));
- ASSERT_NE(RD, nullptr);
+ ASSERT_TRUE(RD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, RD), "ns::Widget");
}
@@ -126,7 +125,7 @@ TEST(IssueHashTest, EnclosingNamespaceDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *NS = selectFirst<NamespaceDecl>(
"n", match(namespaceDecl(hasName("inner")).bind("n"), Ctx));
- ASSERT_NE(NS, nullptr);
+ ASSERT_TRUE(NS != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, NS), "outer::inner");
}
@@ -141,7 +140,7 @@ TEST(IssueHashTest, EnclosingRecordDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *RD = selectFirst<RecordDecl>(
"r", match(recordDecl(hasName("S")).bind("r"), Ctx));
- ASSERT_NE(RD, nullptr);
+ ASSERT_TRUE(RD != nullptr);
ASSERT_FALSE(isa<CXXRecordDecl>(RD));
EXPECT_EQ(getEnclosingDeclSignature(Ctx, RD), "S");
@@ -154,7 +153,7 @@ TEST(IssueHashTest, EnclosingEnumDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *ED = selectFirst<EnumDecl>(
"e", match(enumDecl(hasName("Color")).bind("e"), Ctx));
- ASSERT_NE(ED, nullptr);
+ ASSERT_TRUE(ED != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, ED), "Color");
}
@@ -169,7 +168,7 @@ TEST(IssueHashTest, EnclosingObjCInterfaceDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *ID = selectFirst<ObjCInterfaceDecl>(
"i", match(objcInterfaceDecl(hasName("Foo")).bind("i"), Ctx));
- ASSERT_NE(ID, nullptr);
+ ASSERT_TRUE(ID != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, ID), "Foo");
}
@@ -186,7 +185,7 @@ TEST(IssueHashTest, EnclosingObjCImplementationDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *ImplD = selectFirst<ObjCImplementationDecl>(
"i", match(objcImplementationDecl(hasName("Foo")).bind("i"), Ctx));
- ASSERT_NE(ImplD, nullptr);
+ ASSERT_TRUE(ImplD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, ImplD), "Foo");
}
@@ -203,7 +202,7 @@ TEST(IssueHashTest, EnclosingObjCCategoryDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *CatD = selectFirst<ObjCCategoryDecl>(
"c", match(objcCategoryDecl(hasName("Cat")).bind("c"), Ctx));
- ASSERT_NE(CatD, nullptr);
+ ASSERT_TRUE(CatD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, CatD), "Cat");
}
@@ -222,7 +221,7 @@ TEST(IssueHashTest, EnclosingObjCCategoryImplDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *CatImplD = selectFirst<ObjCCategoryImplDecl>(
"c", match(objcCategoryImplDecl(hasName("Cat")).bind("c"), Ctx));
- ASSERT_NE(CatImplD, nullptr);
+ ASSERT_TRUE(CatImplD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, CatImplD), "Cat");
}
@@ -236,7 +235,7 @@ TEST(IssueHashTest, EnclosingObjCProtocolDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *PD = selectFirst<ObjCProtocolDecl>(
"p", match(objcProtocolDecl(hasName("Proto")).bind("p"), Ctx));
- ASSERT_NE(PD, nullptr);
+ ASSERT_TRUE(PD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, PD), "Proto");
}
@@ -248,7 +247,7 @@ TEST(IssueHashTest, EnclosingCXXConstructorDeclUsesSignature) {
ASTContext &Ctx = AST->getASTContext();
const auto *Ctor = selectFirst<CXXConstructorDecl>(
"c", match(cxxConstructorDecl(ofClass(hasName("S"))).bind("c"), Ctx));
- ASSERT_NE(Ctor, nullptr);
+ ASSERT_TRUE(Ctor != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, Ctor), "S::S(int)");
}
@@ -260,7 +259,7 @@ TEST(IssueHashTest, EnclosingCXXDestructorDeclUsesSignature) {
ASTContext &Ctx = AST->getASTContext();
const auto *Dtor = selectFirst<CXXDestructorDecl>(
"d", match(cxxDestructorDecl(ofClass(hasName("S"))).bind("d"), Ctx));
- ASSERT_NE(Dtor, nullptr);
+ ASSERT_TRUE(Dtor != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, Dtor), "S::~S()");
}
@@ -272,7 +271,7 @@ TEST(IssueHashTest, EnclosingCXXConversionDeclUsesSignature) {
ASTContext &Ctx = AST->getASTContext();
const auto *Conv = selectFirst<CXXConversionDecl>(
"cv", match(cxxConversionDecl().bind("cv"), Ctx));
- ASSERT_NE(Conv, nullptr);
+ ASSERT_TRUE(Conv != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, Conv), "S::operator int()");
}
@@ -284,7 +283,7 @@ TEST(IssueHashTest, EnclosingCXXMethodDeclUsesSignature) {
ASTContext &Ctx = AST->getASTContext();
const auto *M = selectFirst<CXXMethodDecl>(
"m", match(cxxMethodDecl(hasName("method")).bind("m"), Ctx));
- ASSERT_NE(M, nullptr);
+ ASSERT_TRUE(M != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, M), "void S::method(int)");
}
@@ -299,7 +298,7 @@ TEST(IssueHashTest, EnclosingObjCMethodDeclUsesQualifiedName) {
ASTContext &Ctx = AST->getASTContext();
const auto *MD = selectFirst<ObjCMethodDecl>(
"m", match(objcMethodDecl(hasName("method")).bind("m"), Ctx));
- ASSERT_NE(MD, nullptr);
+ ASSERT_TRUE(MD != nullptr);
EXPECT_EQ(getEnclosingDeclSignature(Ctx, MD), "Foo::method");
}
@@ -312,4 +311,3 @@ TEST(IssueHashTest, NullDeclProducesEmptySignature) {
}
} // namespace
-} // namespace clang
>From ebe102f297ef4177430529ad3384909fd51bc754 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 7 Aug 2026 13:23:37 -0700
Subject: [PATCH 7/7] address comments
---
clang/lib/Analysis/IssueHash.cpp | 7 ++++---
clang/unittests/Analysis/IssueHashTest.cpp | 2 +-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Analysis/IssueHash.cpp b/clang/lib/Analysis/IssueHash.cpp
index 82e323d2ba526..236466001dc96 100644
--- a/clang/lib/Analysis/IssueHash.cpp
+++ b/clang/lib/Analysis/IssueHash.cpp
@@ -86,12 +86,13 @@ static std::string GetEnclosingDeclContextSignature(const Decl *EnclosingDecl) {
if (!EnclosingDecl)
return "";
- if (const auto *ND = dyn_cast<NamedDecl>(EnclosingDecl)) {
- if (const auto *FD = dyn_cast<FunctionDecl>(EnclosingDecl))
+ if (const auto *ND = dyn_cast_or_null<NamedDecl>(EnclosingDecl)) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ // To distinguish overloads we need to use the signature.
return GetSignature(FD);
+ }
return ND->getQualifiedNameAsString();
}
- return "";
}
static StringRef GetNthLineOfFile(std::optional<llvm::MemoryBufferRef> Buffer,
diff --git a/clang/unittests/Analysis/IssueHashTest.cpp b/clang/unittests/Analysis/IssueHashTest.cpp
index cd4d68ba5eb73..8cf8fe9047f41 100644
--- a/clang/unittests/Analysis/IssueHashTest.cpp
+++ b/clang/unittests/Analysis/IssueHashTest.cpp
@@ -19,7 +19,7 @@
namespace {
-using namespace clang;
+using namespace clang;
using namespace ast_matchers;
std::unique_ptr<ASTUnit> buildAST(llvm::StringRef Code,
More information about the cfe-commits
mailing list