Author: Ryosuke Niwa
Date: 2026-10-23T08:08:37-07:00
New Revision: 1998e4ffdd1da0ccc3b8b29ba414c4d068af0fb8

URL: 
https://github.com/llvm/llvm-project/commit/1998e4ffdd1da0ccc3b8b29ba414c4d068af0fb8
DIFF: 
https://github.com/llvm/llvm-project/commit/1998e4ffdd1da0ccc3b8b29ba414c4d068af0fb8.diff

LOG: [WebKit checkers] Treat NeverDestroyed<T> as immortal (#234867)

Add the support for NeverDestroyed<T> in WebKit, which is an immortal
object. Such an object with non-local storage (e.g. global or static) is
safe as a local variable or call argument.

Co-authored with Claude.

Added: 
    clang/test/Analysis/Checkers/WebKit/never-destroyed.cpp

Modified: 
    clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index 21f4275acc4ac..d737696c0b06b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -170,6 +170,10 @@ static bool tryToFindPtrOriginImpl(
           return callback(E, /*IsSafe=*/true,
                           OriginDependsOnFullExpressionTemporary,
                           PtrIsLifetimeBoundToOrigin);
+        if (isImmortalVar(VD))
+          return callback(E, /*IsSafe=*/true,
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
 
         if (FollowLifetimeBound && VD->isImplicit() && VD->isLocalVarDecl()) {
           if (auto *Init = VD->getInit()) {
@@ -298,6 +302,10 @@ static bool tryToFindPtrOriginImpl(
             E = memberCall->getImplicitObjectArgument();
             continue;
           }
+          if (isAccessorOfNeverDestroyed(decl)) {
+            E = memberCall->getImplicitObjectArgument();
+            continue;
+          }
         }
       }
 
@@ -313,6 +321,12 @@ static bool tryToFindPtrOriginImpl(
               continue;
             }
           }
+          auto *Method = dyn_cast<CXXMethodDecl>(Callee);
+          if (Method && isAccessorOfNeverDestroyed(Method) &&
+              operatorCall->getNumArgs() == 1) {
+            E = operatorCall->getArg(0);
+            continue;
+          }
         }
       }
 

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index afcb5bf2cb692..76afe9ada0620 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -537,6 +537,56 @@ bool isGetterOfUniquePtr(const CXXMethodDecl *M) {
   return false;
 }
 
