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 <[email protected]> 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)) +@interface NSObject ++ (instancetype)alloc; +- (instancetype)init; +@end + +@interface FirstClass : NSObject { + RefCountable* _uncounted; +} +@end + +@implementation FirstClass +@end + +@interface SecondClass : NSObject { + RefCountable* _uncounted; +} +@end + +@implementation SecondClass +@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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> --- 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 <[email protected]> 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)) -@interface NSObject -+ (instancetype)alloc; -- (instancetype)init; -@end - -@interface FirstClass : NSObject { - RefCountable* _uncounted; -} -@end - -@implementation FirstClass -@end - -@interface SecondClass : NSObject { - RefCountable* _uncounted; -} -@end - -@implementation SecondClass -@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 <[email protected]> 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, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
