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

Reply via email to