[clang] [WebKit checkers] Treat destruction of a null smart pointer temporary as trivial (PR #230809)
Ryosuke Niwa via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 10 09:23:09 PDT 2026
https://github.com/rniwa created https://github.com/llvm/llvm-project/pull/230809
TrivialFunctionAnalysis treated HashMap<K, Ref<V>>::get as non-trivial because its miss path, MappedTraits::peek(MappedTraits::emptyValue()), creates and destroys a temporary Ref<V> constructed from HashTableEmptyValue. Ref's destructor calls deref(), which may delete, so any local raw pointer obtained from such a HashMap was reported by alpha.webkit.UncountedLocalVarsChecker even in otherwise trivial contexts.
Skip the destructor check of a temporary owning smart pointer when it is known to hold nullptr: one which is default constructed, constructed from nullptr or HashTableEmptyValue, copied or moved from such a smart pointer, or returned by a function whose body is a single return statement of such a smart pointer (e.g. HashTraits<Ref<T>>::emptyValue()).
Also treat CXXScalarValueInitExpr (e.g. T() for a scalar T as in GenericHashTraits<T>::emptyValue()) as trivial.
Authored with Claude Code.
>From df6d1df2123581484b3eadf8bf2b9b60a7d1146f Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Sat, 10 Oct 2026 09:18:13 -0700
Subject: [PATCH] [WebKit checkers] Treat destruction of a null smart pointer
temporary as trivial
TrivialFunctionAnalysis treated HashMap<K, Ref<V>>::get as non-trivial
because its miss path, MappedTraits::peek(MappedTraits::emptyValue()),
creates and destroys a temporary Ref<V> constructed from
HashTableEmptyValue. Ref's destructor calls deref(), which may delete, so
any local raw pointer obtained from such a HashMap was reported by
alpha.webkit.UncountedLocalVarsChecker even in otherwise trivial contexts.
Skip the destructor check of a temporary owning smart pointer when it is
known to hold nullptr: one which is default constructed, constructed from
nullptr or HashTableEmptyValue, copied or moved from such a smart pointer,
or returned by a function whose body is a single return statement of such
a smart pointer (e.g. HashTraits<Ref<T>>::emptyValue()).
Also treat CXXScalarValueInitExpr (e.g. T() for a scalar T as in
GenericHashTraits<T>::emptyValue()) as trivial.
Authored with Claude Code.
---
.../Checkers/WebKit/PtrTypesSemantics.cpp | 60 ++++++++++++++++++-
.../Analysis/Checkers/WebKit/mock-types.h | 3 +
.../Checkers/WebKit/uncounted-local-vars.cpp | 45 ++++++++++++++
3 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index 1cd2822575845..dda64bea9fec6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -1227,9 +1227,62 @@ class TrivialFunctionAnalysisVisitor
return Visit(VMT->getSubExpr());
}
+ // Returns true if E is known to evaluate to a smart pointer which holds
+ // nullptr, e.g. RefPtr<T>(), RefPtr<T>(nullptr), Ref<T>(HashTableEmptyValue),
+ // or a call to a function which simply returns one such as
+ // HashTraits<Ref<T>>::emptyValue(). Destructing such a smart pointer can't
+ // delete anything.
+ bool isNullSmartPtr(const Expr *E, unsigned Depth = 0) {
+ if (!E || Depth > 4)
+ return false;
+ while (true) {
+ E = E->IgnoreParenCasts();
+ if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
+ E = EWC->getSubExpr();
+ else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(E))
+ E = MTE->getSubExpr();
+ else if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+ E = BTE->getSubExpr();
+ else
+ break;
+ }
+ if (auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+ auto *Ctor = CE->getConstructor();
+ if (!Ctor || !isOwnerPtr(safeGetName(Ctor->getParent())))
+ return false;
+ if (!CE->getNumArgs())
+ return Ctor->isDefaultConstructor();
+ if (CE->getNumArgs() != 1)
+ return false;
+ auto *Arg = CE->getArg(0)->IgnoreParenImpCasts();
+ auto ArgType = Arg->getType();
+ if (ArgType->isNullPtrType())
+ return true;
+ if (auto *TD = ArgType->getAsTagDecl();
+ TD && safeGetName(TD) == "HashTableEmptyValueType")
+ return true;
+ if (Ctor->isCopyOrMoveConstructor())
+ return isNullSmartPtr(Arg, Depth + 1);
+ return false;
+ }
+ if (auto *CE = dyn_cast<CallExpr>(E)) {
+ auto *Callee = CE->getDirectCallee();
+ if (!Callee || (isa<CXXMethodDecl>(Callee) &&
+ cast<CXXMethodDecl>(Callee)->isVirtual()))
+ return false;
+ auto *Body = dyn_cast_or_null<CompoundStmt>(Callee->getBody());
+ if (!Body || Body->size() != 1)
+ return false;
+ auto *RS = dyn_cast<ReturnStmt>(Body->body_front());
+ return RS && isNullSmartPtr(RS->getRetValue(), Depth + 1);
+ }
+ return false;
+ }
+
bool VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE) {
if (auto *Temp = BTE->getTemporary()) {
- if (!IsFunctionTrivial(Temp->getDestructor()))
+ if (!isNullSmartPtr(BTE->getSubExpr()) &&
+ !IsFunctionTrivial(Temp->getDestructor()))
return false;
}
return Visit(BTE->getSubExpr());
@@ -1300,6 +1353,11 @@ class TrivialFunctionAnalysisVisitor
return true;
}
+ bool VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E) {
+ // A value initialization of a scalar type such as int() is trivial.
+ return true;
+ }
+
private:
CacheTy &Cache;
CacheTy FieldCtorCache;
diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h
index 46818a9642ee3..6deac440a06e1 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-types.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h
@@ -300,12 +300,15 @@ template<typename T> struct DefaultRefDerefTraits {
}
};
+enum HashTableEmptyValueType { HashTableEmptyValue };
+
template <typename T, typename PtrTraits = RawPtrTraits<T>, typename RefDerefTraits = DefaultRefDerefTraits<T>> struct Ref {
typename PtrTraits::StorageType t;
enum AdoptTag { Adopt };
Ref() : t{} {};
+ Ref(HashTableEmptyValueType) : t(nullptr) { }
Ref(T &t, AdoptTag) : t(&t) { }
Ref(T &t) : t(&RefDerefTraits::ref(t)) { }
Ref(const Ref& o) : t(RefDerefTraits::refIfNotNull(PtrTraits::unwrap(o.t))) { }
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
index 7f086f13f68e4..239b11ef90fbc 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
@@ -945,3 +945,48 @@ void unrelated_temporary_traces_to_guardian(RefCountable &obj) {
}
} // namespace short_lived_temporaries
+
+namespace hash_table_empty_value {
+
+template <typename T> struct HashTraits {
+ static T emptyValue() { return T(); }
+ static T peek(const T& value) { return value; }
+};
+
+template <typename P> struct HashTraits<Ref<P>> {
+ static Ref<P> emptyValue() { return HashTableEmptyValue; }
+ static P* peek(const Ref<P>& value) { return value.ptr(); }
+};
+
+template <typename KeyType, typename MappedType> struct SingleEntryHashMap {
+ using KeyTraits = HashTraits<KeyType>;
+ using MappedTraits = HashTraits<MappedType>;
+
+ auto get(const KeyType& key) const {
+ if (key == KeyTraits::emptyValue() || key != m_key)
+ return MappedTraits::peek(MappedTraits::emptyValue());
+ return MappedTraits::peek(*m_value);
+ }
+
+ KeyType m_key;
+ MappedType* m_value;
+};
+
+void get_in_trivial_context(const SingleEntryHashMap<int, Ref<RefCountable>>& map) {
+ {
+ auto* obj = map.get(1); // no-warning
+ obj->trivial();
+ }
+}
+
+Ref<RefCountable> makeRef(RefCountable& obj) { return obj; }
+
+void non_null_temporary(RefCountable& obj) {
+ {
+ auto* bar = HashTraits<Ref<RefCountable>>::peek(makeRef(obj));
+ // expected-warning at -1{{Local variable 'bar' is a raw pointer to RefPtr-capable type 'RefCountable' [alpha.webkit.UncountedLocalVarsChecker]}}
+ bar->trivial();
+ }
+}
+
+} // namespace hash_table_empty_value
More information about the cfe-commits
mailing list