[clang] [alpha.webkit.NoDeleteChecker] Support nodelete on constructors and destructors (PR #218822)

Ryosuke Niwa via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 3 00:45:04 PDT 2026


https://github.com/rniwa updated https://github.com/llvm/llvm-project/pull/218822

>From a229bf42500e77be7e3e7f02493495c79754ccc9 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Tue, 25 Aug 2026 18:28:00 -0700
Subject: [PATCH 1/5] [alpha.webkit.NoDeleteChecker] Support nodelete on
 constructors and destructors

This PR adds the support for specifying nodelete annotation on C++ constructors and destructors.
To do this, we recognize [[clang::annotate("webkit.nodelete")]] on function declarations instead
of [[clang::annotate_type("webkit.nodelete")]] on the return value.
---
 .../Checkers/WebKit/NoDeleteChecker.cpp       |  46 ++++++-
 .../Checkers/WebKit/PtrTypesSemantics.cpp     | 125 +++++++++++++++---
 .../Checkers/WebKit/PtrTypesSemantics.h       |  10 ++
 .../Checkers/WebKit/nodelete-annotation.cpp   |  47 +++++++
 4 files changed, 204 insertions(+), 24 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/NoDeleteChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/NoDeleteChecker.cpp
index b3b43361a25efc..cf4518bb962d78 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/NoDeleteChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/NoDeleteChecker.cpp
@@ -84,16 +84,50 @@ class NoDeleteChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
         break;
       }
     }
+
+    const FieldDecl *Field = nullptr;
     const Stmt *OffendingStmt = nullptr;
-    if (!ParamDecl && TFA.isTrivial(Body, &OffendingStmt))
+    bool IsCtor = false;
+    bool IsDtor = false;
+    if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+      IsCtor = true;
+      Field = TFA.fieldWithNonTrivialCtor(Ctor->getParent());
+      if (!Field) {
+        for (auto *CtorInit : Ctor->inits()) {
+          if (!TFA.isTrivial(CtorInit->getInit(), &OffendingStmt)) {
+            if (!OffendingStmt)
+              OffendingStmt = CtorInit->getInit();
+            break;
+          }
+        }
+      }
+    } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+      IsDtor = true;
+      Field = TFA.fieldWithNonTrivialDtor(Dtor->getParent());
+    }
+
+    if (!ParamDecl && !Field && !OffendingStmt &&
+        TFA.isTrivial(Body, &OffendingStmt))
       return;
 
     SmallString<100> Buf;
     llvm::raw_svector_ostream Os(Buf);
 
-    Os << "A function ";
+    if (IsCtor)
+      Os << "A constructor ";
+    else if (IsDtor)
+      Os << "A destructor ";
+    else
+      Os << "A function ";
     printQuotedName(Os, FD);
