Skip to content

[alpha.webkit.UncountedCallArgsChecker] Detect more trivial functions #81829

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
Feb 16, 2024

Conversation

rniwa
Copy link
Contributor

@rniwa rniwa commented Feb 15, 2024

Allow address-of operator (&), enum constant, and a reference to constant as well as materializing temporqary expression and an expression with cleanups to appear within a trivial function.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Feb 15, 2024
@llvmbot
Copy link
Member

llvmbot commented Feb 15, 2024

@llvm/pr-subscribers-clang

Author: Ryosuke Niwa (rniwa)

Changes

Allow address-of operator (&), enum constant, and a reference to constant as well as materializing temporqary expression and an expression with cleanups to appear within a trivial function.


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

3 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp (+20-1)
  • (modified) clang/test/Analysis/Checkers/WebKit/mock-types.h (+1)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp (+94-1)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index bf6f9a64877c64..e8295938a855d7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -285,7 +285,7 @@ class TrivialFunctionAnalysisVisitor
 
   bool VisitUnaryOperator(const UnaryOperator *UO) {
     // Operator '*' and '!' are allowed as long as the operand is trivial.
-    if (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_LNot)
+    if (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_AddrOf || UO->getOpcode() == UO_LNot)
       return Visit(UO->getSubExpr());
 
     // Other operators are non-trivial.
@@ -306,6 +306,10 @@ class TrivialFunctionAnalysisVisitor
     if (auto *decl = DRE->getDecl()) {
       if (isa<ParmVarDecl>(decl))
         return true;
+      if (isa<EnumConstantDecl>(decl))
+        return true;
+      if (auto *VD = dyn_cast<VarDecl>(decl))
+        return VD->hasConstantInitialization() && VD->getEvaluatedValue();
     }
     return false;
   }
@@ -377,6 +381,15 @@ class TrivialFunctionAnalysisVisitor
     return Visit(ECE->getSubExpr());
   }
 
+  bool VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *VMT)
+  {
+    return Visit(VMT->getSubExpr());
+  }
+
+  bool VisitExprWithCleanups(const ExprWithCleanups *EWC) {
+    return Visit(EWC->getSubExpr());
+  }
+
   bool VisitParenExpr(const ParenExpr *PE) { return Visit(PE->getSubExpr()); }
 
   bool VisitInitListExpr(const InitListExpr *ILE) {
@@ -397,6 +410,12 @@ class TrivialFunctionAnalysisVisitor
     return true;
   }
 
+  bool VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E)
+  {
+    // nullptr is trivial.
+    return true;
+  }
+
   // Constant literal expressions are always trivial
   bool VisitIntegerLiteral(const IntegerLiteral *E) { return true; }
   bool VisitFloatingLiteral(const FloatingLiteral *E) { return true; }
diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h
index cc40487614a83d..d08a997aa8c043 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-types.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h
@@ -19,6 +19,7 @@ template <typename T> struct RefPtr {
   RefPtr(T *t) : t(t) {}
   T *get() { return t; }
   T *operator->() { return t; }
+  const T *operator->() const { return t; }
   T &operator*() { return *t; }
   RefPtr &operator=(T *) { return *this; }
   operator bool() { return t; }
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
index 156a2480901bf0..83c4414d1d01aa 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
@@ -1,7 +1,6 @@
 // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s
 
 #include "mock-types.h"
-//#include <type_traits>
 
 void WTFBreakpointTrap();
 void WTFCrashWithInfo(int, const char*, const char*, int);
@@ -60,11 +59,86 @@ NO_RETURN_DUE_TO_CRASH ALWAYS_INLINE void WTFCrashWithInfo(int line, const char*
     WTFCrashWithInfoImpl(line, file, function, counter, wtfCrashArg(reason));
 }
 
+enum class Flags : unsigned short {
+  Flag1 = 1 << 0,
+  Flag2 = 1 << 1,
+  Flag3 = 1 << 2,
+};
+
+template<typename E> class OptionSet {
+public:
+  using StorageType = unsigned short;
+
+  static constexpr OptionSet fromRaw(StorageType rawValue) {
+    return OptionSet(static_cast<E>(rawValue), FromRawValue);
+  }
+
+  constexpr OptionSet() = default;
+
+  constexpr OptionSet(E e)
+    : m_storage(static_cast<StorageType>(e)) {
+  }
+
+  constexpr StorageType toRaw() const { return m_storage; }
+
+  constexpr bool isEmpty() const { return !m_storage; }
+
+  constexpr explicit operator bool() const { return !isEmpty(); }
+
+  constexpr bool contains(E option) const { return containsAny(option); }
+  constexpr bool containsAny(OptionSet optionSet) const {
+    return !!(*this & optionSet);
+  }
+
+  constexpr bool containsAll(OptionSet optionSet) const {
+    return (*this & optionSet) == optionSet;
+  }
+
+  constexpr void add(OptionSet optionSet) { m_storage |= optionSet.m_storage; }
+
+  constexpr void remove(OptionSet optionSet)
+  {
+      m_storage &= ~optionSet.m_storage;
+  }
+
+  constexpr void set(OptionSet optionSet, bool value)
+  {
+    if (value)
+      add(optionSet);
+    else
+      remove(optionSet);
+  }
+
+  constexpr friend OptionSet operator|(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage | rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator&(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage & rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator-(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage & ~rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator^(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage ^ rhs.m_storage);
+  }
+
+private:
+  enum InitializationTag { FromRawValue };
+  constexpr OptionSet(E e, InitializationTag)
+    : m_storage(static_cast<StorageType>(e)) {
+  }
+  StorageType m_storage { 0 };
+};
+
 class Number {
 public:
   Number(int v) : v(v) { }
   Number(double);
   Number operator+(const Number&);
+  const int& value() const { return v; }
 private:
   int v;
 };
@@ -112,6 +186,19 @@ class RefCounted {
   RefCounted& trivial18() const { RELEASE_ASSERT(this, "this must be not null"); return const_cast<RefCounted&>(*this); }
   void trivial19() const { return; }
 
+  static constexpr unsigned numBits = 4;
+  int trivial20() { return v >> numBits; }
+
+  const int* trivial21() { return number ? &number->value() : nullptr; }
+
+  enum class Enum : unsigned short  {
+      Value1 = 1,
+      Value2 = 2,
+  };
+  bool trivial22() { return enumValue == Enum::Value1; }
+
+  bool trivial23() const { return OptionSet<Flags>::fromRaw(v).contains(Flags::Flag1); }
+
   static RefCounted& singleton() {
     static RefCounted s_RefCounted;
     s_RefCounted.ref();
@@ -170,6 +257,8 @@ class RefCounted {
   }
 
   unsigned v { 0 };
+  Number* number { nullptr };
+  Enum enumValue { Enum::Value1 };
 };
 
 RefCounted* refCountedObj();
@@ -208,6 +297,10 @@ class UnrelatedClass {
     getFieldTrivial().trivial17(); // no-warning
     getFieldTrivial().trivial18(); // no-warning
     getFieldTrivial().trivial19(); // no-warning
+    getFieldTrivial().trivial20(); // no-warning
+    getFieldTrivial().trivial21(); // no-warning
+    getFieldTrivial().trivial22(); // no-warning
+    getFieldTrivial().trivial23(); // no-warning
     RefCounted::singleton().trivial18(); // no-warning
     RefCounted::singleton().someFunction(); // no-warning
 

@llvmbot
Copy link
Member

llvmbot commented Feb 15, 2024

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

Author: Ryosuke Niwa (rniwa)

Changes

Allow address-of operator (&), enum constant, and a reference to constant as well as materializing temporqary expression and an expression with cleanups to appear within a trivial function.


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

3 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp (+20-1)
  • (modified) clang/test/Analysis/Checkers/WebKit/mock-types.h (+1)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp (+94-1)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index bf6f9a64877c64..e8295938a855d7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -285,7 +285,7 @@ class TrivialFunctionAnalysisVisitor
 
   bool VisitUnaryOperator(const UnaryOperator *UO) {
     // Operator '*' and '!' are allowed as long as the operand is trivial.
-    if (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_LNot)
+    if (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_AddrOf || UO->getOpcode() == UO_LNot)
       return Visit(UO->getSubExpr());
 
     // Other operators are non-trivial.
@@ -306,6 +306,10 @@ class TrivialFunctionAnalysisVisitor
     if (auto *decl = DRE->getDecl()) {
       if (isa<ParmVarDecl>(decl))
         return true;
+      if (isa<EnumConstantDecl>(decl))
+        return true;
+      if (auto *VD = dyn_cast<VarDecl>(decl))
+        return VD->hasConstantInitialization() && VD->getEvaluatedValue();
     }
     return false;
   }
@@ -377,6 +381,15 @@ class TrivialFunctionAnalysisVisitor
     return Visit(ECE->getSubExpr());
   }
 
+  bool VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *VMT)
+  {
+    return Visit(VMT->getSubExpr());
+  }
+
+  bool VisitExprWithCleanups(const ExprWithCleanups *EWC) {
+    return Visit(EWC->getSubExpr());
+  }
+
   bool VisitParenExpr(const ParenExpr *PE) { return Visit(PE->getSubExpr()); }
 
   bool VisitInitListExpr(const InitListExpr *ILE) {
@@ -397,6 +410,12 @@ class TrivialFunctionAnalysisVisitor
     return true;
   }
 
+  bool VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E)
+  {
+    // nullptr is trivial.
+    return true;
+  }
+
   // Constant literal expressions are always trivial
   bool VisitIntegerLiteral(const IntegerLiteral *E) { return true; }
   bool VisitFloatingLiteral(const FloatingLiteral *E) { return true; }
diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h
index cc40487614a83d..d08a997aa8c043 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-types.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h
@@ -19,6 +19,7 @@ template <typename T> struct RefPtr {
   RefPtr(T *t) : t(t) {}
   T *get() { return t; }
   T *operator->() { return t; }
+  const T *operator->() const { return t; }
   T &operator*() { return *t; }
   RefPtr &operator=(T *) { return *this; }
   operator bool() { return t; }
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
index 156a2480901bf0..83c4414d1d01aa 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
@@ -1,7 +1,6 @@
 // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s
 
 #include "mock-types.h"
-//#include <type_traits>
 
 void WTFBreakpointTrap();
 void WTFCrashWithInfo(int, const char*, const char*, int);
@@ -60,11 +59,86 @@ NO_RETURN_DUE_TO_CRASH ALWAYS_INLINE void WTFCrashWithInfo(int line, const char*
     WTFCrashWithInfoImpl(line, file, function, counter, wtfCrashArg(reason));
 }
 
+enum class Flags : unsigned short {
+  Flag1 = 1 << 0,
+  Flag2 = 1 << 1,
+  Flag3 = 1 << 2,
+};
+
+template<typename E> class OptionSet {
+public:
+  using StorageType = unsigned short;
+
+  static constexpr OptionSet fromRaw(StorageType rawValue) {
+    return OptionSet(static_cast<E>(rawValue), FromRawValue);
+  }
+
+  constexpr OptionSet() = default;
+
+  constexpr OptionSet(E e)
+    : m_storage(static_cast<StorageType>(e)) {
+  }
+
+  constexpr StorageType toRaw() const { return m_storage; }
+
+  constexpr bool isEmpty() const { return !m_storage; }
+
+  constexpr explicit operator bool() const { return !isEmpty(); }
+
+  constexpr bool contains(E option) const { return containsAny(option); }
+  constexpr bool containsAny(OptionSet optionSet) const {
+    return !!(*this & optionSet);
+  }
+
+  constexpr bool containsAll(OptionSet optionSet) const {
+    return (*this & optionSet) == optionSet;
+  }
+
+  constexpr void add(OptionSet optionSet) { m_storage |= optionSet.m_storage; }
+
+  constexpr void remove(OptionSet optionSet)
+  {
+      m_storage &= ~optionSet.m_storage;
+  }
+
+  constexpr void set(OptionSet optionSet, bool value)
+  {
+    if (value)
+      add(optionSet);
+    else
+      remove(optionSet);
+  }
+
+  constexpr friend OptionSet operator|(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage | rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator&(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage & rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator-(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage & ~rhs.m_storage);
+  }
+
+  constexpr friend OptionSet operator^(OptionSet lhs, OptionSet rhs) {
+    return fromRaw(lhs.m_storage ^ rhs.m_storage);
+  }
+
+private:
+  enum InitializationTag { FromRawValue };
+  constexpr OptionSet(E e, InitializationTag)
+    : m_storage(static_cast<StorageType>(e)) {
+  }
+  StorageType m_storage { 0 };
+};
+
 class Number {
 public:
   Number(int v) : v(v) { }
   Number(double);
   Number operator+(const Number&);
+  const int& value() const { return v; }
 private:
   int v;
 };
@@ -112,6 +186,19 @@ class RefCounted {
   RefCounted& trivial18() const { RELEASE_ASSERT(this, "this must be not null"); return const_cast<RefCounted&>(*this); }
   void trivial19() const { return; }
 
+  static constexpr unsigned numBits = 4;
+  int trivial20() { return v >> numBits; }
+
+  const int* trivial21() { return number ? &number->value() : nullptr; }
+
+  enum class Enum : unsigned short  {
+      Value1 = 1,
+      Value2 = 2,
+  };
+  bool trivial22() { return enumValue == Enum::Value1; }
+
+  bool trivial23() const { return OptionSet<Flags>::fromRaw(v).contains(Flags::Flag1); }
+
   static RefCounted& singleton() {
     static RefCounted s_RefCounted;
     s_RefCounted.ref();
@@ -170,6 +257,8 @@ class RefCounted {
   }
 
   unsigned v { 0 };
+  Number* number { nullptr };
+  Enum enumValue { Enum::Value1 };
 };
 
 RefCounted* refCountedObj();
@@ -208,6 +297,10 @@ class UnrelatedClass {
     getFieldTrivial().trivial17(); // no-warning
     getFieldTrivial().trivial18(); // no-warning
     getFieldTrivial().trivial19(); // no-warning
+    getFieldTrivial().trivial20(); // no-warning
+    getFieldTrivial().trivial21(); // no-warning
+    getFieldTrivial().trivial22(); // no-warning
+    getFieldTrivial().trivial23(); // no-warning
     RefCounted::singleton().trivial18(); // no-warning
     RefCounted::singleton().someFunction(); // no-warning
 

Copy link

github-actions bot commented Feb 15, 2024

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

Allow address-of operator (&), enum constant, and a reference to constant
as well as materializing temporqary expression and an expression with cleanups
to appear within a trivial function.
@rniwa rniwa force-pushed the detect-more-trivial-functions branch from 793c721 to 382ce72 Compare February 15, 2024 07:38
@rniwa rniwa merged commit ceaf09c into llvm:main Feb 16, 2024
@rniwa rniwa deleted the detect-more-trivial-functions branch February 16, 2024 04:53
rniwa added a commit to rniwa/llvm-project that referenced this pull request Mar 8, 2024
…llvm#81829)

Allow address-of operator (&), enum constant, and a reference to
constant as well as materializing temporqary expression and an
expression with cleanups to appear within a trivial function.
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.

3 participants