+// The object lives until the program exits but a pointer stored in it doesn't
+// keep its pointee alive. A smart pointer keeps its pointee alive only if it
+// can't be reassigned or swapped.
+static bool isImmortalStorageType(QualType T) {
+  if (T.isNull() || T->isPointerType() || T->isReferenceType() ||
+      T->isObjCObjectPointerType())
+    return false;
+  auto Name = safeGetName(T->getAsCXXRecordDecl());
+  if (isSmartPtrClass(Name) || isOwnerPtr(Name))
+    return T.isConstQualified();
+  return true;
+}
+
+/// \returns true if \p R is NeverDestroyed or LazyNeverDestroyed which stores
+/// a non-pointer type.
+static bool isNeverDestroyedOfObject(const CXXRecordDecl *R) {
+  auto *Spec = dyn_cast_or_null<ClassTemplateSpecializationDecl>(R);
+  if (!Spec)
+    return false;
+  auto Name = safeGetName(Spec);
+  if (Name != "NeverDestroyed" && Name != "LazyNeverDestroyed")
+    return false;
+  const auto &Args = Spec->getTemplateArgs();
+  return Args.size() && Args[0].getKind() == TemplateArgument::Type &&
+         isImmortalStorageType(Args[0].getAsType());
+}
+
+bool isAccessorOfNeverDestroyed(const CXXMethodDecl *M) {
+  assert(M);
+  if (!isNeverDestroyedOfObject(M->getParent()))
+    return false;
+  if (safeGetName(M) == "get" || M->getOverloadedOperator() == OO_Arrow)
+    return true;
+  if (auto *Conversion = dyn_cast<CXXConversionDecl>(M)) {
+    const Type *T = Conversion->getConversionType().getTypePtrOrNull();
+    return T && (T->isPointerType() || T->isReferenceType());
+  }
+  return false;
+}
+
+bool isImmortalVar(const VarDecl *V) {
+  assert(V);
+  if (!V->hasGlobalStorage())
+    return false;
+  QualType T = V->getType();
+  if (V->hasAttr<NoDestroyAttr>() && isImmortalStorageType(T))
+    return true;
+  return isNeverDestroyedOfObject(T->getAsCXXRecordDecl());
+}
+
 bool isRefCounted(const CXXRecordDecl *R) {
   assert(R);
   if (auto *TmplR = R->getTemplateInstantiationPattern()) {

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
index 31c87dada0eee..e6ef9ba984637 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -195,6 +195,17 @@ std::optional<bool> isGetterOfSafePtr(const 
clang::CXXMethodDecl *Method);
 /// LazyUniqueRef, false if not.
 bool isGetterOfUniquePtr(const clang::CXXMethodDecl *Method);
 
+/// \returns true if \p M is an accessor of NeverDestroyed or
+/// LazyNeverDestroyed which stores a non-pointer type, false if not. A smart
+/// pointer type must be const.
+bool isAccessorOfNeverDestroyed(const clang::CXXMethodDecl *Method);
+
+/// \returns true if \p V is a variable with global storage whose object is
+/// never destroyed: either a NeverDestroyed or LazyNeverDestroyed which stores
+/// a non-pointer type or a non-pointer variable annotated with
+/// [[clang::no_destroy]]. A smart pointer type must be const.
+bool isImmortalVar(const clang::VarDecl *V);
+
 /// \returns true if \p F is a conversion between ref-countable or ref-counted
 /// pointer types.
 bool isPtrConversion(const FunctionDecl *F);

diff  --git a/clang/test/Analysis/Checkers/WebKit/never-destroyed.cpp 
b/clang/test/Analysis/Checkers/WebKit/never-destroyed.cpp
new file mode 100644
index 0000000000000..5ede51321f043
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/never-destroyed.cpp
@@ -0,0 +1,190 @@
+// RUN: %clang_analyze_cc1 
-analyzer-checker=alpha.webkit.UncountedCallArgsChecker,alpha.webkit.UncountedLocalVarsChecker,alpha.webkit.UncheckedCallArgsChecker,alpha.webkit.UncheckedLocalVarsChecker
 -verify %s
+
+#include "mock-types.h"
+
+namespace WTF {
+
+template <typename T> class NeverDestroyed {
+public:
+  template <typename... Args> NeverDestroyed(Args&&...);
+  operator T&() { return *storagePointer(); }
+  T& get() { return *storagePointer(); }
+  T* operator->() { return storagePointer(); }
+  operator const T&() const { return *storagePointer(); }
+  const T& get() const { return *storagePointer(); }
+  const T* operator->() const { return storagePointer(); }
+
+private:
+  T* storagePointer() const { return 
reinterpret_cast<T*>(const_cast<char*>(m_storage)); }
+  alignas(T) char m_storage[sizeof(T)];
+};
+
+template <typename T> class LazyNeverDestroyed {
+public:
+  template <typename... Args> void construct(Args&&...);
+  operator T&() { return *storagePointer(); }
+  T& get() { return *storagePointer(); }
+  T* operator->() { return storagePointer(); }
+
+private:
+  T* storagePointer() const { return 
reinterpret_cast<T*>(const_cast<char*>(m_storage)); }
+  alignas(T) char m_storage[sizeof(T)];
+};
+
+} // namespace WTF
+
+using WTF::LazyNeverDestroyed;
+using WTF::NeverDestroyed;
+
+void consume(RefCountable*);
+void consumeRef(RefCountable&);
+void consume(CheckedObj*);
+void consumeRef(CheckedObj&);
+
+namespace static_local {
+
+RefCountable& shared() {
+  static NeverDestroyed<RefCountable> instance;
+  instance->method();
+  instance.get().method();
+  consume(&instance.get());
+  consumeRef(instance);
+  RefCountable& ref = instance;
+  ref.method();
+  RefCountable* ptr = &instance.get();
+  ptr->method();
+  return instance;
+}
+
+CheckedObj& sharedChecked() {
+  static NeverDestroyed<CheckedObj> instance;
+  instance->method();
+  consumeRef(instance);
+  CheckedObj& ref = instance.get();
+  ref.method();
+  return instance;
+}
+
+void lazy() {
+  static LazyNeverDestroyed<RefCountable> instance;
+  instance.construct();
+  instance->method();
+  RefCountable& ref = instance;
+  ref.method();
+}
+
+void constRef() {
+  static NeverDestroyed<const Ref<RefCountable>> 
instance(RefCountable::create());
+  instance->get().method();
+  instance.get()->method();
+  consume(instance.get().ptr());
+  RefCountable* ptr = instance.get().ptr();
+  ptr->method();
+  RefCountable& ref = instance.get();
+  ref.method();
+}
+
+void constRefPtr() {
+  static NeverDestroyed<const RefPtr<RefCountable>> 
instance(RefCountable::create());
+  instance.get()->method();
+  consume(instance.get().get());
+  RefCountable* ptr = instance.get().get();
+  ptr->method();
+}
+
+void ref() {
+  static NeverDestroyed<Ref<RefCountable>> instance(RefCountable::create());
+  instance.get()->method();
+  // expected-warning@-1{{Function argument 'instance.get()' (parameter 'this' 
to 'RefCountable::method') is a raw pointer to RefPtr-capable type 
'RefCountable'}}
+  RefCountable* ptr = instance.get().ptr();
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+void refPtr() {
+  static NeverDestroyed<RefPtr<RefCountable>> instance(RefCountable::create());
+  instance.get()->method();
+  // expected-warning@-1{{Function argument 'instance.get()' (parameter 'this' 
to 'RefCountable::method') is a raw pointer to RefPtr-capable type 
'RefCountable'}}
+  RefCountable* ptr = instance.get().get();
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+void rawPointer() {
+  static NeverDestroyed<RefCountable*> instance;
+  instance.get()->method();
+  // expected-warning@-1{{Function argument 'instance.get()' (parameter 'this' 
to 'RefCountable::method') is a raw pointer to RefPtr-capable type 
'RefCountable'}}
+  RefCountable* ptr = instance.get();
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+} // namespace static_local
+
+namespace global {
+
+NeverDestroyed<RefCountable> globalInstance;
+LazyNeverDestroyed<CheckedObj> lazyGlobalInstance;
+
+void foo() {
+  globalInstance->method();
+  RefCountable& ref = globalInstance;
+  ref.method();
+  lazyGlobalInstance->method();
+  CheckedObj* ptr = &lazyGlobalInstance.get();
+  ptr->method();
+}
+
+} // namespace global
+
+namespace no_destroy {
+
+[[clang::no_destroy]] static RefCountable refCountable;
+[[clang::no_destroy]] static CheckedObj checkedObj;
+[[clang::no_destroy]] static RefCountable* rawPointer;
+[[clang::no_destroy]] static const RefPtr<RefCountable> constRefPtr;
+[[clang::no_destroy]] static RefPtr<RefCountable> refPtr;
+
+void foo() {
+  refCountable.method();
+  consume(&refCountable);
+  RefCountable& ref = refCountable;
+  ref.method();
+  checkedObj.method();
+  CheckedObj* ptr = &checkedObj;
+  ptr->method();
+}
+
+void bar() {
+  rawPointer->method();
+  // expected-warning@-1{{Function argument 'rawPointer' (parameter 'this' to 
'RefCountable::method') is a raw pointer to RefPtr-capable type 'RefCountable'}}
+  RefCountable* ptr = rawPointer;
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+void constSmartPointer() {
+  constRefPtr->method();
+  RefCountable* ptr = constRefPtr.get();
+  ptr->method();
+}
+
+void smartPointer() {
+  RefCountable* ptr = refPtr.get();
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+} // namespace no_destroy
+
+namespace not_immortal {
+
+RefCountable* provide();
+
+void foo() {
+  RefCountable* ptr = provide();
+  // expected-warning@-1{{Local variable 'ptr' is a raw pointer to 
RefPtr-capable type 'RefCountable'}}
+  ptr->method();
+}
+
+} // namespace not_immortal


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to