https://github.com/rniwa created
https://github.com/llvm/llvm-project/pull/224723
TrivialFunctionAnalysisVisitor had no handler for CXXStdInitializerListExpr, so
a braced list bound to a std::initializer_list fell through to VisitStmt and
was conservatively treated as non-trivial. This made any nodelete function
containing e.g. std::min({a, b, c}) report that it "contains code that could
destruct an object".
The backing array of a std::initializer_list is a temporary whose lifetime ends
in the enclosing function, so its elements really are destructed there. Accept
the node when the array's element type is trivially destructible and recurse
into the initializers, and keep rejecting it otherwise.
>From 75dbb1d9efe3d59d357d9e18e3a6eb448bacd8bd Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <[email protected]>
Date: Fri, 18 Sep 2026 12:56:37 -0700
Subject: [PATCH] [alpha.webkit.NoDeleteChecker] Handle
CXXStdInitializerListExpr in trivial analysis
TrivialFunctionAnalysisVisitor had no handler for CXXStdInitializerListExpr,
so a braced list bound to a std::initializer_list fell through to VisitStmt
and was conservatively treated as non-trivial. This made any nodelete
function containing e.g. std::min({a, b, c}) report that it "contains code
that could destruct an object".
The backing array of a std::initializer_list is a temporary whose lifetime
ends in the enclosing function, so its elements really are destructed there.
Accept the node when the array's element type is trivially destructible and
recurse into the initializers, and keep rejecting it otherwise.
---
.../Checkers/WebKit/PtrTypesSemantics.cpp | 11 ++++
.../Checkers/WebKit/nodelete-annotation.cpp | 52 +++++++++++++++++++
2 files changed, 63 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index d8a62db4daee1f..196ee4cf99d089 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -1095,6 +1095,17 @@ class TrivialFunctionAnalysisVisitor
return true;
}
+ bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *ILE) {
+ auto *SubExpr = ILE->getSubExpr();
+ if (!SubExpr)
+ return false;
+ // The backing array of a std::initializer_list is a temporary whose
+ // lifetime ends in this function, so its elements are destructed here.
+ if (!CanTriviallyDestruct(SubExpr->getType()))
+ return false;
+ return Visit(SubExpr);
+ }
+
bool VisitMemberExpr(const MemberExpr *ME) {
// Field access is allowed but the base pointer may itself be non-trivial.
return Visit(ME->getBase());
diff --git a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
index 83ec6e704f14a5..e1d0ffc05cb77b 100644
--- a/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/nodelete-annotation.cpp
@@ -795,3 +795,55 @@ void [[clang::annotate_type("webkit.nodelete")]]
valueInitNew() {
}
} // namespace trivial_implicit_ctor_in_new_expr
+
+namespace std {
+
+// The compiler only recognises the real ::std::initializer_list, so this mock
+// has to live in the global std namespace.
+template <typename T>
+class initializer_list {
+ const T* m_begin;
+ decltype(sizeof(0)) m_size;
+
+public:
+ constexpr initializer_list() : m_begin(nullptr), m_size(0) { }
+ constexpr const T* begin() const { return m_begin; }
+ constexpr const T* end() const { return m_begin + m_size; }
+ constexpr decltype(sizeof(0)) size() const { return m_size; }
+};
+
+template <typename T>
+constexpr T min(initializer_list<T> list) {
+ const T* first = list.begin();
+ const T* last = list.end();
+ T result = *first;
+ for (++first; first != last; ++first) {
+ if (*first < result)
+ result = *first;
+ }
+ return result;
+}
+
+} // namespace std
+
+namespace std_initializer_list {
+
+// A braced list passed as std::initializer_list materialises a backing array
+// temporary wrapped in a CXXStdInitializerListExpr. That array is destructed
in
+// this function, so it's only safe when its element type destructs trivially.
+
+unsigned [[clang::annotate_type("webkit.nodelete")]] safeSize();
+
+void [[clang::annotate_type("webkit.nodelete")]]
callsMinWithInitializerList(unsigned other) {
+ unsigned smallest = std::min({ safeSize(), other, 3u });
+ (void)smallest;
+}
+
+void takesTrackedList(std::initializer_list<ObjectWithNonTrivialDestructor>);
+
+void [[clang::annotate_type("webkit.nodelete")]] passesListOfTrackedObjects() {
+ takesTrackedList({ ObjectWithNonTrivialDestructor(),
ObjectWithNonTrivialDestructor() });
+ // expected-warning@-1{{A function 'passesListOfTrackedObjects' has
[[clang::annotate_type("webkit.nodelete")]] but it contains code that could
destruct an object}}
+}
+
+} // namespace std_initializer_list
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits