
`FunctionEffectsRef::get()` is supposed to strip off layers of indirection (pointers/references, type sugar) to get to a `FunctionProtoType` (if any) and return its effects (if any). It wasn't correctly dealing with situations where the compiler implicitly converts an array to a pointer. --------- Co-authored-by: Doug Wyatt <dwyatt@apple.com> Co-authored-by: Sirraide <aeternalmail@gmail.com>
505 lines
19 KiB
C++
505 lines
19 KiB
C++
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify -Wfunction-effects -Wno-vla-extension %s
|
|
// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
|
|
// the FXAnalysis pass from running at all.
|
|
|
|
// This diagnostic is re-enabled and exercised in isolation later in this file.
|
|
#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
|
|
|
|
// --- CONSTRAINTS ---
|
|
|
|
void nb1() [[clang::nonblocking]]
|
|
{
|
|
int *pInt = new int; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
delete pInt; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
}
|
|
|
|
void nb2() [[clang::nonblocking]]
|
|
{
|
|
static int global; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
|
|
}
|
|
|
|
void nb3() [[clang::nonblocking]]
|
|
{
|
|
try {
|
|
throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
|
|
}
|
|
catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
|
|
}
|
|
}
|
|
|
|
void nb4_inline() {}
|
|
void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
|
|
void nb4() [[clang::nonblocking]]
|
|
{
|
|
nb4_inline(); // OK
|
|
nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
}
|
|
|
|
|
|
struct HasVirtual {
|
|
virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
|
|
};
|
|
|
|
void nb5() [[clang::nonblocking]]
|
|
{
|
|
HasVirtual hv;
|
|
hv.unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
}
|
|
|
|
void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
void nb6_transitively_unsafe()
|
|
{
|
|
nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
|
|
}
|
|
|
|
void nb6() [[clang::nonblocking]]
|
|
{
|
|
nb6_transitively_unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
}
|
|
|
|
thread_local int tl_var{ 42 };
|
|
|
|
bool tl_test() [[clang::nonblocking]]
|
|
{
|
|
return tl_var > 0; // expected-warning {{function with 'nonblocking' attribute must not use thread-local variables}}
|
|
}
|
|
|
|
void nb7()
|
|
{
|
|
// Make sure we verify blocks
|
|
auto blk = ^() [[clang::nonblocking]] {
|
|
throw 42; // expected-warning {{block with 'nonblocking' attribute must not throw or catch exceptions}}
|
|
};
|
|
}
|
|
|
|
void nb8()
|
|
{
|
|
// Make sure we verify lambdas
|
|
auto lambda = []() [[clang::nonblocking]] {
|
|
throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}}
|
|
};
|
|
}
|
|
|
|
void nb8a() [[clang::nonblocking]]
|
|
{
|
|
// A blocking lambda shouldn't make the outer function unsafe.
|
|
auto unsafeLambda = []() {
|
|
throw 42;
|
|
};
|
|
}
|
|
|
|
void nb8b() [[clang::nonblocking]]
|
|
{
|
|
// An unsafe lambda capture makes the outer function unsafe.
|
|
auto unsafeCapture = [foo = new int]() { // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
delete foo;
|
|
};
|
|
}
|
|
|
|
void nb8c()
|
|
{
|
|
// An unsafe lambda capture does not make the lambda unsafe.
|
|
auto unsafeCapture = [foo = new int]() [[clang::nonblocking]] {
|
|
};
|
|
}
|
|
|
|
// Make sure template expansions are found and verified.
|
|
template <typename T>
|
|
struct Adder {
|
|
static T add_explicit(T x, T y) [[clang::nonblocking]]
|
|
{
|
|
return x + y; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
}
|
|
static T add_implicit(T x, T y)
|
|
{
|
|
return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
|
|
}
|
|
};
|
|
|
|
struct Stringy {
|
|
friend Stringy operator+(const Stringy& x, const Stringy& y)
|
|
{
|
|
// Do something inferably unsafe
|
|
auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
|
|
return {};
|
|
}
|
|
};
|
|
|
|
struct Stringy2 {
|
|
friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
|
|
{
|
|
// Do something inferably unsafe
|
|
throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
|
|
}
|
|
};
|
|
|
|
void nb9() [[clang::nonblocking]]
|
|
{
|
|
Adder<int>::add_explicit(1, 2);
|
|
Adder<int>::add_implicit(1, 2);
|
|
|
|
Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
|
|
Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \
|
|
expected-note {{in template expansion here}}
|
|
}
|
|
|
|
// Make sure we verify lambdas produced from template expansions.
|
|
struct HasTemplatedLambda {
|
|
void (*fptr)() [[clang::nonblocking]];
|
|
|
|
template <typename C>
|
|
HasTemplatedLambda(const C&)
|
|
: fptr{ []() [[clang::nonblocking]] {
|
|
auto* y = new int; // expected-warning {{lambda with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
} }
|
|
{}
|
|
};
|
|
|
|
void nb9a()
|
|
{
|
|
HasTemplatedLambda bad(42);
|
|
}
|
|
|
|
// Templated function and lambda.
|
|
template <typename T>
|
|
void TemplatedFunc(T x) [[clang::nonblocking]] {
|
|
auto* ptr = new T; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
}
|
|
|
|
void nb9b() [[clang::nonblocking]] {
|
|
TemplatedFunc(42); // expected-note {{in template expansion here}}
|
|
|
|
auto foo = [](auto x) [[clang::nonblocking]] {
|
|
auto* ptr = new int; // expected-warning {{lambda with 'nonblocking' attribute must not allocate or deallocate memory}}
|
|
return x;
|
|
};
|
|
|
|
// Note that foo() won't be validated unless instantiated.
|
|
foo(42);
|
|
}
|
|
|
|
void nb10(
|
|
void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
|
|
void (*fp2)() [[clang::nonblocking]]
|
|
) [[clang::nonblocking]]
|
|
{
|
|
fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
fp2();
|
|
|
|
// When there's a cast, there's a separate diagnostic.
|
|
static_cast<void (*)()>(fp1)(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' expression}}
|
|
}
|
|
|
|
// Expression involving indirection
|
|
int nb10a() [[clang::nonblocking]];
|
|
int nb10b() [[clang::nonblocking]];
|
|
int blocking();
|
|
|
|
int nb10c(bool x) [[clang::nonblocking]]
|
|
{
|
|
int y = (x ? nb10a : blocking)(); // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
|
|
return (x ? nb10a : nb10b)(); // No diagnostic.
|
|
}
|
|
|
|
// Interactions with nonblocking(false)
|
|
void nb11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
|
|
{
|
|
}
|
|
void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
|
|
|
|
template <bool V>
|
|
struct ComputedNB {
|
|
void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}}
|
|
};
|
|
|
|
void nb11() [[clang::nonblocking]]
|
|
{
|
|
nb11_no_inference_1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
nb11_no_inference_2(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
|
|
ComputedNB<true> CNB_true;
|
|
CNB_true.method();
|
|
|
|
ComputedNB<false> CNB_false;
|
|
CNB_false.method(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
|
|
}
|
|
|
|
// Verify that when attached to a redeclaration, the attribute successfully attaches.
|
|
void nb12() {
|
|
static int x; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
|
|
}
|
|
void nb12() [[clang::nonblocking]];
|
|
void nb13() [[clang::nonblocking]] { nb12(); }
|
|
|
|
// C++ member function pointers
|
|
struct PTMFTester {
|
|
typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
|
|
|
|
void convert() [[clang::nonblocking]];
|
|
|
|
ConvertFunction mConvertFunc;
|
|
};
|
|
|
|
void PTMFTester::convert() [[clang::nonblocking]]
|
|
{
|
|
(this->*mConvertFunc)();
|
|
}
|
|
|
|
// Allow implicit conversion from array to pointer.
|
|
void nb14(unsigned idx) [[clang::nonblocking]]
|
|
{
|
|
using FP = void (*)() [[clang::nonblocking]];
|
|
using FPArray = FP[2];
|
|
auto nb = +[]() [[clang::nonblocking]] {};
|
|
|
|
FPArray src{ nb, nullptr };
|
|
FP f = src[idx]; // This should not generate a warning.
|
|
|
|
FP twoDim[2][2] = {};
|
|
FP g = twoDim[1][1];
|
|
|
|
FP vla[idx];
|
|
FP h = vla[0];
|
|
}
|
|
|
|
// Block variables
|
|
void nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
|
|
blk();
|
|
}
|
|
|
|
// References to blocks
|
|
void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
|
|
{
|
|
auto &ref = block;
|
|
ref();
|
|
}
|
|
|
|
// Builtin functions
|
|
void nb19() [[clang::nonblocking]] {
|
|
__builtin_assume(1);
|
|
void *ptr = __builtin_malloc(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_malloc'}}
|
|
__builtin_free(ptr); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_free'}}
|
|
|
|
void *p2 = __builtin_operator_new(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_new'}}
|
|
__builtin_operator_delete(p2); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_delete'}}
|
|
}
|
|
|
|
// Function try-block
|
|
void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
|
|
|
|
void nb20() [[clang::nonblocking]] {
|
|
catches(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}}
|
|
}
|
|
|
|
struct S {
|
|
int x;
|
|
S(int x) try : x(x) {} catch (...) {} // expected-note {{constructor cannot be inferred 'nonblocking' because it throws or catches exceptions}}
|
|
S(double) : x((throw 3, 3)) {} // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}} \
|
|
expected-note {{in constructor here}}
|
|
};
|
|
|
|
int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
|
|
// expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
|
|
struct A { // expected-note {{in implicit constructor here}}
|
|
int x = (throw 3, 3); // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}}
|
|
};
|
|
|
|
struct B {
|
|
int y = badi(); // expected-note {{member initializer cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
|
|
};
|
|
|
|
void f() [[clang::nonblocking]] {
|
|
S s1(3); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
|
|
S s2(3.0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
|
|
A a; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'A::A'}}
|
|
B b; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'B::B'}}
|
|
}
|
|
|
|
struct T {
|
|
int x = badi(); // expected-warning {{member initializer of constructor with 'nonblocking' attribute must not call non-'nonblocking' function 'badi'}}
|
|
T() [[clang::nonblocking]] {} // expected-note {{in constructor here}}
|
|
T(int x) [[clang::nonblocking]] : x(x) {} // OK
|
|
};
|
|
|
|
// Default arguments
|
|
int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
|
|
expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
|
|
expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
|
|
void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}} \
|
|
expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}}
|
|
}
|
|
|
|
void nb21() [[clang::nonblocking]] {
|
|
hasDefaultArg(); // expected-note {{in evaluating default argument here}} \
|
|
expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}}
|
|
}
|
|
|
|
void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}}
|
|
}
|
|
|
|
// Verify traversal of implicit code paths - constructors and destructors.
|
|
struct Unsafe {
|
|
static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
|
|
Unsafe() { problem1(); } // expected-note {{constructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
|
|
~Unsafe() { problem2(); } // expected-note {{destructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
|
|
|
|
Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
|
|
|
|
// Delegating initializer.
|
|
Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
|
|
};
|
|
|
|
struct DerivedFromUnsafe : public Unsafe {
|
|
DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
|
|
DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
|
|
~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
|
|
};
|
|
|
|
// Don't try to follow a deleted destructor, as with std::optional<T>.
|
|
struct HasDtor {
|
|
~HasDtor() {}
|
|
};
|
|
|
|
template <typename T>
|
|
struct Optional {
|
|
union {
|
|
char __null_state_;
|
|
T __val_;
|
|
};
|
|
bool engaged = false;
|
|
|
|
~Optional() {
|
|
if (engaged)
|
|
__val_.~T();
|
|
}
|
|
};
|
|
|
|
void nb_opt() [[clang::nonblocking]] {
|
|
Optional<HasDtor> x;
|
|
}
|
|
|
|
// Virtual inheritance
|
|
struct VBase {
|
|
int *Ptr;
|
|
|
|
VBase() { Ptr = new int; } // expected-note {{constructor cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
|
|
virtual ~VBase() { delete Ptr; } // expected-note {{virtual method cannot be inferred 'nonblocking'}}
|
|
};
|
|
|
|
struct VDerived : virtual VBase {
|
|
VDerived() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'VBase::VBase'}}
|
|
|
|
~VDerived() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'VBase::~VBase'}}
|
|
};
|
|
|
|
// Contexts where there is no function call, no diagnostic.
|
|
bool bad();
|
|
|
|
template <bool>
|
|
requires requires { bad(); }
|
|
void g() [[clang::nonblocking]] {}
|
|
|
|
void g() [[clang::nonblocking]] {
|
|
decltype(bad()) a; // doesn't generate a call so, OK
|
|
[[maybe_unused]] auto b = noexcept(bad());
|
|
[[maybe_unused]] auto c = sizeof(bad());
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wassume"
|
|
[[assume(bad())]]; // never evaluated, but maybe still semantically questionable?
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
// Make sure we are skipping concept requirements -- they can trigger an unexpected
|
|
// warning involving use of a function pointer (e.g. std::reverse_iterator::operator==
|
|
struct HasFoo { int foo() const { return 0; } };
|
|
|
|
template <class A, class B>
|
|
inline bool compare(const A& a, const B& b)
|
|
requires requires {
|
|
a.foo();
|
|
}
|
|
{
|
|
return a.foo() == b.foo();
|
|
}
|
|
|
|
void nb25() [[clang::nonblocking]] {
|
|
HasFoo a, b;
|
|
compare(a, b);
|
|
}
|
|
|
|
// If the callee is both noreturn and noexcept, it presumably terminates.
|
|
// Ignore it for the purposes of effect analysis.
|
|
[[noreturn]] void abort_wrapper() noexcept;
|
|
|
|
void nb26() [[clang::nonblocking]] {
|
|
abort_wrapper(); // no diagnostic
|
|
}
|
|
|
|
// --- Make sure we don't traverse requires and noexcept clauses. ---
|
|
|
|
// Apparently some requires clauses are able to be collapsed into a constant before the nonblocking
|
|
// analysis sees any function calls. This example (extracted from a real-world case where
|
|
// `operator&&` in <valarray>, preceding the inclusion of <expected>) is sufficiently complex
|
|
// to look like it contains function calls. There may be simpler examples.
|
|
|
|
namespace ExpectedTest {
|
|
|
|
template <class _Tp>
|
|
inline constexpr bool is_copy_constructible_v = __is_constructible(_Tp, _Tp&);
|
|
|
|
template <bool, class _Tp = void>
|
|
struct enable_if {};
|
|
template <class _Tp>
|
|
struct enable_if<true, _Tp> {
|
|
typedef _Tp type;
|
|
};
|
|
|
|
template <bool _Bp, class _Tp = void>
|
|
using enable_if_t = typename enable_if<_Bp, _Tp>::type;
|
|
|
|
// Doesn't seem to matter whether the enable_if is true or false.
|
|
template <class E1, class E2, enable_if_t<is_copy_constructible_v<E1>> = 0>
|
|
inline bool operator&&(const E1& x, const E2& y);
|
|
|
|
template <class _Tp, class _Err>
|
|
class expected {
|
|
public:
|
|
constexpr expected()
|
|
{}
|
|
|
|
// This is a deliberate corruption of the real implementation for simplicity.
|
|
constexpr expected(const expected&)
|
|
requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err>)
|
|
= default;
|
|
};
|
|
|
|
void test() [[clang::nonblocking]]
|
|
{
|
|
expected<int, int> a;
|
|
auto b = a; // Copy constructor.
|
|
}
|
|
|
|
} // namespace ExpectedTest
|
|
|
|
// Make sure a function call in a noexcept() clause is ignored.
|
|
constexpr bool foo() [[clang::nonblocking(false)]] { return true; }
|
|
void nb27() noexcept(foo()) [[clang::nonblocking]] {}
|
|
|
|
// Make sure that simple type traits don't cause violations.
|
|
void nb28() [[clang::nonblocking]] {
|
|
bool x = __is_constructible(int, const int&);
|
|
}
|
|
|
|
// --- nonblocking implies noexcept ---
|
|
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
|
|
|
|
void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}}
|
|
{
|
|
auto lambda = []() [[clang::nonblocking]] {}; // expected-warning {{lambda with 'nonblocking' attribute should be declared noexcept}}
|
|
}
|