Skip to content

[WebKit checkers] Recognize ensureFoo functions #119681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 13, 2024

Conversation

rniwa
Copy link
Contributor

@rniwa rniwa commented Dec 12, 2024

In WebKit, we often write Foo::ensureBar function which lazily initializes m_bar and returns a raw pointer or a raw reference to m_bar. Such a return value is safe to use for the duration of a member function call in Foo so long as m_bar is const so that it never gets unset or updated with a new value once it's initialized.

This PR adds support for recognizing these types of functions and treating its return value as a safe origin of a function argument (including "this") or a local variable.

In WebKit, we often write Foo::ensureBar function which lazily initializes m_bar and returns
a raw pointer or a raw reference to m_bar. Such a return value is safe to use for the
duration of a member function call in Foo so long as m_bar is const so that it never gets
unset or updated with a new value once it's initialized.

This PR adds support for recognizing these types of functions and treating its return value
as a safe origin of a function argument (including "this") or a local variable.
@rniwa rniwa requested a review from t-rasmud December 12, 2024 09:36
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Dec 12, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 12, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Ryosuke Niwa (rniwa)

Changes

In WebKit, we often write Foo::ensureBar function which lazily initializes m_bar and returns a raw pointer or a raw reference to m_bar. Such a return value is safe to use for the duration of a member function call in Foo so long as m_bar is const so that it never gets unset or updated with a new value once it's initialized.

This PR adds support for recognizing these types of functions and treating its return value as a safe origin of a function argument (including "this") or a local variable.


Full diff: https://github.com/llvm/llvm-project/pull/119681.diff

9 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp (+42)
  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h (+10)
  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp (+4-1)
  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp (+4)
  • (modified) clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp (+29)
  • (modified) clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp (+29)
  • (modified) clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp (+33)
  • (modified) clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp (+33)
  • (modified) clang/test/Analysis/Checkers/WebKit/mock-types.h (+32-29)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index b3cd594a0f3529..4086b267b6dad8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -13,6 +13,7 @@
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
+#include "clang/AST/StmtVisitor.h"
 #include <optional>
 
 namespace clang {
@@ -158,6 +159,9 @@ bool isConstOwnerPtrMemberExpr(const clang::Expr *E) {
         E = ThisArg;
       }
     }
+  } else if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(E)) {
+    if (OCE->getOperator() == OO_Star && OCE->getNumArgs() == 1)
+      E = OCE->getArg(0);
   }
   auto *ME = dyn_cast<MemberExpr>(E);
   if (!ME)
@@ -169,4 +173,42 @@ bool isConstOwnerPtrMemberExpr(const clang::Expr *E) {
   return isOwnerPtrType(T) && T.isConstQualified();
 }
 
+class EnsureFunctionVisitor
+    : public ConstStmtVisitor<EnsureFunctionVisitor, bool> {
+public:
+  bool VisitStmt(const Stmt *S) {
+    for (const Stmt *Child : S->children()) {
+      if (Child && !Visit(Child))
+        return false;
+    }
+    return true;
+  }
+
+  bool VisitReturnStmt(const ReturnStmt *RS) {
+    if (auto *RV = RS->getRetValue()) {
+      RV = RV->IgnoreParenCasts();
+      if (isa<CXXNullPtrLiteralExpr>(RV))
+        return true;
+      return isConstOwnerPtrMemberExpr(RV);
+    }
+    return false;
+  }
+};
+
+bool EnsureFunctionAnalysis::isACallToEnsureFn(const clang::Expr *E) const {
+  auto *MCE = dyn_cast<CXXMemberCallExpr>(E);
+  if (!MCE)
+    return false;
+  auto *Callee = MCE->getDirectCallee();
+  if (!Callee)
+    return false;
+  auto* Body = Callee->getBody();
+  if (!Body)
+    return false;
+  auto [CacheIt, IsNew] = Cache.insert(std::make_pair(Callee, false));
+  if (IsNew)
+    CacheIt->second = EnsureFunctionVisitor().Visit(Body);
+  return CacheIt->second;
+}
+
 } // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
index ddbef0fba04489..a4d46235dc9c47 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
@@ -67,6 +67,16 @@ bool isASafeCallArg(const clang::Expr *E);
 /// \returns true if E is a MemberExpr accessing a const smart pointer type.
 bool isConstOwnerPtrMemberExpr(const clang::Expr *E);
 