-    Os << " has [[clang::annotate_type(\"webkit.nodelete\")]] but it contains ";
+    // FIXME: Update this to say clang::annotate("webkit.nodelete").
+    Os << " has [[clang::annotate_type(\"webkit.nodelete\")]] but it ";
+    if (IsCtor && Field)
+      Os << "constructs ";
+    else if (IsDtor && Field)
+      Os << "destructs ";
+    else
+      Os << "contains ";
     SourceLocation SrcLocToReport;
     SourceRange Range;
     if (ParamDecl) {
@@ -102,6 +136,12 @@ class NoDeleteChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
       Os << " which could destruct an object.";
       SrcLocToReport = FD->getBeginLoc();
       Range = ParamDecl->getSourceRange();
+    } else if (Field) {
+      Os << "a member variable ";
+      printQuotedName(Os, Field);
+      Os << " that could destruct an object.";
+      SrcLocToReport = FD->getBeginLoc();
+      Range = Field->getSourceRange();
     } else {
       Os << "code that could destruct an object.";
       SrcLocToReport = OffendingStmt->getBeginLoc();
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index e8e404c753dcbc..7557449def246c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -445,7 +445,26 @@ enum class WebKitAnnotation : uint8_t {
   NoDelete,
 };
 
-static WebKitAnnotation typeAnnotationForReturnType(const FunctionDecl *FD) {
+static WebKitAnnotation annotationType(const StringRef &Annotation) {
+  if (Annotation == "webkit.pointerconversion")
+    return WebKitAnnotation::PointerConversion;
+  if (Annotation == "webkit.nodelete")
+    return WebKitAnnotation::NoDelete;
+  return WebKitAnnotation::None;
+}
+
+static WebKitAnnotation annotationForFunction(const FunctionDecl *FD) {
+  if (isa<CXXRecordDecl>(FD->getParent())) { // FIXME: Add support for annotate
+                                             // on non-C++ functions.
+    for (auto *Attr : FD->attrs()) {
+      auto *AnnoAttr = dyn_cast_or_null<AnnotateAttr>(Attr);
+      if (!AnnoAttr)
+        continue;
+      auto Annotation = annotationType(AnnoAttr->getAnnotation());
+      if (Annotation != WebKitAnnotation::None)
+        return Annotation;
+    }
+  }
   auto RetType = FD->getReturnType();
   auto *Type = RetType.getTypePtrOrNull();
   if (auto *MacroQualified = dyn_cast_or_null<MacroQualifiedType>(Type))
@@ -456,12 +475,7 @@ static WebKitAnnotation typeAnnotationForReturnType(const FunctionDecl *FD) {
   auto *AnnotateType = dyn_cast_or_null<AnnotateTypeAttr>(Attr->getAttr());
   if (!AnnotateType)
     return WebKitAnnotation::None;
-  auto Annotation = AnnotateType->getAnnotation();
-  if (Annotation == "webkit.pointerconversion")
-    return WebKitAnnotation::PointerConversion;
-  if (Annotation == "webkit.nodelete")
-    return WebKitAnnotation::NoDelete;
-  return WebKitAnnotation::None;
+  return annotationType(AnnotateType->getAnnotation());
 }
 
 bool isPtrConversion(const FunctionDecl *F) {
@@ -481,14 +495,14 @@ bool isPtrConversion(const FunctionDecl *F) {
       FunctionName == "checked_objc_cast")
     return true;
 
-  if (typeAnnotationForReturnType(F) == WebKitAnnotation::PointerConversion)
+  if (annotationForFunction(F) == WebKitAnnotation::PointerConversion)
     return true;
 
   return false;
 }
 
 static bool isNoDeleteFunctionDecl(const FunctionDecl *F) {
-  return typeAnnotationForReturnType(F) == WebKitAnnotation::NoDelete;
+  return annotationForFunction(F) == WebKitAnnotation::NoDelete;
 }
 
 bool isNoDeleteFunction(const FunctionDecl *F) {
@@ -574,14 +588,22 @@ class TrivialFunctionAnalysisVisitor
     return Result;
   }
 
+  static bool isTrivialType(QualType Ty) {
+    // T*, T&, or T&& does not delete.
+    if (Ty->isPointerOrReferenceType())
+      return true;
+
+    // Fundamental types (integral, nullptr, etc...) does not delete.
+    if (Ty->isFundamentalType() || Ty->isIntegralOrEnumerationType())
+      return true;
+
+    return false;
+  }
+
   bool CanTriviallyDestruct(QualType Ty) {
     if (Ty.isNull())
       return false;
 
-    // T*, T& or T&& does not run its destructor.
-    if (Ty->isPointerOrReferenceType())
-      return true;
-
     // FIXME: Handle a case when there is a local autorelease pool.
     if (Ty->isObjCObjectPointerType()) {
       auto Type = Ty.isDestructedType();
@@ -590,8 +612,7 @@ class TrivialFunctionAnalysisVisitor
       // strong lifetime in ARC could dealloc an object.
     }
 
-    // Fundamental types (integral, nullptr_t, etc...) don't have destructors.
-    if (Ty->isFundamentalType() || Ty->isIntegralOrEnumerationType())
+    if (isTrivialType(Ty))
       return true;
 
     if (const auto *R = Ty->getAsCXXRecordDecl()) {
@@ -599,7 +620,12 @@ class TrivialFunctionAnalysisVisitor
       if (R->hasDefinition() && R->hasTrivialDestructor())
         return true;
 
-      if (HasFieldWithNonTrivialDtor(R))
+      if (auto *Dtor = R->getDestructor()) {
+        if (isNoDeleteFunction(Dtor))
+          return true;
+      }
+
+      if (FieldWithNonTrivialDtor(R))
         return false;
 
       // For Webkit, side-effects are fine as long as we don't delete objects,
@@ -620,16 +646,42 @@ class TrivialFunctionAnalysisVisitor
     return false; // Otherwise it's likely not trivial.
   }
 
-  bool HasFieldWithNonTrivialDtor(const CXXRecordDecl *Cls) {
-    auto CacheIt = FieldDtorCache.find(Cls);
-    if (CacheIt != FieldDtorCache.end())
+  bool CanTriviallyConstruct(QualType Ty) {
+    if (Ty.isNull())
+      return false;
+
+    if (isTrivialType(Ty))
+      return true;
+
+    if (const auto *R = Ty->getAsCXXRecordDecl()) {
+      // C++ trivially destructible classes are fine.
+      if (R->hasDefinition() && R->hasTrivialDefaultConstructor())
+        return true;
+      for (auto *Ctor : R->ctors()) {
+        if (Ctor->isDefaultConstructor() && IsFunctionTrivial(Ctor))
+          return true;
+      }
+    }
+
+    return false;
+  }
+
+  template <typename CacheTy, typename IsTrivialTypeFn>
+  bool hasNonTrivialField(const CXXRecordDecl *Cls,
+                          const FieldDecl **OffendingField, CacheTy &Cache,
+                          IsTrivialTypeFn IsTrivialType) {
+    auto CacheIt = Cache.find(Cls);
+    if (CacheIt != Cache.end() && !OffendingField)
       return CacheIt->second;
 
     bool Result = ([&] {
       auto HasNonTrivialField = [&](const CXXRecordDecl *R) {
         for (const FieldDecl *F : R->fields()) {
-          if (!CanTriviallyDestruct(F->getType()))
+          if (!IsTrivialType(F->getType())) {
+            if (OffendingField)
+              *OffendingField = F;
             return true;
+          }
         }
         return false;
       };
@@ -653,7 +705,7 @@ class TrivialFunctionAnalysisVisitor
           Paths, /*LookupInDependent =*/true);
     })();
 
-    FieldDtorCache[Cls] = Result;
+    Cache[Cls] = Result;
 
     return Result;
   }
@@ -722,6 +774,22 @@ class TrivialFunctionAnalysisVisitor
         VD, [&] { return CanTriviallyDestruct(VD->getType()); });
   }
 
+  const FieldDecl *FieldWithNonTrivialCtor(const CXXRecordDecl *Cls) {
+    const FieldDecl *OffendingField = nullptr;
+    hasNonTrivialField(
+        Cls, &OffendingField, FieldCtorCache,
+        [&](const QualType Ty) { return CanTriviallyConstruct(Ty); });
+    return OffendingField;
+  }
+
+  const FieldDecl *FieldWithNonTrivialDtor(const CXXRecordDecl *Cls) {
+    const FieldDecl *OffendingField = nullptr;
+    hasNonTrivialField(
+        Cls, &OffendingField, FieldDtorCache,
+        [&](const QualType Ty) { return CanTriviallyDestruct(Ty); });
+    return OffendingField;
+  }
+
   bool IsStatementTrivial(const Stmt *S) {
     auto CacheIt = Cache.find(S);
     if (CacheIt != Cache.end())
@@ -1084,6 +1152,7 @@ class TrivialFunctionAnalysisVisitor
 
 private:
   CacheTy &Cache;
+  CacheTy FieldCtorCache;
   CacheTy FieldDtorCache;
   CacheTy RecursiveFn;
   const Stmt **OffendingStmt;
@@ -1109,4 +1178,18 @@ bool TrivialFunctionAnalysis::hasTrivialDtorImpl(const VarDecl *VD,
   return V.HasTrivialDestructor(VD);
 }
 
+const FieldDecl *
+TrivialFunctionAnalysis::fieldWithNonTrivialCtorImpl(const CXXRecordDecl *RD,
+                                                     CacheTy &Cache) {
+  TrivialFunctionAnalysisVisitor V(Cache);
+  return V.FieldWithNonTrivialCtor(RD);
+}
+
+const FieldDecl *
+TrivialFunctionAnalysis::fieldWithNonTrivialDtorImpl(const CXXRecordDecl *RD,
+                                                     CacheTy &Cache) {
+  TrivialFunctionAnalysisVisitor V(Cache);
+  return V.FieldWithNonTrivialDtor(RD);
+}
+
 } // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
index 4e548c44c6bb9d..13f102e22661c4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -183,6 +183,12 @@ class TrivialFunctionAnalysis {
   bool hasTrivialDtor(const VarDecl *VD) const {
     return hasTrivialDtorImpl(VD, TheCache);
   }
+  const FieldDecl *fieldWithNonTrivialCtor(const CXXRecordDecl *RD) const {
+    return fieldWithNonTrivialCtorImpl(RD, TheCache);
+  }
+  const FieldDecl *fieldWithNonTrivialDtor(const CXXRecordDecl *RD) const {
+    return fieldWithNonTrivialDtorImpl(RD, TheCache);
+  }
 
 private:
   friend class TrivialFunctionAnalysisVisitor;
@@ -194,6 +200,10 @@ class TrivialFunctionAnalysis {
   static bool isTrivialImpl(const Decl *D, CacheTy &Cache, const Stmt **);
   static bool isTrivialImpl(const Stmt *S, CacheTy &Cache, const Stmt **);
   static bool hasTrivialDtorImpl(const VarDecl *VD, CacheTy &Cache);
+  static const FieldDecl *fieldWithNonTrivialCtorImpl(const CXXRecordDecl *RD,
+                                                      CacheTy &Cache);
+  static const FieldDecl *fieldWithNonTrivialDtorImpl(const CXXRecordDecl *RD,
+                                                      CacheTy &Cache);
 };
 
 } // namespace clang
diff --git a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
index 06ba7c47ae91af..e0161bb3f18c3d 100644
--- a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
@@ -759,3 +759,50 @@ void [[clang::annotate_type("webkit.nodelete")]] valueInitNew() {
 }
 
 } // namespace trivial_implicit_ctor_in_new_expr
+
+namespace nodelete_ctor_dtor {
+
+struct OpaqueObject {
+  OpaqueObject();
+  ~OpaqueObject();
+};
+
+struct RefPtrContainer {
+  [[clang::annotate("webkit.nodelete")]] RefPtrContainer() { }
+  // expected-warning at -1{{A constructor 'RefPtrContainer' has [[clang::annotate_type("webkit.nodelete")]] but it constructs a member variable 'opaqueObject' that could destruct an object}}
+  [[clang::annotate("webkit.nodelete")]] ~RefPtrContainer() { }
+  // expected-warning at -1{{A destructor '~RefPtrContainer' has [[clang::annotate_type("webkit.nodelete")]] but it destructs a member variable 'countable' that could destruct an object}}
+  RefPtr<RefCountable> countable;
+  OpaqueObject opaqueObject;
+};
+
+struct RefPtrContainerWithSuppressedDestructor {
+  [[clang::suppress]] [[clang::annotate("webkit.nodelete")]] ~RefPtrContainerWithSuppressedDestructor() { }
+  RefPtr<RefCountable> countable;
+};
+
+void [[clang::annotate_type("webkit.nodelete")]] foo(const RefPtrContainer& src) {
+  RefPtrContainer container(src);
+}
+
+struct ObjectWithOpaqueCopyConstructor {
+  ObjectWithOpaqueCopyConstructor(const ObjectWithOpaqueCopyConstructor&);
+  ObjectWithOpaqueCopyConstructor() { }
+};
+
+struct CallDefaultConstructor {
+  [[clang::annotate("webkit.nodelete")]] CallDefaultConstructor() { }
+  ObjectWithOpaqueCopyConstructor objectWithOpaqueCopyConstructor;
+};
+
+struct CallCopyConstructor {
+  using InnerObjectType = ObjectWithOpaqueCopyConstructor;
+  [[clang::annotate("webkit.nodelete")]] CallCopyConstructor(const InnerObjectType& obj)
+    : object(obj)
+    // expected-warning at -1{{A constructor 'CallCopyConstructor' has [[clang::annotate_type("webkit.nodelete")]] but it contains code that could destruct an object}}
+  {
+  }
+  InnerObjectType object;
+};
+
+} // namespace nodelete_ctor_dtor

>From 689a5a2cab0374006c4552d3c8b19ba33ffc025c Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <ryosuke.niwa at gmail.com>
Date: Thu, 1 Oct 2026 16:07:02 -0700
Subject: [PATCH 2/5] Update
 clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.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/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index 7557449def246c..ba375691986e93 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -445,7 +445,7 @@ enum class WebKitAnnotation : uint8_t {
   NoDelete,
 };
 
-static WebKitAnnotation annotationType(const StringRef &Annotation) {
+static WebKitAnnotation annotationType(StringRef Annotation) {
   if (Annotation == "webkit.pointerconversion")
     return WebKitAnnotation::PointerConversion;
   if (Annotation == "webkit.nodelete")

>From 27b90399c97e97b4d80617fb156ae2a508f0aa00 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <ryosuke.niwa at gmail.com>
Date: Thu, 1 Oct 2026 16:07:24 -0700
Subject: [PATCH 3/5] Update
 clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.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>
---
 .../lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index ba375691986e93..a1abe638bd789d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -454,8 +454,8 @@ static WebKitAnnotation annotationType(StringRef Annotation) {
 }
 
 static WebKitAnnotation annotationForFunction(const FunctionDecl *FD) {
-  if (isa<CXXRecordDecl>(FD->getParent())) { // FIXME: Add support for annotate
-                                             // on non-C++ functions.
+  // FIXME: Add support for annotate on non-C++ functions.
+  if (isa<CXXMethodDecl>(FD)) {
     for (auto *Attr : FD->attrs()) {
       auto *AnnoAttr = dyn_cast_or_null<AnnotateAttr>(Attr);
       if (!AnnoAttr)

>From 5369ae4b2c40b0ae542f95104f56060169b8ab78 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Thu, 1 Oct 2026 16:39:29 -0700
Subject: [PATCH 4/5] Make the annotation work with a regular C function and
 make it possible to specify multiple annotations per function

---
 .../Checkers/WebKit/PtrTypesSemantics.cpp     | 26 +++++++------------
 .../Checkers/WebKit/nodelete-annotation.cpp   |  8 ++++++
 2 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index a1abe638bd789d..85c3086a8f6464 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -453,17 +453,11 @@ static WebKitAnnotation annotationType(StringRef Annotation) {
   return WebKitAnnotation::None;
 }
 
-static WebKitAnnotation annotationForFunction(const FunctionDecl *FD) {
-  // FIXME: Add support for annotate on non-C++ functions.
-  if (isa<CXXMethodDecl>(FD)) {
-    for (auto *Attr : FD->attrs()) {
-      auto *AnnoAttr = dyn_cast_or_null<AnnotateAttr>(Attr);
-      if (!AnnoAttr)
-        continue;
-      auto Annotation = annotationType(AnnoAttr->getAnnotation());
-      if (Annotation != WebKitAnnotation::None)
-        return Annotation;
-    }
+static bool hasAnnotationForFunction(const FunctionDecl *FD,
+                                     WebKitAnnotation TargetAnnotation) {
+  for (auto *Attr : FD->specific_attrs<AnnotateAttr>()) {
+    if (annotationType(Attr->getAnnotation()) == TargetAnnotation)
+      return true;
   }
   auto RetType = FD->getReturnType();
   auto *Type = RetType.getTypePtrOrNull();
@@ -471,11 +465,11 @@ static WebKitAnnotation annotationForFunction(const FunctionDecl *FD) {
     Type = MacroQualified->desugar().getTypePtrOrNull();
   auto *Attr = dyn_cast_or_null<AttributedType>(Type);
   if (!Attr)
-    return WebKitAnnotation::None;
+    return false;
   auto *AnnotateType = dyn_cast_or_null<AnnotateTypeAttr>(Attr->getAttr());
   if (!AnnotateType)
-    return WebKitAnnotation::None;
-  return annotationType(AnnotateType->getAnnotation());
+    return false;
+  return annotationType(AnnotateType->getAnnotation()) == TargetAnnotation;
 }
 
 bool isPtrConversion(const FunctionDecl *F) {
@@ -495,14 +489,14 @@ bool isPtrConversion(const FunctionDecl *F) {
       FunctionName == "checked_objc_cast")
     return true;
 
-  if (annotationForFunction(F) == WebKitAnnotation::PointerConversion)
+  if (hasAnnotationForFunction(F, WebKitAnnotation::PointerConversion))
     return true;
 
   return false;
 }
 
 static bool isNoDeleteFunctionDecl(const FunctionDecl *F) {
-  return annotationForFunction(F) == WebKitAnnotation::NoDelete;
+  return hasAnnotationForFunction(F, WebKitAnnotation::NoDelete);
 }
 
 bool isNoDeleteFunction(const FunctionDecl *F) {
diff --git a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
index e0161bb3f18c3d..89f8b7f420fc31 100644
--- a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
@@ -806,3 +806,11 @@ struct CallCopyConstructor {
 };
 
 } // namespace nodelete_ctor_dtor
+
+namespace nodelete_ptrconversion {
+
+  [[clang::annotate("webkit.ptrconversion")]] [[clang::annotate("webkit.nodelete")]] void foo(void* ptr) {
+    someFunction(); // expected-warning{{A function 'foo' has [[clang::annotate_type("webkit.nodelete")]] but it contains code that could destruct an object}}
+  }
+
+} // namespace nodelete_ptrconversion

>From 333b4fc87ccb5a5820ce74fde4874a4477d3b847 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Sat, 3 Oct 2026 00:44:41 -0700
Subject: [PATCH 5/5] Fix builds

---
 clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
index 13f102e22661c4..9a601c806c1c40 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -21,6 +21,7 @@ class CXXBaseSpecifier;
 class CXXMethodDecl;
 class CXXRecordDecl;
 class Decl;
+class FieldDecl;
 class FunctionDecl;
 class NamedDecl;
 class QualType;



More information about the cfe-commits mailing list