[clang] [alpha.webkit.UncountedCallArgsChecker] Ignore calls to WTF's container methods (PR #82156)

Ryosuke Niwa via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 19 01:06:19 PST 2024


https://github.com/rniwa updated https://github.com/llvm/llvm-project/pull/82156

>From d169fddf3896bd334bc4776059258116ac08096a Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Sun, 18 Feb 2024 01:32:00 -0800
Subject: [PATCH] [alpha.webkit.UncountedCallArgsChecker] Ignore calls to WTF's
 container methods

This PR makes the checker ignore / skip calls to methods of Web Template Framework's container types
such as HashMap, HashSet, WeakHashSet, WeakHashMap, Vector, etc...
---
 .../WebKit/UncountedCallArgsChecker.cpp       |  32 ++++
 .../WebKit/call-args-wtf-containers.cpp       | 146 ++++++++++++++++++
 2 files changed, 178 insertions(+)
 create mode 100644 clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp
index 17a64e1b1b8e04..9ed7f2a4e065d3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp
@@ -25,6 +25,11 @@ using namespace ento;
 
 namespace {
 
+bool stringEndsWith(const std::string &str, const std::string &suffix) {
+  auto index = str.rfind(suffix);
+  return index != std::string::npos && str.size() - suffix.size() == index;
+}
+
 class UncountedCallArgsChecker
     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
   BugType Bug{this,
@@ -165,6 +170,9 @@ class UncountedCallArgsChecker
     if (!Callee)
       return false;
 
+    if (isMethodOnWTFContainerType(Callee))
+      return true;
+
     auto overloadedOperatorType = Callee->getOverloadedOperator();
     if (overloadedOperatorType == OO_EqualEqual ||
         overloadedOperatorType == OO_ExclaimEqual ||
@@ -193,6 +201,30 @@ class UncountedCallArgsChecker
     return false;
   }
 
+  bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const {
+    if (!isa<CXXMethodDecl>(Decl))
+      return false;
+    auto *ClassDecl = Decl->getParent();
+    if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl))
+      return false;
+
+    auto *NsDecl = ClassDecl->getParent();
+    if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
+      return false;
+
+    auto methodName = safeGetName(Decl);
+    auto clsName = safeGetName(ClassDecl);
+    auto nsName = safeGetName(NsDecl);
+    // FIXME: These should be implemented via attributes.
+    return nsName == "WTF" &&
+           (methodName == "find" || methodName == "findIf" ||
+            methodName == "reverseFind" || methodName == "reverseFindIf" ||
+            methodName == "get" || methodName == "inlineGet" ||
+            methodName == "contains" || methodName == "containsIf") &&
+           (stringEndsWith(clsName, "Vector") ||
+            stringEndsWith(clsName, "Set") || stringEndsWith(clsName, "Map"));
+  }
+
   void reportBug(const Expr *CallArg, const ParmVarDecl *Param) const {
     assert(CallArg);
 
diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp
new file mode 100644
index 00000000000000..0a63a789856127
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp
@@ -0,0 +1,146 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s
+
+#include "mock-types.h"
+
+namespace WTF {
+
+  template <typename T>
+  class HashSet {
+  public:
+    template <typename U> T* find(U&) const;
+    template <typename U> bool contains(U&) const;
+    unsigned size() { return m_size; }
+    template <typename U> void add(U&) const;
+    template <typename U> void remove(U&) const;
+
+  private:
+    T* m_table { nullptr };
+    unsigned m_size { 0 };
+  };
+
+  template <typename T, typename S>
+  class HashMap {
+  public:
+    struct Item {
+      T key;
+      S value;
+    };
+
+    template <typename U> Item* find(U&) const;
+    template <typename U> bool contains(U&) const;
+    template <typename U> S* get(U&) const;
+    template <typename U> S* inlineGet(U&) const;
+    template <typename U> void add(U&) const;
+    template <typename U> void remove(U&) const;
+
+  private:
+    Item* m_table { nullptr };
+  };
+
+  template <typename T>
+  class WeakHashSet {
+  public:
+    template <typename U> T* find(U&) const;
+    template <typename U> bool contains(U&) const;
+    template <typename U> void add(U&) const;
+    template <typename U> void remove(U&) const;
+  };
+
+  template <typename T>
+  class Vector {
+  public:
+    unsigned size() { return m_size; }
+    T& at(unsigned i) { return m_buffer[i]; }
+    T& operator[](unsigned i) { return m_buffer[i]; }
+    template <typename U> unsigned find(U&);
+    template <typename U> unsigned reverseFind(U&);
+    template <typename U> bool contains(U&);
+    template <typename MatchFunction> unsigned findIf(const MatchFunction& match)
+    {
+      for (unsigned i = 0; i < m_size; ++i) {
+        if (match(at(i)))
+          return i;
+      }
+      return static_cast<unsigned>(-1);
+    }
+    template <typename MatchFunction> unsigned reverseFindIf(const MatchFunction& match)
+    {
+      for (unsigned i = 0; i < m_size; ++i) {
+        if (match(at(m_size - i)))
+          return i;
+      }
+      return static_cast<unsigned>(-1);
+    }
+    template <typename MatchFunction> bool containsIf(const MatchFunction& match)
+    {
+      for (unsigned i = 0; i < m_size; ++i) {
+        if (match(at(m_size - i)))
+          return true;
+      }
+      return false;
+    }
+    template <typename U> void append(U&) const;
+    template <typename U> void remove(U&) const;
+
+  private:
+    T* m_buffer { nullptr };
+    unsigned m_size { 0 };
+  };
+
+}
+
+using WTF::HashSet;
+using WTF::HashMap;
+using WTF::WeakHashSet;
+using WTF::Vector;
+
+class RefCounted {
+public:
+  void ref() const;
+  void deref() const;
+};
+
+RefCounted* object();
+
+void test() {
+  HashSet<RefPtr<RefCounted>> set;
+  set.find(*object());
+  set.contains(*object());
+  set.add(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+  set.remove(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+
+  HashMap<Ref<RefCounted>, unsigned> map;
+  map.find(*object());
+  map.contains(*object());
+  map.inlineGet(*object());
+  map.add(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+  map.remove(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+
+  WeakHashSet<Ref<RefCounted>> weakSet;
+  weakSet.find(*object());
+  weakSet.contains(*object());
+  weakSet.add(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+  weakSet.remove(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+
+  Vector<Ref<RefCounted>> vector;
+  vector.at(0);
+  vector[0];
+  vector.find(*object());
+  vector.reverseFind(*object());
+  vector.contains(*object());
+  vector.append(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+  vector.remove(*object());
+  // expected-warning at -1{{Call argument is uncounted and unsafe}}
+
+  auto* obj = object();
+  vector.findIf([&](Ref<RefCounted> key) { return key.ptr() == obj; });
+  vector.reverseFindIf([&](Ref<RefCounted> key) { return key.ptr() == obj; });
+  vector.containsIf([&](Ref<RefCounted> key) { return key.ptr() == obj; });
+}
\ No newline at end of file



More information about the cfe-commits mailing list