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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^

Expand Down
38 changes: 38 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand All @@ -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;
Expand Down
14 changes: 8 additions & 6 deletions clang/test/Analysis/Inputs/system-header-simulator-cxx.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
20 changes: 20 additions & 0 deletions clang/test/Analysis/builtin-functions.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
// 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();

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;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/diagnostics/explicit-suppression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
41 changes: 41 additions & 0 deletions clang/test/Analysis/issue-94193.cpp
Original file line number Diff line number Diff line change
@@ -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
9 changes: 2 additions & 7 deletions clang/test/Analysis/use-after-move.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
{
Expand Down
Loading