https://github.com/geoffreygaren created 
https://github.com/llvm/llvm-project/pull/224877

RefPtr checking skips temporaries, reporting a path through any temporary as 
unsafe. This is mostly correct, but not always. For example, the following is a 
false positive:

    // makeKey() returns a temporary
    RefCountable* p = condition(makeKey()) ? guardian.ptr() : nullptr;

In the upcoming Borrow checker, it's even more important to trace through 
temporaries because not tracing an expression can drop a `lifetimebound` link, 
resulting in false **negatives**.

This patch adds tracing through temporaries. The logic is:

    * In function call arguments, temporaries are lifetime safe because the 
full expression does not end until the call returns

    * In ranged for loops, temporaries are lifetime safe because lifetime 
extends past the full expression to the duration of the loop (C++ P2718)

    * Otherwise, temporaries are not lifetime safe

Assisted-by: Claude

>From db2f325b26f14fca4ff0316cbe18ac030f1c1249 Mon Sep 17 00:00:00 2001
From: Geoff Garen <[email protected]>
Date: Tue, 25 Aug 2026 19:29:20 -0700
Subject: [PATCH] [WebKit Checkers] Trace through temporaries in
 tryToFindPtrOrigin

RefPtr checking skips temporaries, reporting a path through any temporary
as unsafe. This is mostly correct, but not always. For example, the
following is a false positive:

    // makeKey() returns a temporary
    RefCountable* p = condition(makeKey()) ? guardian.ptr() : nullptr;

In the upcoming Borrow checker, it's even more important to trace through
temporaries because not tracing an expression can drop a `lifetimebound`
link, resulting in false **negatives**.

This patch adds tracing through temporaries. The logic is:

    * In function call arguments, temporaries are lifetime safe because the
    full expression does not end until the call returns

    * In ranged for loops, temporaries are lifetime safe because lifetime
    extends past the full expression to the duration of the loop (C++ P2718)

    * Otherwise, temporaries are not lifetime safe

Assisted-by: Claude
---
 .../Checkers/WebKit/ASTUtils.cpp              | 23 ++++++++++++++----
 .../StaticAnalyzer/Checkers/WebKit/ASTUtils.h |  7 +++++-
 .../WebKit/RawPtrRefLocalVarsChecker.cpp      |  9 +++++--
 .../Checkers/WebKit/uncounted-local-vars.cpp  | 24 +++++++++++++++++++
 4 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index eeb0d8535d8ff..2d9c88de10d11 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -27,7 +27,8 @@ bool tryToFindPtrOrigin(
     std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
     std::function<bool(const clang::QualType)> isSafePtrType,
     std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
-    std::function<bool(const clang::Expr *, bool)> callback) {
+    std::function<bool(const clang::Expr *, bool)> callback,
+    bool *CrossedShortLivedTemporary) {
   while (E) {
     if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
       if (auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl())) {
@@ -39,7 +40,14 @@ bool tryToFindPtrOrigin(
           return callback(E, true);
       }
     }
+    if (auto *Cleanups = dyn_cast<ExprWithCleanups>(E)) {
+      E = Cleanups->getSubExpr();
+      continue;
+    }
     if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) {
+      if (CrossedShortLivedTemporary &&
+          tempExpr->getStorageDuration() == SD_FullExpression)
+        *CrossedShortLivedTemporary = true;
       E = tempExpr->getSubExpr();
       continue;
     }
@@ -73,12 +81,17 @@ bool tryToFindPtrOrigin(
       continue;
     }
     if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
