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
