Skip to content

[analyzer] Model builtin-like functions as builtin functions #99886

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 1 commit into from
Jul 23, 2024
Merged

[analyzer] Model builtin-like functions as builtin functions #99886

merged 1 commit into from
Jul 23, 2024

Conversation

steakhal
Copy link
Contributor

Some template function instantiations don't have a body, even though their templates did have a body.
Examples are: std::move, std::forward, std::addressof etc.

They had bodies before
72315d0

After that change, the sentiment was that these special functions should be considered and treated as builtin functions.

Fixes #94193

CPP-5358

Some template function instantiations don't have a body, even though
their templates did have a body.
Examples are: `std::move`, `std::forward`, `std::addressof` etc.

They had bodies before
72315d0

After that change, the sentiment was that these special funtions
should be considered and treated as builtin functions.

Fixes #94193

CPP-5358
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Jul 22, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2024

@llvm/pr-subscribers-clang

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

Author: Balazs Benics (steakhal)

Changes

Some template function instantiations don't have a body, even though their templates did have a body.
Examples are: std::move, std::forward, std::addressof etc.

They had bodies before
72315d0

After that change, the sentiment was that these special functions should be considered and treated as builtin functions.

Fixes #94193

CPP-5358


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

7 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+4)
  • (modified) clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp (+38)
  • (modified) clang/test/Analysis/Inputs/system-header-simulator-cxx.h (+8-6)
  • (modified) clang/test/Analysis/builtin-functions.cpp (+20)
  • (modified) clang/test/Analysis/diagnostics/explicit-suppression.cpp (+1-1)
  • (added) clang/test/Analysis/issue-94193.cpp (+41)
  • (modified) clang/test/Analysis/use-after-move.cpp (+2-7)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 61629ff30aeeb..4ddb6baa49824 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1326,6 +1326,10 @@ Crash and bug fixes
 - Z3 crosschecking (aka. Z3 refutation) is now bounded, and can't consume
   more total time than the eymbolic execution itself. (#GH97298)
 
+- ``std::addressof``, ``std::as_const``, ``std::forward``,
+  ``std::forward_like``, ``std::move``, ``std::move_if_noexcept``, are now
+  modeled just like their builtin counterpart. (#GH94193)
+
 Improvements
 ^^^^^^^^^^^^
 
diff --git a/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
index 1a75d7b52ad6e..b198b1c2ff4d1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
@@ -18,6 +18,7 @@
 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
@@ -30,8 +31,40 @@ namespace {
 class BuiltinFunctionChecker : public Checker<eval::Call> {
 public:
   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+  // From: clang/include/clang/Basic/Builtins.def
+  // C++ standard library builtins in namespace 'std'.
+  const CallDescriptionSet BuiltinLikeStdFunctions{
+      {CDM::SimpleFunc, {"std", "addressof"}},        //
+      {CDM::SimpleFunc, {"std", "__addressof"}},      //
+      {CDM::SimpleFunc, {"std", "as_const"}},         //
+      {CDM::SimpleFunc, {"std", "forward"}},          //
+      {CDM::SimpleFunc, {"std", "forward_like"}},     //
+      {CDM::SimpleFunc, {"std", "move"}},             //
+      {CDM::SimpleFunc, {"std", "move_if_noexcept"}}, //
+  };
+
+  bool isBuiltinLikeFunction(const CallEvent &Call) const;
 };
 
+} // namespace
+
+bool BuiltinFunctionChecker::isBuiltinLikeFunction(
+    const CallEvent &Call) const {
+  const auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+  if (!FD || FD->getNumParams() != 1)
+    return false;
+
+  if (QualType RetTy = FD->getReturnType();
+      !RetTy->isPointerType() && !RetTy->isReferenceType())
+    return false;
+
+  if (QualType ParmTy = FD->getParamDecl(0)->getType();
+      !ParmTy->isPointerType() && !ParmTy->isReferenceType())
+    return false;
+
+  return BuiltinLikeStdFunctions.contains(Call);
 }
 
 bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
@@ -44,6 +77,11 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
   const LocationContext *LCtx = C.getLocationContext();
   const Expr *CE = Call.getOriginExpr();
 
+  if (isBuiltinLikeFunction(Call)) {
+    C.addTransition(state->BindExpr(CE, LCtx, Call.getArgSVal(0)));
+    return true;
+  }
+
   switch (FD->getBuiltinID()) {
   default:
     return false;
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
index 29326ec1f9280..a379a47515668 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
@@ -263,11 +263,13 @@ namespace std {
   template< class T > struct remove_reference<T&>  {typedef T type;};
   template< class T > struct remove_reference<T&&> {typedef T type;};
 
-  template<class T> 
-  typename remove_reference<T>::type&& move(T&& a) {
-    typedef typename remove_reference<T>::type&& RvalRef;
-    return static_cast<RvalRef>(a);
-  }
+  template<typename T> typename remove_reference<T>::type&& move(T&& a);
+  template <typename T> T *__addressof(T &x);
+  template <typename T> T *addressof(T &x);
+  template <typename T> const T& as_const(T& x);
+  template <typename T> T&& forward(T&& x);
+  // FIXME: Declare forward_like
+  // FIXME: Declare move_if_noexcept
 
   template< class T >
   using remove_reference_t = typename remove_reference<T>::type;
@@ -754,7 +756,7 @@ namespace std {
     template<class InputIter, class OutputIter>
     OutputIter __copy(InputIter II, InputIter IE, OutputIter OI) {
       while (II != IE)
-        *OI++ = *II++;
+        *OI++ = *II++; // #system_header_simulator_cxx_std_copy_impl_loop
 
       return OI;
     }
diff --git a/clang/test/Analysis/builtin-functions.cpp b/clang/test/Analysis/builtin-functions.cpp
index 8719193e405c4..f7bafabd23cbc 100644
--- a/clang/test/Analysis/builtin-functions.cpp
+++ b/clang/test/Analysis/builtin-functions.cpp
@@ -1,6 +1,16 @@
 // RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,debug.ExprInspection %s -std=c++11 -verify
 // RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc19.11.0 -fms-extensions -analyzer-checker=core,debug.ExprInspection %s -std=c++11 -verify
 
+#include "Inputs/system-header-simulator-cxx.h"
+
+namespace std {
+// Intentionally not using an "auto" return type here, as such must also have a definition.
+template <typename T, typename U> constexpr int&& forward_like(U&& x) noexcept;
+template <typename T> const T& move_if_noexcept(T& x) noexcept;
+} // namespace std
+
+template <typename T> void clang_analyzer_dump_ref(T&&);
+template <typename T> void clang_analyzer_dump_ptr(T*);
 void clang_analyzer_eval(bool);
 void clang_analyzer_warnIfReached();
 
@@ -8,6 +18,16 @@ void testAddressof(int x) {
   clang_analyzer_eval(&x == __builtin_addressof(x)); // expected-warning{{TRUE}}
 }
 
+void testStdBuiltinLikeFunctions(int x) {
+  clang_analyzer_dump_ptr(std::addressof(x));           // expected-warning{{&x}}
+  clang_analyzer_dump_ptr(std::__addressof(x));         // expected-warning{{&x}}
+  clang_analyzer_dump_ref(std::as_const(x));            // expected-warning{{&x}}
+  clang_analyzer_dump_ref(std::forward<int &>(x));      // expected-warning{{&x}}
+  clang_analyzer_dump_ref(std::forward_like<int &>(x)); // expected-warning{{&x}}
+  clang_analyzer_dump_ref(std::move(x));                // expected-warning{{&x}}
+  clang_analyzer_dump_ref(std::move_if_noexcept(x));    // expected-warning{{&x}}
+}
+
 void testSize() {
   struct {
     int x;
diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp
index 24586e37fe207..3a904dac1c0fe 100644
--- a/clang/test/Analysis/diagnostics/explicit-suppression.cpp
+++ b/clang/test/Analysis/diagnostics/explicit-suppression.cpp
@@ -19,6 +19,6 @@ class C {
 void testCopyNull(C *I, C *E) {
   std::copy(I, E, (C *)0);
 #ifndef SUPPRESSED
-  // expected-warning@../Inputs/system-header-simulator-cxx.h:757 {{Called C++ object pointer is null}}
+  // expected-warning@#system_header_simulator_cxx_std_copy_impl_loop {{Called C++ object pointer is null}}
 #endif
 }
diff --git a/clang/test/Analysis/issue-94193.cpp b/clang/test/Analysis/issue-94193.cpp
new file mode 100644
index 0000000000000..97acfdd8685be
--- /dev/null
+++ b/clang/test/Analysis/issue-94193.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_analyze_cc1 %s -verify -analyzer-checker=core
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+
+namespace GH94193 {
+template<typename T> class optional {
+  union {
+    char x;
+    T uvalue;
+  };
+  bool holds_value = false;
+public:
+  optional() = default;
+  optional(const optional&) = delete;
+  optional(optional&&) = delete;
+  template <typename U = T> explicit optional(U&& value) : holds_value(true) {
+    new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
+  }
+  optional& operator=(const optional&) = delete;
+  optional& operator=(optional&&) = delete;
+  explicit operator bool() const {
+    return holds_value;
+  }
+  T& unwrap() & {
+    return uvalue; // no-warning: returns a valid value
+  }
+};
+
+int top1(int x) {
+  optional<int> opt{x}; // note: Ctor was inlined.
+  return opt.unwrap();  // no-warning: returns a valid value
+}
+
+std::string *top2() {
+  std::string a = "123";
+  // expected-warning@+2 {{address of stack memory associated with local variable 'a' returned}} diagnosed by -Wreturn-stack-address
+  // expected-warning@+1 {{Address of stack memory associated with local variable 'a' returned to caller [core.StackAddressEscape]}}
+  return std::addressof(a);
+}
+} // namespace GH94193
diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp
index 33980e6ea2b8b..24d5dd8a8b3d2 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -570,13 +570,8 @@ void differentBranchesTest(int i) {
   {
     A a;
     a.foo() > 0 ? a.foo() : A(std::move(a)).foo();
-#ifdef DFS
-    // peaceful-note@-2 {{Assuming the condition is false}}
-    // peaceful-note@-3 {{'?' condition is false}}
-#else
-    // peaceful-note@-5 {{Assuming the condition is true}}
-    // peaceful-note@-6 {{'?' condition is true}}
-#endif
+    // peaceful-note@-1 {{Assuming the condition is true}}
+    // peaceful-note@-2 {{'?' condition is true}}
   }
   // Same thing, but with a switch statement.
   {

Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@steakhal steakhal merged commit 5663639 into llvm:main Jul 23, 2024
11 checks passed
@steakhal steakhal deleted the bb/model-builtin-like-std-fns branch July 23, 2024 06:39
Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

LGTM.

yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
Some template function instantiations don't have a body, even though
their templates did have a body.
Examples are: `std::move`, `std::forward`, `std::addressof` etc.

They had bodies before
72315d0

After that change, the sentiment was that these special functions should
be considered and treated as builtin functions.

Fixes #94193

CPP-5358

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251093
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.

[clang-tidy] std::addressof not checked by clang-analyzer-core.StackAddressEscape
4 participants