-      return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj,
-                                isSafePtr, isSafePtrType, isSafeGlobalDecl,
-                                callback) &&
+      const bool ShortLivedOnEntry =
+          CrossedShortLivedTemporary && *CrossedShortLivedTemporary;
+      bool TrueResult = tryToFindPtrOrigin(
+          Expr->getTrueExpr(), StopAtFirstRefCountedObj, isSafePtr,
+          isSafePtrType, isSafeGlobalDecl, callback, 
CrossedShortLivedTemporary);
+      if (CrossedShortLivedTemporary)
+        *CrossedShortLivedTemporary = ShortLivedOnEntry;
+      return TrueResult &&
              tryToFindPtrOrigin(Expr->getFalseExpr(), StopAtFirstRefCountedObj,
                                 isSafePtr, isSafePtrType, isSafeGlobalDecl,
-                                callback);
+                                callback, CrossedShortLivedTemporary);
     }
     if (auto *cast = dyn_cast<CastExpr>(E)) {
       if (StopAtFirstRefCountedObj) {
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
index fc2c43f33037e..ef1b400120b77 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
@@ -52,12 +52,17 @@ class Expr;
 /// Calls \p callback with the subexpression that we traversed to and if \p
 /// StopAtFirstRefCountedObj is true we also specify whether we stopped early.
 /// Returns false if any of calls to callbacks returned false. Otherwise true.
+///
+/// If \p CrossedShortLivedTemporary is non-null, it is set to true when the
+/// path to the reported origin crosses a temporary that dies at the end of the
+/// full-expression.
 bool tryToFindPtrOrigin(
     const clang::Expr *E, bool StopAtFirstRefCountedObj,
     std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
     std::function<bool(const clang::QualType)> isSafePtrType,
     std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
-    std::function<bool(const clang::Expr *, bool)> callback);
+    std::function<bool(const clang::Expr *, bool)> callback,
+    bool *CrossedShortLivedTemporary = nullptr);
 
 /// For \p E referring to a ref-countable/-counted pointer/reference we return
 /// whether it's a safe call argument. Examples: function parameter or
diff --git 
a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index b83f1e3ea00b7..a3632187a4510 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -342,6 +342,7 @@ class RawPtrRefLocalVarsChecker
 
   bool isPtrOriginSafe(const VarDecl *V, const Expr *Value,
                        const Decl *DeclWithIssue) const {
+    bool CrossedShortLivedTemporary = false;
     return tryToFindPtrOrigin(
         Value, /*StopAtFirstRefCountedObj=*/false,
         [&](const clang::CXXRecordDecl *Record) {
@@ -352,9 +353,12 @@ class RawPtrRefLocalVarsChecker
           return Model->isSafeDecl(D, BR->getSourceManager());
         },
         [&](const clang::Expr *InitArgOrigin, bool IsSafe) {
-          if (!InitArgOrigin || IsSafe)
+          if (!InitArgOrigin)
             return true;
 
+          if (IsSafe)
+            return !CrossedShortLivedTemporary;
+
           if (isa<CXXThisExpr>(InitArgOrigin))
             return true;
 
@@ -377,7 +381,8 @@ class RawPtrRefLocalVarsChecker
             return true;
 
           return false;
-        });
+        },
+        &CrossedShortLivedTemporary);
   }
 
   bool hasGuardian(const VarDecl *V, const Expr *InitArgOrigin,
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp 
b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
index c6c75968ae924..2a3d9f2fefab8 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
@@ -799,3 +799,27 @@ namespace using_reexported_ref_deref {
   }
 
 }
+
+namespace short_lived_temporaries {
+
+Ref<RefCountable> provide_ref();
+bool condition(const Ref<RefCountable> &);
+
+void dying_ref_temporary() {
+  RefCountable *bar = provide_ref().ptr();
+  // expected-warning@-1{{Local variable 'bar' is a raw pointer to 
RefPtr-capable type 'RefCountable' [alpha.webkit.UncountedLocalVarsChecker]}}
+  someFunction();
+  bar->method();
+}
+
+void unrelated_temporary_traces_to_guardian(RefCountable &obj) {
+  Ref<RefCountable> guardian(obj);
+  {
+    RefCountable *bar = condition(provide_ref()) ? guardian.ptr() : nullptr;
+    someFunction();
+    if (bar)
+      bar->method();
+  }
+}
+
+} // namespace short_lived_temporaries

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

Reply via email to