Skip to content

Commit 5663639

Browse files
authored
[analyzer] Model builtin-like functions as builtin functions (#99886)
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
1 parent b2f5ac6 commit 5663639

File tree

7 files changed

+114
-14
lines changed

7 files changed

+114
-14
lines changed

clang/docs/ReleaseNotes.rst

+4
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,10 @@ Crash and bug fixes
13511351
- Z3 crosschecking (aka. Z3 refutation) is now bounded, and can't consume
13521352
more total time than the eymbolic execution itself. (#GH97298)
13531353

1354+
- ``std::addressof``, ``std::as_const``, ``std::forward``,
1355+
``std::forward_like``, ``std::move``, ``std::move_if_noexcept``, are now
1356+
modeled just like their builtin counterpart. (#GH94193)
1357+
13541358
Improvements
13551359
^^^^^^^^^^^^
13561360

clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp

+38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1919
#include "clang/StaticAnalyzer/Core/Checker.h"
2020
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
21+
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
2122
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
2223
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2324
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
@@ -30,8 +31,40 @@ namespace {
3031
class BuiltinFunctionChecker : public Checker<eval::Call> {
3132
public:
3233
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
34+
35+
private:
36+
// From: clang/include/clang/Basic/Builtins.def
37+
// C++ standard library builtins in namespace 'std'.
38+
const CallDescriptionSet BuiltinLikeStdFunctions{
39+
{CDM::SimpleFunc, {"std", "addressof"}}, //
40+
{CDM::SimpleFunc, {"std", "__addressof"}}, //
41+
{CDM::SimpleFunc, {"std", "as_const"}}, //
42+
{CDM::SimpleFunc, {"std", "forward"}}, //
43+
{CDM::SimpleFunc, {"std", "forward_like"}}, //
44+
{CDM::SimpleFunc, {"std", "move"}}, //
45+
{CDM::SimpleFunc, {"std", "move_if_noexcept"}}, //
46+
};
47+
48+
bool isBuiltinLikeFunction(const CallEvent &Call) const;
3349
};
3450

51+
} // namespace
52+
53+
bool BuiltinFunctionChecker::isBuiltinLikeFunction(
54+
const CallEvent &Call) const {
55+
const auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(Call.getDecl());
56+
if (!FD || FD->getNumParams() != 1)
57+
return false;
58+
59+
if (QualType RetTy = FD->getReturnType();
60+
!RetTy->isPointerType() && !RetTy->isReferenceType())
61+
return false;
62+
63+
if (QualType ParmTy = FD->getParamDecl(0)->getType();
64+
!ParmTy->isPointerType() && !ParmTy->isReferenceType())
65+
return false;
66+
67+
return BuiltinLikeStdFunctions.contains(Call);
3568
}
3669

3770
bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
@@ -44,6 +77,11 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
4477
const LocationContext *LCtx = C.getLocationContext();
4578
const Expr *CE = Call.getOriginExpr();
4679

80+
if (isBuiltinLikeFunction(Call)) {
81+
C.addTransition(state->BindExpr(CE, LCtx, Call.getArgSVal(0)));
82+
return true;
83+
}
84+
4785
switch (FD->getBuiltinID()) {
4886
default:
4987
return false;

clang/test/Analysis/Inputs/system-header-simulator-cxx.h

+8-6
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,13 @@ namespace std {
263263
template< class T > struct remove_reference<T&> {typedef T type;};
264264
template< class T > struct remove_reference<T&&> {typedef T type;};
265265

266-
template<class T>
267-
typename remove_reference<T>::type&& move(T&& a) {
268-
typedef typename remove_reference<T>::type&& RvalRef;
269-
return static_cast<RvalRef>(a);
270-
}
266+
template<typename T> typename remove_reference<T>::type&& move(T&& a);
267+
template <typename T> T *__addressof(T &x);
268+
template <typename T> T *addressof(T &x);
269+
template <typename T> const T& as_const(T& x);
270+
template <typename T> T&& forward(T&& x);
271+
// FIXME: Declare forward_like
272+
// FIXME: Declare move_if_noexcept
271273

272274
template< class T >
273275
using remove_reference_t = typename remove_reference<T>::type;
@@ -754,7 +756,7 @@ namespace std {
754756
template<class InputIter, class OutputIter>
755757
OutputIter __copy(InputIter II, InputIter IE, OutputIter OI) {
756758
while (II != IE)
757-
*OI++ = *II++;
759+
*OI++ = *II++; // #system_header_simulator_cxx_std_copy_impl_loop
758760

759761
return OI;
760762
}

clang/test/Analysis/builtin-functions.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,debug.ExprInspection %s -std=c++11 -verify
22
// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc19.11.0 -fms-extensions -analyzer-checker=core,debug.ExprInspection %s -std=c++11 -verify
33

4+
#include "Inputs/system-header-simulator-cxx.h"
5+
6+
namespace std {
7+
// Intentionally not using an "auto" return type here, as such must also have a definition.
8+
template <typename T, typename U> constexpr int&& forward_like(U&& x) noexcept;
9+
template <typename T> const T& move_if_noexcept(T& x) noexcept;
10+
} // namespace std
11+
12+
template <typename T> void clang_analyzer_dump_ref(T&&);
13+
template <typename T> void clang_analyzer_dump_ptr(T*);
414
void clang_analyzer_eval(bool);
515
void clang_analyzer_warnIfReached();
616

717
void testAddressof(int x) {
818
clang_analyzer_eval(&x == __builtin_addressof(x)); // expected-warning{{TRUE}}
919
}
1020

21+
void testStdBuiltinLikeFunctions(int x) {
22+
clang_analyzer_dump_ptr(std::addressof(x)); // expected-warning{{&x}}
23+
clang_analyzer_dump_ptr(std::__addressof(x)); // expected-warning{{&x}}
24+
clang_analyzer_dump_ref(std::as_const(x)); // expected-warning{{&x}}
25+
clang_analyzer_dump_ref(std::forward<int &>(x)); // expected-warning{{&x}}
26+
clang_analyzer_dump_ref(std::forward_like<int &>(x)); // expected-warning{{&x}}
27+
clang_analyzer_dump_ref(std::move(x)); // expected-warning{{&x}}
28+
clang_analyzer_dump_ref(std::move_if_noexcept(x)); // expected-warning{{&x}}
29+
}
30+
1131
void testSize() {
1232
struct {
1333
int x;

clang/test/Analysis/diagnostics/explicit-suppression.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ class C {
1919
void testCopyNull(C *I, C *E) {
2020
std::copy(I, E, (C *)0);
2121
#ifndef SUPPRESSED
22-
// expected-warning@../Inputs/system-header-simulator-cxx.h:757 {{Called C++ object pointer is null}}
22+
// expected-warning@#system_header_simulator_cxx_std_copy_impl_loop {{Called C++ object pointer is null}}
2323
#endif
2424
}

clang/test/Analysis/issue-94193.cpp

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %clang_analyze_cc1 %s -verify -analyzer-checker=core
2+
3+
#include "Inputs/system-header-simulator-cxx.h"
4+
5+
6+
namespace GH94193 {
7+
template<typename T> class optional {
8+
union {
9+
char x;
10+
T uvalue;
11+
};
12+
bool holds_value = false;
13+
public:
14+
optional() = default;
15+
optional(const optional&) = delete;
16+
optional(optional&&) = delete;
17+
template <typename U = T> explicit optional(U&& value) : holds_value(true) {
18+
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
19+
}
20+
optional& operator=(const optional&) = delete;
21+
optional& operator=(optional&&) = delete;
22+
explicit operator bool() const {
23+
return holds_value;
24+
}
25+
T& unwrap() & {
26+
return uvalue; // no-warning: returns a valid value
27+
}
28+
};
29+
30+
int top1(int x) {
31+
optional<int> opt{x}; // note: Ctor was inlined.
32+
return opt.unwrap(); // no-warning: returns a valid value
33+
}
34+
35+
std::string *top2() {
36+
std::string a = "123";
37+
// expected-warning@+2 {{address of stack memory associated with local variable 'a' returned}} diagnosed by -Wreturn-stack-address
38+
// expected-warning@+1 {{Address of stack memory associated with local variable 'a' returned to caller [core.StackAddressEscape]}}
39+
return std::addressof(a);
40+
}
41+
} // namespace GH94193

clang/test/Analysis/use-after-move.cpp

+2-7
Original file line numberDiff line numberDiff line change
@@ -570,13 +570,8 @@ void differentBranchesTest(int i) {
570570
{
571571
A a;
572572
a.foo() > 0 ? a.foo() : A(std::move(a)).foo();
573-
#ifdef DFS
574-
// peaceful-note@-2 {{Assuming the condition is false}}
575-
// peaceful-note@-3 {{'?' condition is false}}
576-
#else
577-
// peaceful-note@-5 {{Assuming the condition is true}}
578-
// peaceful-note@-6 {{'?' condition is true}}
579-
#endif
573+
// peaceful-note@-1 {{Assuming the condition is true}}
574+
// peaceful-note@-2 {{'?' condition is true}}
580575
}
581576
// Same thing, but with a switch statement.
582577
{

0 commit comments

Comments
 (0)