+/// \returns true if E is a CXXMemberCallExpr which returns a const smart pointer
+/// type.
+class EnsureFunctionAnalysis {
+  using CacheTy = llvm::DenseMap<const FunctionDecl *, bool>;
+  mutable CacheTy Cache{};
+
+public:
+  bool isACallToEnsureFn(const Expr *E) const;
+};
+
 /// \returns name of AST node or empty string.
 template <typename T> std::string safeGetName(const T *ASTNode) {
   const auto *const ND = llvm::dyn_cast_or_null<clang::NamedDecl>(ASTNode);
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
index ef2d42ccada65c..56fa72c100ec8c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
@@ -33,6 +33,7 @@ class RawPtrRefCallArgsChecker
   mutable BugReporter *BR;
 
   TrivialFunctionAnalysis TFA;
+  EnsureFunctionAnalysis EFA;
 
 public:
   RawPtrRefCallArgsChecker(const char *description)
@@ -140,7 +141,7 @@ class RawPtrRefCallArgsChecker
 
   bool isPtrOriginSafe(const Expr *Arg) const {
     return tryToFindPtrOrigin(Arg, /*StopAtFirstRefCountedObj=*/true,
-                              [](const clang::Expr *ArgOrigin, bool IsSafe) {
+                              [&](const clang::Expr *ArgOrigin, bool IsSafe) {
                                 if (IsSafe)
                                   return true;
                                 if (isa<CXXNullPtrLiteralExpr>(ArgOrigin)) {
@@ -154,6 +155,8 @@ class RawPtrRefCallArgsChecker
                                 }
                                 if (isASafeCallArg(ArgOrigin))
                                   return true;
+                                if (EFA.isACallToEnsureFn(ArgOrigin))
+                                  return true;
                                 return false;
                               });
   }
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index e0433c5c2c1a09..c225bbfcd46dc5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -166,6 +166,7 @@ class RawPtrRefLocalVarsChecker
     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
   BugType Bug;
   mutable BugReporter *BR;
+  EnsureFunctionAnalysis EFA;
 
 public:
   RawPtrRefLocalVarsChecker(const char *description)
@@ -278,6 +279,9 @@ class RawPtrRefLocalVarsChecker
                 if (isConstOwnerPtrMemberExpr(InitArgOrigin))
                   return true;
 
+                if (EFA.isACallToEnsureFn(InitArgOrigin))
+                  return true;
+
                 if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
                   if (auto *MaybeGuardian =
                           dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp
index 76f80a12c1703c..f7095606c77a4c 100644
--- a/clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp
@@ -49,15 +49,44 @@ class Foo {
   Foo();
   void bar();
 
+  CheckedObj& ensureObj3() {
+    if (!m_obj3)
+      const_cast<std::unique_ptr<CheckedObj>&>(m_obj3) = new CheckedObj;
+    return *m_obj3;
+  }
+
+  CheckedObj& badEnsureObj4() {
+    if (!m_obj4)
+      const_cast<std::unique_ptr<CheckedObj>&>(m_obj4) = new CheckedObj;
+    if (auto* next = m_obj4->next())
+      return *next;
+    return *m_obj4;
+  }
+
+  CheckedObj* ensureObj5() {
+    if (!m_obj5)
+      const_cast<std::unique_ptr<CheckedObj>&>(m_obj5) = new CheckedObj;
+    if (m_obj5->next())
+      return nullptr;
+    return m_obj5.get();
+  }
+
 private:
   const std::unique_ptr<CheckedObj> m_obj1;
   std::unique_ptr<CheckedObj> m_obj2;
+  const std::unique_ptr<CheckedObj> m_obj3;
+  const std::unique_ptr<CheckedObj> m_obj4;
+  const std::unique_ptr<CheckedObj> m_obj5;
 };
 
 void Foo::bar() {
   m_obj1->method();
   m_obj2->method();
   // expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}}
+  ensureObj3().method();
+  badEnsureObj4().method();
+  // expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}}
+  ensureObj5()->method();
 }
 
 } // namespace call_args_const_unique_ptr
diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp
index b3296507a5c92d..215238a7fcf071 100644
--- a/clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp
@@ -52,15 +52,44 @@ class Foo {
   Foo();
   void bar();
 
+  RefCountable& ensureObj3() {
+    if (!m_obj3)
+      const_cast<std::unique_ptr<RefCountable>&>(m_obj3) = RefCountable::makeUnique();
+    return *m_obj3;
+  }
+
+  RefCountable& badEnsureObj4() {
+    if (!m_obj4)
+      const_cast<std::unique_ptr<RefCountable>&>(m_obj4) = RefCountable::makeUnique();
+    if (auto* next = m_obj4->next())
+      return *next;
+    return *m_obj4;
+  }
+
+  RefCountable* ensureObj5() {
+    if (!m_obj5)
+      const_cast<std::unique_ptr<RefCountable>&>(m_obj5) = RefCountable::makeUnique();
+    if (m_obj5->next())
+      return nullptr;
+    return m_obj5.get();
+  }
+
 private:
   const std::unique_ptr<RefCountable> m_obj1;
   std::unique_ptr<RefCountable> m_obj2;
+  const std::unique_ptr<RefCountable> m_obj3;
+  const std::unique_ptr<RefCountable> m_obj4;
+  const std::unique_ptr<RefCountable> m_obj5;
 };
 
 void Foo::bar() {
   m_obj1->method();
   m_obj2->method();
   // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
+  ensureObj3().method();
+  badEnsureObj4().method();
+  // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
+  ensureObj5()->method();
 }
 
 } // namespace call_args_const_unique_ptr
diff --git a/clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp b/clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp
index e52d1e735f6379..be04cf26be2e82 100644
--- a/clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp
@@ -12,9 +12,36 @@ class Foo {
   Foo();
   void bar();
 
+  CheckedObj& ensureObj3() {
+    if (!m_obj3)
+      const_cast<CheckedPtr<CheckedObj>&>(m_obj3) = new CheckedObj;
+    return *m_obj3;
+  }
+
+  CheckedObj& ensureObj4() {
+    if (!m_obj4)
+      const_cast<CheckedPtr<CheckedObj>&>(m_obj4) = new CheckedObj;
+    if (auto* next = m_obj4->next()) {
+      // expected-warning@-1{{Local variable 'next' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
+      return *next;
+    }
+    return *m_obj4;
+  }
+
+  CheckedObj* ensureObj5() {
+    if (!m_obj5)
+      const_cast<CheckedPtr<CheckedObj>&>(m_obj5) = new CheckedObj;
+    if (m_obj5->next())
+      return nullptr;
+    return m_obj5.get();
+  }
+
 private:
   const CheckedPtr<CheckedObj> m_obj1;
   CheckedPtr<CheckedObj> m_obj2;
+  const CheckedPtr<CheckedObj> m_obj3;
+  const CheckedPtr<CheckedObj> m_obj4;
+  const CheckedPtr<CheckedObj> m_obj5;
 };
 
 void Foo::bar() {
@@ -23,6 +50,12 @@ void Foo::bar() {
   auto* obj2 = m_obj2.get();
   // expected-warning@-1{{Local variable 'obj2' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
   obj2->method();
+  auto& obj3 = ensureObj3();
+  obj3.method();
+  auto& obj4 = ensureObj4();
+  // expected-warning@-1{{Local variable 'obj4' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
+  obj4.method();
+  auto* obj5 = ensureObj5();
 }
 
 } // namespace local_vars_const_checkedptr_member
diff --git a/clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp b/clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp
index 03d16285f88b53..e12c9b900a423c 100644
--- a/clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp
@@ -12,9 +12,36 @@ class Foo {
   Foo();
   void bar();
 
+  RefCountable& ensureObj3() {
+    if (!m_obj3)
+      const_cast<RefPtr<RefCountable>&>(m_obj3) = RefCountable::create();
+    return *m_obj3;
+  }
+
+  RefCountable& ensureObj4() {
+    if (!m_obj4)
+      const_cast<RefPtr<RefCountable>&>(m_obj4) = RefCountable::create();
+    if (auto* next = m_obj4->next()) {
+      // expected-warning@-1{{Local variable 'next' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
+      return *next;
+    }
+    return *m_obj4;
+  }
+
+  RefCountable* ensureObj5() {
+    if (!m_obj5)
+      const_cast<RefPtr<RefCountable>&>(m_obj5) = RefCountable::create();
+    if (m_obj5->next())
+      return nullptr;
+    return m_obj5.get();
+  }
+
 private:
   const RefPtr<RefCountable> m_obj1;
   RefPtr<RefCountable> m_obj2;
+  const RefPtr<RefCountable> m_obj3;
+  const RefPtr<RefCountable> m_obj4;
+  const RefPtr<RefCountable> m_obj5;
 };
 
 void Foo::bar() {
@@ -23,6 +50,12 @@ void Foo::bar() {
   auto* obj2 = m_obj2.get();
   // expected-warning@-1{{Local variable 'obj2' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
   obj2->method();
+  auto& obj3 = ensureObj3();
+  obj3.method();
+  auto& obj4 = ensureObj4();
+  // expected-warning@-1{{Local variable 'obj4' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
+  obj4.method();
+  auto* obj5 = ensureObj5();
 }
 
 } // namespace local_vars_const_refptr_member
diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h
index fb1ee51c7ec1de..f3bd20f8bcf603 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-types.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h
@@ -1,6 +1,34 @@
 #ifndef mock_types_1103988513531
 #define mock_types_1103988513531
 
+namespace std {
+
+template <typename T>
+class unique_ptr {
+private:
+  T *t;
+
+public:
+  unique_ptr() : t(nullptr) { }
+  unique_ptr(T *t) : t(t) { }
+  ~unique_ptr() {
+    if (t)
+      delete t;
+  }
+  template <typename U> unique_ptr(unique_ptr<U>&& u)
+    : t(u.t)
+  {
+    u.t = nullptr;
+  }
+  T *get() const { return t; }
+  T *operator->() const { return t; }
+  T &operator*() const { return *t; }
+  unique_ptr &operator=(T *) { return *this; }
+  explicit operator bool() const { return !!t; }
+};
+
+};
+
 template<typename T>
 struct RawPtrTraits {
   using StorageType = T*;
@@ -103,7 +131,7 @@ template <typename T> struct RefPtr {
   }
   T *get() const { return t; }
   T *operator->() const { return t; }
-  T &operator*() { return *t; }
+  T &operator*() const { return *t; }
   RefPtr &operator=(T *t) {
     RefPtr o(t);
     swap(o);
@@ -130,6 +158,7 @@ template <typename T> bool operator!=(const RefPtr<T> &, T &) { return false; }
 
 struct RefCountable {
   static Ref<RefCountable> create();
+  static std::unique_ptr<RefCountable> makeUnique();
   void ref() {}
   void deref() {}
   void method();
@@ -176,7 +205,7 @@ template <typename T> struct CheckedPtr {
   }
   T *get() const { return t; }
   T *operator->() const { return t; }
-  T &operator*() { return *t; }
+  T &operator*() const { return *t; }
   CheckedPtr &operator=(T *) { return *this; }
   operator bool() const { return t; }
 };
@@ -187,6 +216,7 @@ class CheckedObj {
   void decrementCheckedPtrCount();
   void method();
   int trivial() { return 123; }
+  CheckedObj* next();
 };
 
 class RefCountableAndCheckable {
@@ -220,31 +250,4 @@ class UniqueRef {
   UniqueRef &operator=(T &) { return *this; }
 };
 
-namespace std {
-
-template <typename T>
-class unique_ptr {
-private:
-  T *t;
-
-public:
-  unique_ptr() : t(nullptr) { }
-  unique_ptr(T *t) : t(t) { }
-  ~unique_ptr() {
-    if (t)
-      delete t;
-  }
-  template <typename U> unique_ptr(unique_ptr<U>&& u)
-    : t(u.t)
-  {
-    u.t = nullptr;
-  }
-  T *get() const { return t; }
-  T *operator->() const { return t; }
-  T &operator*() { return *t; }
-  unique_ptr &operator=(T *) { return *this; }
-};
-
-};
-
 #endif

Copy link

github-actions bot commented Dec 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

auto [CacheIt, IsNew] = Cache.insert(std::make_pair(Callee, false));
if (IsNew)
CacheIt->second = EnsureFunctionVisitor().Visit(Body);
return CacheIt->second;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the fact you thought of performance optimization here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured we should cache the result instead of keep analyzing the same function repeatedly.

Copy link
Contributor

@t-rasmud t-rasmud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

…ow a function which just returns nullptr.
@rniwa rniwa merged commit 05860f9 into llvm:main Dec 13, 2024
8 checks passed
@rniwa rniwa deleted the recognize-ensure-functions branch December 13, 2024 09:48
rniwa added a commit to rniwa/llvm-project that referenced this pull request Feb 3, 2025
In WebKit, we often write Foo::ensureBar function which lazily
initializes m_bar and returns a raw pointer or a raw reference to m_bar.
Such a return value is safe to use for the duration of a member function
call in Foo so long as m_bar is const so that it never gets unset or
updated with a new value once it's initialized.

This PR adds support for recognizing these types of functions and
treating its return value as a safe origin of a function argument
(including "this") or a local variable.
devincoughlin pushed a commit to swiftlang/llvm-project that referenced this pull request Feb 25, 2025
In WebKit, we often write Foo::ensureBar function which lazily
initializes m_bar and returns a raw pointer or a raw reference to m_bar.
Such a return value is safe to use for the duration of a member function
call in Foo so long as m_bar is const so that it never gets unset or
updated with a new value once it's initialized.

This PR adds support for recognizing these types of functions and
treating its return value as a safe origin of a function argument
(including "this") or a local variable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants