
HandleMemberPointerAccess considered whether the lvalue path in a member pointer access matched the bases of the containing class of the member, but neglected to check the same for the containing class of the member itself, thereby ignoring access attempts to members in direct sibling classes. Fixes #150705. Fixes #150709.
1513 lines
48 KiB
C++
1513 lines
48 KiB
C++
// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu -Wno-mismatched-new-delete
|
|
|
|
#include "Inputs/std-compare.h"
|
|
|
|
namespace std {
|
|
struct type_info;
|
|
struct destroying_delete_t {
|
|
explicit destroying_delete_t() = default;
|
|
} inline constexpr destroying_delete{};
|
|
struct nothrow_t {
|
|
explicit nothrow_t() = default;
|
|
} inline constexpr nothrow{};
|
|
using size_t = decltype(sizeof(0));
|
|
enum class align_val_t : size_t {};
|
|
};
|
|
|
|
[[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t);
|
|
void operator delete(void*, const std::nothrow_t&) noexcept;
|
|
void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
void operator delete[](void*, const std::nothrow_t&) noexcept;
|
|
void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
|
|
// Helper to print out values for debugging.
|
|
constexpr void not_defined();
|
|
template<typename T> constexpr void print(T) { not_defined(); }
|
|
|
|
namespace ThreeWayComparison {
|
|
struct A {
|
|
int n;
|
|
constexpr friend int operator<=>(const A &a, const A &b) {
|
|
return a.n < b.n ? -1 : a.n > b.n ? 1 : 0;
|
|
}
|
|
};
|
|
static_assert(A{1} <=> A{2} < 0);
|
|
static_assert(A{2} <=> A{1} > 0);
|
|
static_assert(A{2} <=> A{2} == 0);
|
|
|
|
static_assert(1 <=> 2 < 0);
|
|
static_assert(2 <=> 1 > 0);
|
|
static_assert(1 <=> 1 == 0);
|
|
constexpr int k = (1 <=> 1, 0);
|
|
// expected-warning@-1 {{three-way comparison result unused}}
|
|
|
|
static_assert(std::strong_ordering::equal == 0);
|
|
|
|
constexpr void f() {
|
|
void(1 <=> 1);
|
|
}
|
|
|
|
struct MemPtr {
|
|
void foo() {}
|
|
void bar() {}
|
|
int data;
|
|
int data2;
|
|
long data3;
|
|
};
|
|
|
|
struct MemPtr2 {
|
|
void foo() {}
|
|
void bar() {}
|
|
int data;
|
|
int data2;
|
|
long data3;
|
|
};
|
|
using MemPtrT = void (MemPtr::*)();
|
|
|
|
using FnPtrT = void (*)();
|
|
|
|
void FnPtr1() {}
|
|
void FnPtr2() {}
|
|
|
|
#define CHECK(...) ((__VA_ARGS__) ? void() : throw "error")
|
|
#define CHECK_TYPE(...) static_assert(__is_same(__VA_ARGS__));
|
|
|
|
constexpr bool test_constexpr_success = [] {
|
|
{
|
|
auto &EQ = std::strong_ordering::equal;
|
|
auto &LESS = std::strong_ordering::less;
|
|
auto &GREATER = std::strong_ordering::greater;
|
|
using SO = std::strong_ordering;
|
|
auto eq = (42 <=> 42);
|
|
CHECK_TYPE(decltype(eq), SO);
|
|
CHECK(eq.test_eq(EQ));
|
|
|
|
auto less = (-1 <=> 0);
|
|
CHECK_TYPE(decltype(less), SO);
|
|
CHECK(less.test_eq(LESS));
|
|
|
|
auto greater = (42l <=> 1u);
|
|
CHECK_TYPE(decltype(greater), SO);
|
|
CHECK(greater.test_eq(GREATER));
|
|
}
|
|
{
|
|
using PO = std::partial_ordering;
|
|
auto EQUIV = PO::equivalent;
|
|
auto LESS = PO::less;
|
|
auto GREATER = PO::greater;
|
|
|
|
auto eq = (42.0 <=> 42.0);
|
|
CHECK_TYPE(decltype(eq), PO);
|
|
CHECK(eq.test_eq(EQUIV));
|
|
|
|
auto less = (39.0 <=> 42.0);
|
|
CHECK_TYPE(decltype(less), PO);
|
|
CHECK(less.test_eq(LESS));
|
|
|
|
auto greater = (-10.123 <=> -101.1);
|
|
CHECK_TYPE(decltype(greater), PO);
|
|
CHECK(greater.test_eq(GREATER));
|
|
}
|
|
|
|
return true;
|
|
}();
|
|
|
|
int dummy = 42;
|
|
int dummy2 = 101;
|
|
constexpr bool tc9 = (&dummy <=> &dummy2) != 0; // expected-error {{constant expression}} expected-note {{unspecified}}
|
|
|
|
template <class T, class R, class I>
|
|
constexpr T makeComplex(R r, I i) {
|
|
T res{r, i};
|
|
return res;
|
|
};
|
|
} // namespace ThreeWayComparison
|
|
|
|
constexpr bool for_range_init() {
|
|
int k = 0;
|
|
for (int arr[3] = {1, 2, 3}; int n : arr) k += n;
|
|
return k == 6;
|
|
}
|
|
static_assert(for_range_init());
|
|
|
|
namespace Virtual {
|
|
struct NonZeroOffset { int padding = 123; };
|
|
|
|
constexpr void assert(bool b) { if (!b) throw 0; }
|
|
|
|
// Ensure that we pick the right final overrider during construction.
|
|
struct A {
|
|
virtual constexpr char f() const { return 'A'; }
|
|
char a = f();
|
|
constexpr ~A() { assert(f() == 'A'); }
|
|
};
|
|
struct NoOverrideA : A {};
|
|
struct B : NonZeroOffset, NoOverrideA {
|
|
virtual constexpr char f() const { return 'B'; }
|
|
char b = f();
|
|
constexpr ~B() { assert(f() == 'B'); }
|
|
};
|
|
struct NoOverrideB : B {};
|
|
struct C : NonZeroOffset, A {
|
|
virtual constexpr char f() const { return 'C'; }
|
|
A *pba;
|
|
char c = ((A*)this)->f();
|
|
char ba = pba->f();
|
|
constexpr C(A *pba) : pba(pba) {}
|
|
constexpr ~C() { assert(f() == 'C'); }
|
|
};
|
|
struct D : NonZeroOffset, NoOverrideB, C { // expected-warning {{inaccessible}}
|
|
virtual constexpr char f() const { return 'D'; }
|
|
char d = f();
|
|
constexpr D() : C((B*)this) {}
|
|
constexpr ~D() { assert(f() == 'D'); }
|
|
};
|
|
constexpr int n = (D(), 0);
|
|
constexpr D d;
|
|
static_assert(((B&)d).a == 'A');
|
|
static_assert(((C&)d).a == 'A');
|
|
static_assert(d.b == 'B');
|
|
static_assert(d.c == 'C');
|
|
// During the construction of C, the dynamic type of B's A is B.
|
|
static_assert(d.ba == 'B');
|
|
static_assert(d.d == 'D');
|
|
static_assert(d.f() == 'D');
|
|
constexpr const A &a = (B&)d;
|
|
constexpr const B &b = d;
|
|
static_assert(a.f() == 'D');
|
|
static_assert(b.f() == 'D');
|
|
|
|
// FIXME: It is unclear whether this should be permitted.
|
|
D d_not_constexpr;
|
|
static_assert(d_not_constexpr.f() == 'D'); // expected-error {{constant expression}} expected-note {{virtual function called on object 'd_not_constexpr' whose dynamic type is not constant}}
|
|
|
|
// Check that we apply a proper adjustment for a covariant return type.
|
|
struct Covariant1 {
|
|
D d;
|
|
virtual const A *f() const;
|
|
};
|
|
template<typename T>
|
|
struct Covariant2 : Covariant1 {
|
|
virtual const T *f() const;
|
|
};
|
|
template<typename T>
|
|
struct Covariant3 : Covariant2<T> {
|
|
constexpr virtual const D *f() const { return &this->d; }
|
|
};
|
|
|
|
constexpr Covariant3<B> cb;
|
|
constexpr Covariant3<C> cc;
|
|
|
|
constexpr const Covariant1 *cb1 = &cb;
|
|
constexpr const Covariant2<B> *cb2 = &cb;
|
|
static_assert(cb1->f()->a == 'A');
|
|
static_assert(cb1->f() == (B*)&cb.d);
|
|
static_assert(cb1->f()->f() == 'D');
|
|
static_assert(cb2->f()->b == 'B');
|
|
static_assert(cb2->f() == &cb.d);
|
|
static_assert(cb2->f()->f() == 'D');
|
|
|
|
constexpr const Covariant1 *cc1 = &cc;
|
|
constexpr const Covariant2<C> *cc2 = &cc;
|
|
static_assert(cc1->f()->a == 'A');
|
|
static_assert(cc1->f() == (C*)&cc.d);
|
|
static_assert(cc1->f()->f() == 'D');
|
|
static_assert(cc2->f()->c == 'C');
|
|
static_assert(cc2->f() == &cc.d);
|
|
static_assert(cc2->f()->f() == 'D');
|
|
|
|
static_assert(cb.f()->d == 'D');
|
|
static_assert(cc.f()->d == 'D');
|
|
|
|
struct Abstract {
|
|
constexpr virtual void f() = 0; // expected-note {{declared here}}
|
|
constexpr Abstract() { do_it(); } // expected-note {{in call to}}
|
|
constexpr void do_it() { f(); } // expected-note {{pure virtual function 'Virtual::Abstract::f' called}}
|
|
};
|
|
struct PureVirtualCall : Abstract { void f(); }; // expected-note {{in call to 'Abstract}}
|
|
constexpr PureVirtualCall pure_virtual_call; // expected-error {{constant expression}} expected-note {{in call to 'PureVirtualCall}}
|
|
}
|
|
|
|
namespace DynamicCast {
|
|
struct A2 { virtual void a2(); };
|
|
struct A : A2 { virtual void a(); };
|
|
struct B : A {};
|
|
struct C2 { virtual void c2(); };
|
|
struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); };
|
|
struct D { virtual void d(); };
|
|
struct E { virtual void e(); };
|
|
struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); };
|
|
struct Padding { virtual void padding(); };
|
|
struct G : Padding, F {};
|
|
|
|
constexpr G g;
|
|
|
|
// During construction of C, A is unambiguous subobject of dynamic type C.
|
|
static_assert(g.c == (C*)&g);
|
|
// ... but in the complete object, the same is not true, so the runtime fails.
|
|
static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&g)) == nullptr);
|
|
|
|
// dynamic_cast<void*> produces a pointer to the object of the dynamic type.
|
|
static_assert(g.f == (void*)(F*)&g);
|
|
static_assert(dynamic_cast<const void*>(static_cast<const D*>(&g)) == &g);
|
|
|
|
// expected-note@+1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}}
|
|
constexpr int d_a = (dynamic_cast<const A&>(static_cast<const D&>(g)), 0); // expected-error {{}}
|
|
|
|
// Can navigate from A2 to its A...
|
|
static_assert(&dynamic_cast<A&>((A2&)(B&)g) == &(A&)(B&)g);
|
|
// ... and from B to its A ...
|
|
static_assert(&dynamic_cast<A&>((B&)g) == &(A&)(B&)g);
|
|
// ... but not from D.
|
|
// expected-note@+1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}}
|
|
static_assert(&dynamic_cast<A&>((D&)g) == &(A&)(B&)g); // expected-error {{}}
|
|
|
|
// Can cast from A2 to sibling class D.
|
|
static_assert(&dynamic_cast<D&>((A2&)(B&)g) == &(D&)g);
|
|
|
|
// Cannot cast from private base E to derived class F.
|
|
// expected-note@+1 {{reference dynamic_cast failed: static type 'DynamicCast::E' of operand is a non-public base class of dynamic type 'DynamicCast::G'}}
|
|
constexpr int e_f = (dynamic_cast<F&>((E&)g), 0); // expected-error {{}}
|
|
|
|
// Cannot cast from B to private sibling E.
|
|
// expected-note@+1 {{reference dynamic_cast failed: 'E' is a non-public base class of dynamic type 'DynamicCast::G' of operand}}
|
|
constexpr int b_e = (dynamic_cast<E&>((B&)g), 0); // expected-error {{}}
|
|
|
|
struct Unrelated { virtual void unrelated(); };
|
|
// expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'Unrelated'}}
|
|
constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)g), 0); // expected-error {{}}
|
|
// expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'Unrelated'}}
|
|
constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)g), 0); // expected-error {{}}
|
|
}
|
|
|
|
namespace TypeId {
|
|
struct A {
|
|
const std::type_info &ti = typeid(*this);
|
|
};
|
|
struct A2 : A {};
|
|
static_assert(&A().ti == &typeid(A));
|
|
static_assert(&typeid((A2())) == &typeid(A2));
|
|
extern A2 extern_a2;
|
|
static_assert(&typeid(extern_a2) == &typeid(A2));
|
|
|
|
constexpr A2 a2;
|
|
constexpr const A &a1 = a2;
|
|
static_assert(&typeid(a1) == &typeid(A));
|
|
|
|
struct B {
|
|
virtual void f();
|
|
const std::type_info &ti1 = typeid(*this);
|
|
};
|
|
struct B2 : B {
|
|
const std::type_info &ti2 = typeid(*this);
|
|
};
|
|
static_assert(&B2().ti1 == &typeid(B));
|
|
static_assert(&B2().ti2 == &typeid(B2));
|
|
extern B2 extern_b2;
|
|
static_assert(&typeid(extern_b2) == &typeid(B2));
|
|
|
|
constexpr B2 b2;
|
|
constexpr const B &b1 = b2;
|
|
static_assert(&typeid(b1) == &typeid(B2));
|
|
|
|
constexpr bool side_effects() {
|
|
// Not polymorphic nor a glvalue.
|
|
bool OK = true;
|
|
(void)typeid(OK = false, A2()); // expected-warning {{has no effect}}
|
|
if (!OK) return false;
|
|
|
|
// Not polymorphic.
|
|
A2 a2;
|
|
(void)typeid(OK = false, a2); // expected-warning {{has no effect}}
|
|
if (!OK) return false;
|
|
|
|
// Not a glvalue.
|
|
(void)typeid(OK = false, B2()); // expected-warning {{has no effect}}
|
|
if (!OK) return false;
|
|
|
|
// Polymorphic glvalue: operand evaluated.
|
|
OK = false;
|
|
B2 b2;
|
|
(void)typeid(OK = true, b2); // expected-warning {{will be evaluated}}
|
|
return OK;
|
|
}
|
|
static_assert(side_effects());
|
|
}
|
|
|
|
namespace Union {
|
|
struct Base {
|
|
int y; // expected-note 2{{here}}
|
|
};
|
|
struct A : Base {
|
|
int x;
|
|
int arr[3];
|
|
union { int p, q; };
|
|
};
|
|
union B {
|
|
A a;
|
|
int b;
|
|
};
|
|
constexpr int read_wrong_member() { // expected-error {{never produces a constant}}
|
|
B b = {.b = 1};
|
|
return b.a.x; // expected-note {{read of member 'a' of union with active member 'b'}}
|
|
}
|
|
constexpr int change_member() {
|
|
B b = {.b = 1};
|
|
b.a.x = 1;
|
|
return b.a.x;
|
|
}
|
|
static_assert(change_member() == 1);
|
|
constexpr int change_member_then_read_wrong_member() { // expected-error {{never produces a constant}}
|
|
B b = {.b = 1};
|
|
b.a.x = 1;
|
|
return b.b; // expected-note {{read of member 'b' of union with active member 'a'}}
|
|
}
|
|
constexpr int read_wrong_member_indirect() { // expected-error {{never produces a constant}}
|
|
B b = {.b = 1};
|
|
int *p = &b.a.y;
|
|
return *p; // expected-note {{read of member 'a' of union with active member 'b'}}
|
|
}
|
|
constexpr int read_uninitialized() {
|
|
B b = {.b = 1};
|
|
int *p = &b.a.y;
|
|
b.a.x = 1;
|
|
return *p; // expected-note {{read of uninitialized object}}
|
|
}
|
|
static_assert(read_uninitialized() == 0); // expected-error {{constant}} expected-note {{in call}}
|
|
constexpr void write_wrong_member_indirect() { // expected-error {{never produces a constant}}
|
|
B b = {.b = 1};
|
|
int *p = &b.a.y;
|
|
*p = 1; // expected-note {{assignment to member 'a' of union with active member 'b'}}
|
|
}
|
|
constexpr int write_uninitialized() {
|
|
B b = {.b = 1};
|
|
int *p = &b.a.y;
|
|
b.a.x = 1;
|
|
*p = 1;
|
|
return *p;
|
|
}
|
|
static_assert(write_uninitialized() == 1);
|
|
constexpr int change_member_indirectly() {
|
|
B b = {.b = 1};
|
|
b.a.arr[1] = 1;
|
|
int &r = b.a.y;
|
|
r = 123;
|
|
|
|
b.b = 2;
|
|
b.a.y = 3;
|
|
b.a.arr[2] = 4;
|
|
return b.a.arr[2];
|
|
}
|
|
static_assert(change_member_indirectly() == 4);
|
|
constexpr B return_uninit() {
|
|
B b = {.b = 1};
|
|
b.a.x = 2;
|
|
return b;
|
|
}
|
|
constexpr B uninit = return_uninit(); // expected-error {{constant expression}} expected-note {{subobject 'y' is not initialized}}
|
|
static_assert(return_uninit().a.x == 2);
|
|
constexpr A return_uninit_struct() {
|
|
B b = {.b = 1};
|
|
b.a.x = 2;
|
|
return b.a; // expected-note {{in call to 'A(b.a)'}} expected-note {{subobject 'y' is not initialized}}
|
|
}
|
|
// Note that this is rejected even though return_uninit() is accepted, and
|
|
// return_uninit() copies the same stuff wrapped in a union.
|
|
//
|
|
// Copying a B involves copying the object representation of the union, but
|
|
// copying an A invokes a copy constructor that copies the object
|
|
// elementwise, and reading from b.a.y is undefined.
|
|
static_assert(return_uninit_struct().x == 2); // expected-error {{constant expression}} expected-note {{in call}}
|
|
constexpr B return_init_all() {
|
|
B b = {.b = 1};
|
|
b.a.x = 2;
|
|
b.a.y = 3;
|
|
b.a.arr[0] = 4;
|
|
b.a.arr[1] = 5;
|
|
b.a.arr[2] = 6;
|
|
return b;
|
|
}
|
|
static_assert(return_init_all().a.x == 2);
|
|
static_assert(return_init_all().a.y == 3);
|
|
static_assert(return_init_all().a.arr[0] == 4);
|
|
static_assert(return_init_all().a.arr[1] == 5);
|
|
static_assert(return_init_all().a.arr[2] == 6);
|
|
static_assert(return_init_all().a.p == 7); // expected-error {{}} expected-note {{read of member 'p' of union with no active member}}
|
|
static_assert(return_init_all().a.q == 8); // expected-error {{}} expected-note {{read of member 'q' of union with no active member}}
|
|
constexpr B init_all = return_init_all();
|
|
|
|
constexpr bool test_no_member_change = []{
|
|
union U { char dummy = {}; };
|
|
U u1;
|
|
U u2;
|
|
u1 = u2;
|
|
return true;
|
|
}();
|
|
|
|
struct S1 {
|
|
int n;
|
|
};
|
|
struct S2 : S1 {};
|
|
struct S3 : S2 {};
|
|
void f() {
|
|
S3 s;
|
|
s.n = 0;
|
|
}
|
|
|
|
union ref_member_1 {
|
|
int a;
|
|
int b;
|
|
};
|
|
struct ref_member_2 {
|
|
ref_member_1 &&r;
|
|
};
|
|
union ref_member_3 {
|
|
ref_member_2 a, b;
|
|
};
|
|
constexpr int ref_member_test_1() {
|
|
ref_member_3 r = {.a = {.r = {.a = 1}}};
|
|
r.a.r.b = 2;
|
|
return r.a.r.b;
|
|
}
|
|
static_assert(ref_member_test_1() == 2);
|
|
constexpr int ref_member_test_2() { // expected-error {{never produces a constant}}
|
|
ref_member_3 r = {.a = {.r = {.a = 1}}};
|
|
// FIXME: This note isn't great. The 'read' here is reading the referent of the reference.
|
|
r.b.r.b = 2; // expected-note {{read of member 'b' of union with active member 'a'}}
|
|
return r.b.r.b;
|
|
}
|
|
|
|
namespace PR43762 {
|
|
struct A { int x = 1; constexpr int f() { return 1; } };
|
|
struct B : A { int y = 1; constexpr int g() { return 2; } };
|
|
struct C {
|
|
int x;
|
|
constexpr virtual int f() = 0;
|
|
};
|
|
struct D : C {
|
|
int y;
|
|
constexpr virtual int f() override { return 3; }
|
|
};
|
|
|
|
union U {
|
|
int n;
|
|
B b;
|
|
D d;
|
|
};
|
|
|
|
constexpr int test(int which) {
|
|
U u{.n = 5};
|
|
switch (which) {
|
|
case 0:
|
|
u.b.x = 10; // expected-note {{active member 'n'}}
|
|
return u.b.f();
|
|
case 1:
|
|
u.b.y = 10; // expected-note {{active member 'n'}}
|
|
return u.b.g();
|
|
case 2:
|
|
u.d.x = 10; // expected-note {{active member 'n'}}
|
|
return u.d.f();
|
|
case 3:
|
|
u.d.y = 10; // expected-note {{active member 'n'}}
|
|
return u.d.f();
|
|
}
|
|
}
|
|
|
|
static_assert(test(0)); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(test(1)); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(test(2)); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(test(3)); // expected-error {{}} expected-note {{in call}}
|
|
}
|
|
}
|
|
|
|
namespace TwosComplementShifts {
|
|
using uint32 = __UINT32_TYPE__;
|
|
using int32 = __INT32_TYPE__;
|
|
static_assert(uint32(int32(0x1234) << 16) == 0x12340000);
|
|
static_assert(uint32(int32(0x1234) << 19) == 0x91a00000);
|
|
static_assert(uint32(int32(0x1234) << 20) == 0x23400000);
|
|
static_assert(uint32(int32(0x1234) << 24) == 0x34000000);
|
|
static_assert(uint32(int32(-1) << 31) == 0x80000000);
|
|
|
|
static_assert(-1 >> 1 == -1);
|
|
static_assert(-1 >> 31 == -1);
|
|
static_assert(-2 >> 1 == -1);
|
|
static_assert(-3 >> 1 == -2);
|
|
static_assert(-4 >> 1 == -2);
|
|
}
|
|
|
|
namespace Uninit {
|
|
constexpr int f(bool init) {
|
|
int a;
|
|
if (init)
|
|
a = 1;
|
|
return a; // expected-note {{read of uninitialized object}}
|
|
}
|
|
static_assert(f(true) == 1);
|
|
static_assert(f(false) == 1); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
struct X {
|
|
int n; // expected-note {{declared here}}
|
|
constexpr X(bool init) {
|
|
if (init) n = 123;
|
|
}
|
|
};
|
|
constinit X x1(true);
|
|
constinit X x2(false); // expected-error {{constant initializer}} expected-note {{constinit}} expected-note {{subobject 'n' is not initialized}}
|
|
|
|
struct Y {
|
|
struct Z { int n; }; // expected-note {{here}}
|
|
Z z1;
|
|
Z z2;
|
|
Z z3;
|
|
// OK: the lifetime of z1 (and its members) start before the initializer of
|
|
// z2 runs.
|
|
constexpr Y() : z2{ (z1.n = 1, z1.n + 1) } { z3.n = 3; }
|
|
// Not OK: z3 is not in its lifetime when the initializer of z2 runs.
|
|
constexpr Y(int) : z2{
|
|
(z3.n = 1, // expected-note {{assignment to object outside its lifetime}}
|
|
z3.n + 1) // expected-warning {{uninitialized}}
|
|
} { z1.n = 3; }
|
|
constexpr Y(int, int) : z2{} {}
|
|
};
|
|
// FIXME: This is working around clang not implementing DR2026. With that
|
|
// fixed, we should be able to test this without the injected copy.
|
|
constexpr Y copy(Y y) { return y; } // expected-note {{in call to 'Y(y)'}} expected-note {{subobject 'n' is not initialized}}
|
|
constexpr Y y1 = copy(Y());
|
|
static_assert(y1.z1.n == 1 && y1.z2.n == 2 && y1.z3.n == 3);
|
|
|
|
constexpr Y y2 = copy(Y(0)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
static_assert(Y(0,0).z2.n == 0);
|
|
static_assert(Y(0,0).z1.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}}
|
|
static_assert(Y(0,0).z3.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}}
|
|
|
|
static_assert(copy(Y(0,0)).z2.n == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
constexpr unsigned char not_even_unsigned_char() {
|
|
unsigned char c;
|
|
return c; // expected-note {{read of uninitialized object}}
|
|
}
|
|
constexpr unsigned char x = not_even_unsigned_char(); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
constexpr int switch_var(int n) {
|
|
switch (n) {
|
|
case 1:
|
|
int a;
|
|
a = n;
|
|
return a;
|
|
|
|
case 2:
|
|
a = n;
|
|
return a;
|
|
}
|
|
}
|
|
constexpr int s1 = switch_var(1);
|
|
constexpr int s2 = switch_var(2);
|
|
static_assert(s1 == 1 && s2 == 2);
|
|
|
|
constexpr bool switch_into_init_stmt() {
|
|
switch (1) {
|
|
if (int n; false) {
|
|
for (int m; false;) {
|
|
case 1:
|
|
n = m = 1;
|
|
return n == 1 && m == 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static_assert(switch_into_init_stmt());
|
|
}
|
|
|
|
namespace dtor {
|
|
void lifetime_extension() {
|
|
struct X { constexpr ~X() {} };
|
|
X &&a = X();
|
|
}
|
|
|
|
template<typename T> constexpr T &&ref(T &&t) { return (T&&)t; }
|
|
|
|
struct Buf {
|
|
char buf[64];
|
|
int n = 0;
|
|
constexpr void operator+=(char c) { buf[n++] = c; }
|
|
constexpr bool operator==(const char *str) const {
|
|
return str[n] == 0 && __builtin_memcmp(str, buf, n) == 0;
|
|
}
|
|
constexpr bool operator!=(const char *str) const { return !operator==(str); }
|
|
};
|
|
|
|
struct A {
|
|
constexpr A(Buf &buf, char c) : buf(buf), c(c) { buf += c; }
|
|
constexpr ~A() { buf += c; }
|
|
constexpr operator bool() const { return true; }
|
|
Buf &buf;
|
|
char c;
|
|
};
|
|
|
|
constexpr bool dtor_calls_dtor() {
|
|
union U {
|
|
constexpr U(Buf &buf) : u(buf, 'u') { buf += 'U'; }
|
|
constexpr ~U() { u.buf += 'U'; }
|
|
A u, v;
|
|
};
|
|
|
|
struct B : A {
|
|
A c, &&d, e;
|
|
union {
|
|
A f;
|
|
};
|
|
U u;
|
|
constexpr B(Buf &buf)
|
|
: A(buf, 'a'), c(buf, 'c'), d(ref(A(buf, 'd'))), e(A(buf, 'e')), f(buf, 'f'), u(buf) {
|
|
buf += 'b';
|
|
}
|
|
constexpr ~B() {
|
|
buf += 'b';
|
|
}
|
|
};
|
|
|
|
Buf buf;
|
|
{
|
|
B b(buf);
|
|
if (buf != "acddefuUb")
|
|
return false;
|
|
}
|
|
if (buf != "acddefuUbbUeca")
|
|
return false;
|
|
return true;
|
|
}
|
|
static_assert(dtor_calls_dtor());
|
|
|
|
constexpr void abnormal_termination(Buf &buf) {
|
|
struct Indestructible {
|
|
constexpr ~Indestructible(); // not defined
|
|
};
|
|
|
|
A a(buf, 'a');
|
|
A(buf, 'b');
|
|
int n = 0;
|
|
for (A &&c = A(buf, 'c'); A d = A(buf, 'd'); A(buf, 'e')) {
|
|
switch (A f(buf, 'f'); A g = A(buf, 'g')) { // expected-warning {{boolean}}
|
|
case false: {
|
|
A x(buf, 'x');
|
|
}
|
|
|
|
case true: {
|
|
A h(buf, 'h');
|
|
switch (n++) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
continue;
|
|
case 2:
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
Indestructible indest;
|
|
}
|
|
|
|
A j = (A(buf, 'i'), A(buf, 'j'));
|
|
}
|
|
}
|
|
|
|
constexpr bool check_abnormal_termination() {
|
|
Buf buf = {};
|
|
abnormal_termination(buf);
|
|
return buf ==
|
|
"abbc"
|
|
"dfgh" /*break*/ "hgfijijeed"
|
|
"dfgh" /*continue*/ "hgfeed"
|
|
"dfgh" /*return*/ "hgfd"
|
|
"ca";
|
|
}
|
|
static_assert(check_abnormal_termination());
|
|
|
|
constexpr bool run_dtors_on_array_filler() {
|
|
struct S {
|
|
int times_destroyed = 0;
|
|
constexpr ~S() { if (++times_destroyed != 1) throw "oops"; }
|
|
};
|
|
S s[3];
|
|
return true;
|
|
}
|
|
static_assert(run_dtors_on_array_filler());
|
|
|
|
// Ensure that we can handle temporary cleanups for array temporaries.
|
|
struct ArrElem { constexpr ~ArrElem() {} };
|
|
using Arr = ArrElem[3];
|
|
static_assert(((void)Arr{}, true));
|
|
}
|
|
|
|
namespace dynamic_alloc {
|
|
constexpr int *p = // expected-error {{constant}} expected-note {{pointer to heap-allocated object is not a constant expression}}
|
|
new int; // expected-note {{heap allocation performed here}}
|
|
|
|
constexpr int f(int n) {
|
|
int *p = new int[n];
|
|
for (int i = 0; i != n; ++i) {
|
|
p[i] = i;
|
|
}
|
|
int k = 0;
|
|
for (int i = 0; i != n; ++i) {
|
|
k += p[i];
|
|
}
|
|
delete[] p;
|
|
return k;
|
|
}
|
|
static_assert(f(123) == 123 * 122 / 2);
|
|
|
|
constexpr bool nvdtor() { // expected-error {{never produces a constant expression}}
|
|
struct S {
|
|
constexpr ~S() {}
|
|
};
|
|
struct T : S {};
|
|
delete (S*)new T; // expected-note {{delete of object with dynamic type 'T' through pointer to base class type 'S' with non-virtual destructor}}
|
|
return true;
|
|
}
|
|
|
|
constexpr int vdtor_1() {
|
|
int a;
|
|
struct S {
|
|
constexpr S(int *p) : p(p) {}
|
|
constexpr virtual ~S() { *p = 1; }
|
|
int *p;
|
|
};
|
|
struct T : S {
|
|
// implicit destructor defined eagerly because it is constexpr and virtual
|
|
using S::S;
|
|
};
|
|
delete (S*)new T(&a);
|
|
return a;
|
|
}
|
|
static_assert(vdtor_1() == 1);
|
|
|
|
constexpr int vdtor_2() {
|
|
int a = 0;
|
|
struct S { constexpr virtual ~S() {} };
|
|
struct T : S {
|
|
constexpr T(int *p) : p(p) {}
|
|
constexpr ~T() { ++*p; }
|
|
int *p;
|
|
};
|
|
S *p = new T{&a};
|
|
delete p;
|
|
return a;
|
|
}
|
|
static_assert(vdtor_2() == 1);
|
|
|
|
constexpr int vdtor_3(int mode) {
|
|
int a = 0;
|
|
struct S { constexpr virtual ~S() {} };
|
|
struct T : S {
|
|
constexpr T(int *p) : p(p) {}
|
|
constexpr ~T() { ++*p; }
|
|
int *p;
|
|
};
|
|
S *p = new T[3]{&a, &a, &a}; // expected-note 2{{heap allocation}}
|
|
switch (mode) {
|
|
case 0:
|
|
delete p; // expected-note {{non-array delete used to delete pointer to array object of type 'T[3]'}}
|
|
break;
|
|
case 1:
|
|
// FIXME: This diagnosic isn't great; we should mention the cast to S*
|
|
// somewhere in here.
|
|
delete[] p; // expected-note {{delete of pointer to subobject '&{*new T[3]#0}[0]'}}
|
|
break;
|
|
case 2:
|
|
delete (T*)p; // expected-note {{non-array delete used to delete pointer to array object of type 'T[3]'}}
|
|
break;
|
|
case 3:
|
|
delete[] (T*)p;
|
|
break;
|
|
}
|
|
return a;
|
|
}
|
|
static_assert(vdtor_3(0) == 3); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(vdtor_3(1) == 3); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(vdtor_3(2) == 3); // expected-error {{}} expected-note {{in call}}
|
|
static_assert(vdtor_3(3) == 3);
|
|
|
|
constexpr void delete_mismatch() { // expected-error {{never produces a constant expression}}
|
|
delete[] // expected-note {{array delete used to delete pointer to non-array object of type 'int'}}
|
|
new int; // expected-note {{allocation}}
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr T dynarray(int elems, int i) {
|
|
T *p;
|
|
if constexpr (sizeof(T) == 1)
|
|
p = new T[elems]{"fox"}; // expected-note {{evaluated array bound 3 is too small to hold 4 explicitly initialized elements}}
|
|
else
|
|
p = new T[elems]{1, 2, 3}; // expected-note {{evaluated array bound 2 is too small to hold 3 explicitly initialized elements}}
|
|
T n = p[i]; // expected-note 4{{past-the-end}}
|
|
delete [] p;
|
|
return n;
|
|
}
|
|
static_assert(dynarray<int>(4, 0) == 1);
|
|
static_assert(dynarray<int>(4, 1) == 2);
|
|
static_assert(dynarray<int>(4, 2) == 3);
|
|
static_assert(dynarray<int>(4, 3) == 0);
|
|
static_assert(dynarray<int>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(dynarray<int>(3, 2) == 3);
|
|
static_assert(dynarray<int>(3, 3) == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(dynarray<int>(2, 1) == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(dynarray<char>(5, 0) == 'f');
|
|
static_assert(dynarray<char>(5, 1) == 'o');
|
|
static_assert(dynarray<char>(5, 2) == 'x');
|
|
static_assert(dynarray<char>(5, 3) == 0); // (from string)
|
|
static_assert(dynarray<char>(5, 4) == 0); // (from filler)
|
|
static_assert(dynarray<char>(5, 5) == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(dynarray<char>(4, 0) == 'f');
|
|
static_assert(dynarray<char>(4, 1) == 'o');
|
|
static_assert(dynarray<char>(4, 2) == 'x');
|
|
static_assert(dynarray<char>(4, 3) == 0);
|
|
static_assert(dynarray<char>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(dynarray<char>(3, 2) == 'x'); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
constexpr bool run_dtors_on_array_filler() {
|
|
struct S {
|
|
int times_destroyed = 0;
|
|
constexpr ~S() { if (++times_destroyed != 1) throw "oops"; }
|
|
};
|
|
delete[] new S[3];
|
|
return true;
|
|
}
|
|
static_assert(run_dtors_on_array_filler());
|
|
|
|
constexpr bool erroneous_array_bound(long long n) {
|
|
delete[] new int[n]; // expected-note {{array bound -1 is negative}} expected-note {{array bound 4611686018427387904 is too large}}
|
|
return true;
|
|
}
|
|
static_assert(erroneous_array_bound(3));
|
|
static_assert(erroneous_array_bound(0));
|
|
static_assert(erroneous_array_bound(-1)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(erroneous_array_bound(1LL << 62)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
constexpr bool erroneous_array_bound_nothrow(long long n) {
|
|
int *p = new (std::nothrow) int[n];
|
|
bool result = p != 0;
|
|
delete[] p;
|
|
return result;
|
|
}
|
|
static_assert(erroneous_array_bound_nothrow(3));
|
|
static_assert(erroneous_array_bound_nothrow(0));
|
|
static_assert(!erroneous_array_bound_nothrow(-1));
|
|
static_assert(!erroneous_array_bound_nothrow(1LL << 62));
|
|
|
|
constexpr bool evaluate_nothrow_arg() {
|
|
bool ok = false;
|
|
delete new ((ok = true, std::nothrow)) int;
|
|
return ok;
|
|
}
|
|
static_assert(evaluate_nothrow_arg());
|
|
|
|
constexpr void double_delete() { // expected-error {{never produces a constant expression}}
|
|
int *p = new int;
|
|
delete p;
|
|
delete p; // expected-note {{delete of pointer that has already been deleted}}
|
|
}
|
|
constexpr bool super_secret_double_delete() {
|
|
struct A {
|
|
constexpr ~A() { delete this; } // expected-note {{destruction of object that is already being destroyed}} expected-note {{in call}}
|
|
};
|
|
delete new A; // expected-note {{in call}}
|
|
return true;
|
|
}
|
|
static_assert(super_secret_double_delete()); // expected-error {{constant expression}} expected-note {{in call}}
|
|
|
|
constexpr void use_after_free() { // expected-error {{never produces a constant expression}}
|
|
int *p = new int;
|
|
delete p;
|
|
*p = 1; // expected-note {{read of heap allocated object that has been deleted}}
|
|
}
|
|
constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}}
|
|
struct X { constexpr void f() {} };
|
|
X *p = new X;
|
|
delete p;
|
|
p->f(); // expected-note {{member call on heap allocated object that has been deleted}}
|
|
}
|
|
|
|
template<typename T> struct X {
|
|
std::size_t n;
|
|
char *p;
|
|
void dependent();
|
|
};
|
|
template<typename T> void X<T>::dependent() {
|
|
char *p;
|
|
// Ensure that we don't try to evaluate these for overflow and crash. These
|
|
// are all value-dependent expressions.
|
|
p = new char[n];
|
|
p = new ((std::align_val_t)n) char[n];
|
|
p = new char(n);
|
|
}
|
|
|
|
namespace PR47143 {
|
|
constexpr char *f(int n) {
|
|
return new char[n]();
|
|
}
|
|
const char *p = f(3);
|
|
constexpr bool test() {
|
|
char *p = f(3);
|
|
bool result = !p[0] && !p[1] && !p[2];
|
|
delete [] p;
|
|
return result;
|
|
}
|
|
static_assert(test());
|
|
}
|
|
}
|
|
|
|
struct placement_new_arg {};
|
|
void *operator new(std::size_t, placement_new_arg);
|
|
void operator delete(void*, placement_new_arg);
|
|
|
|
namespace placement_new_delete {
|
|
struct ClassSpecificNew {
|
|
void *operator new(std::size_t);
|
|
};
|
|
struct ClassSpecificDelete {
|
|
void operator delete(void*);
|
|
};
|
|
struct DestroyingDelete {
|
|
void operator delete(DestroyingDelete*, std::destroying_delete_t);
|
|
};
|
|
struct alignas(64) Overaligned {};
|
|
|
|
constexpr bool ok() {
|
|
delete new Overaligned;
|
|
delete ::new ClassSpecificNew;
|
|
::delete new ClassSpecificDelete;
|
|
::delete new DestroyingDelete;
|
|
return true;
|
|
}
|
|
static_assert(ok());
|
|
|
|
constexpr bool bad(int which) {
|
|
switch (which) {
|
|
case 0:
|
|
delete new (placement_new_arg{}) int; // expected-note {{this placement new expression is not supported in constant expressions}}
|
|
break;
|
|
|
|
case 1:
|
|
delete new ClassSpecificNew; // expected-note {{call to class-specific 'operator new'}}
|
|
break;
|
|
|
|
case 2:
|
|
delete new ClassSpecificDelete; // expected-note {{call to class-specific 'operator delete'}}
|
|
break;
|
|
|
|
case 3:
|
|
delete new DestroyingDelete; // expected-note {{call to class-specific 'operator delete'}}
|
|
break;
|
|
|
|
case 4:
|
|
// FIXME: This technically follows the standard's rules, but it seems
|
|
// unreasonable to expect implementations to support this.
|
|
delete new (std::align_val_t{64}) Overaligned; // expected-note {{this placement new expression is not supported in constant expressions}}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static_assert(bad(0)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(bad(1)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(bad(2)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(bad(3)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(bad(4)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
}
|
|
|
|
namespace delete_random_things {
|
|
static_assert((delete new int, true));
|
|
static_assert((delete (int*)0, true));
|
|
int n; // expected-note {{declared here}}
|
|
static_assert((delete &n, true)); // expected-error {{}} expected-note {{delete of pointer '&n' that does not point to a heap-allocated object}}
|
|
struct A { int n; };
|
|
static_assert((delete &(new A)->n, true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new A#0}.n'}}
|
|
static_assert((delete (new int + 1), true)); // expected-error {{}} expected-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}}
|
|
static_assert((delete[] (new int[3] + 1), true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new int[3]#0}[1]'}}
|
|
static_assert((delete &(int&)(int&&)0, true)); // expected-error {{}} expected-note {{delete of pointer '&0' that does not point to a heap-allocated object}} expected-note {{temporary created here}}
|
|
}
|
|
|
|
namespace value_dependent_delete {
|
|
template<typename T> void f(T *p) {
|
|
int arr[(delete p, 0)];
|
|
}
|
|
}
|
|
|
|
namespace memory_leaks {
|
|
static_assert(*new bool(true)); // expected-error {{}} expected-note {{allocation performed here was not deallocated}}
|
|
|
|
constexpr bool *f() { return new bool(true); } // expected-note {{allocation performed here was not deallocated}}
|
|
static_assert(*f()); // expected-error {{}}
|
|
|
|
struct UP {
|
|
bool *p;
|
|
constexpr ~UP() { delete p; }
|
|
constexpr bool &operator*() { return *p; }
|
|
};
|
|
constexpr UP g() { return {new bool(true)}; }
|
|
static_assert(*g()); // ok
|
|
|
|
constexpr bool h(UP p) { return *p; }
|
|
static_assert(h({new bool(true)})); // ok
|
|
}
|
|
|
|
constexpr void *operator new(std::size_t, void *p) { return p; }
|
|
namespace std {
|
|
template<typename T> constexpr T *construct(T *p) { return new (p) T; }
|
|
template<typename T> constexpr void destroy(T *p) { p->~T(); }
|
|
}
|
|
|
|
namespace dtor_call {
|
|
struct A { int n; };
|
|
constexpr void f() { // expected-error {{never produces a constant expression}}
|
|
A a; // expected-note {{destroying object 'a' whose lifetime has already ended}}
|
|
a.~A();
|
|
}
|
|
union U { A a; };
|
|
constexpr void g() {
|
|
U u;
|
|
u.a.n = 3;
|
|
u.a.~A();
|
|
// There's now effectively no active union member, but we model it as if
|
|
// 'a' is still the active union member (but its lifetime has ended).
|
|
u.a.n = 4; // Start lifetime of 'a' again.
|
|
u.a.~A();
|
|
}
|
|
static_assert((g(), true));
|
|
|
|
constexpr bool pseudo(bool read, bool recreate) {
|
|
using T = bool;
|
|
bool b = false; // expected-note {{lifetime has already ended}}
|
|
// This evaluates the store to 'b'...
|
|
(b = true).~T();
|
|
// ... and ends the lifetime of the object.
|
|
return (read
|
|
? b // expected-note {{read of object outside its lifetime}}
|
|
: true) +
|
|
(recreate
|
|
? (std::construct(&b), true)
|
|
: true);
|
|
}
|
|
static_assert(pseudo(false, false)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(pseudo(true, false)); // expected-error {{constant expression}} expected-note {{in call}}
|
|
static_assert(pseudo(false, true));
|
|
|
|
constexpr void use_after_destroy() {
|
|
A a;
|
|
a.~A();
|
|
A b = a; // expected-note {{in call}} expected-note {{read of object outside its lifetime}}
|
|
}
|
|
static_assert((use_after_destroy(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void double_destroy() {
|
|
A a;
|
|
a.~A();
|
|
a.~A(); // expected-note {{destruction of object outside its lifetime}}
|
|
}
|
|
static_assert((double_destroy(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
struct X { char *p; constexpr ~X() { *p++ = 'X'; } };
|
|
struct Y : X { int y; virtual constexpr ~Y() { *p++ = 'Y'; } };
|
|
struct Z : Y { int z; constexpr ~Z() override { *p++ = 'Z'; } };
|
|
union VU {
|
|
constexpr VU() : z() {}
|
|
constexpr ~VU() {}
|
|
Z z;
|
|
};
|
|
|
|
constexpr bool virt_dtor(int mode, const char *expected) {
|
|
char buff[4] = {};
|
|
VU vu;
|
|
vu.z.p = buff;
|
|
switch (mode) {
|
|
case 0:
|
|
vu.z.~Z();
|
|
break;
|
|
case 1:
|
|
((Y&)vu.z).~Y();
|
|
break;
|
|
case 2:
|
|
((X&)vu.z).~X();
|
|
break;
|
|
case 3:
|
|
((Y&)vu.z).Y::~Y();
|
|
vu.z.z = 1; // ok, still have a Z (with no Y base class!)
|
|
break;
|
|
case 4:
|
|
((X&)vu.z).X::~X();
|
|
vu.z.y = 1; // ok, still have a Z and a Y (with no X base class!)
|
|
break;
|
|
}
|
|
return __builtin_strcmp(expected, buff) == 0;
|
|
}
|
|
static_assert(virt_dtor(0, "ZYX"));
|
|
static_assert(virt_dtor(1, "ZYX"));
|
|
static_assert(virt_dtor(2, "X"));
|
|
static_assert(virt_dtor(3, "YX"));
|
|
static_assert(virt_dtor(4, "X"));
|
|
|
|
constexpr bool virt_delete(bool global) {
|
|
struct A {
|
|
virtual constexpr ~A() {}
|
|
};
|
|
struct B : A {
|
|
void operator delete(void *);
|
|
constexpr ~B() {}
|
|
};
|
|
|
|
A *p = new B;
|
|
if (global)
|
|
::delete p;
|
|
else
|
|
delete p; // expected-note {{call to class-specific 'operator delete'}}
|
|
return true;
|
|
}
|
|
static_assert(virt_delete(true));
|
|
static_assert(virt_delete(false)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void use_after_virt_destroy() {
|
|
char buff[4] = {};
|
|
VU vu;
|
|
vu.z.p = buff;
|
|
((Y&)vu.z).~Y();
|
|
((Z&)vu.z).z = 1; // expected-note {{assignment to object outside its lifetime}}
|
|
}
|
|
static_assert((use_after_virt_destroy(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void destroy_after_lifetime() {
|
|
A *p;
|
|
{
|
|
A a;
|
|
p = &a;
|
|
}
|
|
p->~A(); // expected-note {{destruction of object outside its lifetime}}
|
|
}
|
|
static_assert((destroy_after_lifetime(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void destroy_after_lifetime2() {
|
|
A *p = []{ A a; return &a; }(); // expected-warning {{}} expected-note {{declared here}}
|
|
p->~A(); // expected-note {{destruction of variable whose lifetime has ended}}
|
|
}
|
|
static_assert((destroy_after_lifetime2(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void destroy_after_lifetime3() {
|
|
A *p = []{ return &(A&)(A&&)A(); }(); // expected-warning {{}} expected-note {{temporary created here}}
|
|
p->~A(); // expected-note {{destruction of temporary whose lifetime has ended}}
|
|
}
|
|
static_assert((destroy_after_lifetime3(), true)); // expected-error {{}} expected-note {{in call}}
|
|
|
|
constexpr void destroy_after_lifetime4() { // expected-error {{never produces a constant expression}}
|
|
A *p = new A;
|
|
delete p;
|
|
p->~A(); // expected-note {{destruction of heap allocated object that has been deleted}}
|
|
}
|
|
|
|
struct Extern { constexpr ~Extern() {} } extern e;
|
|
constexpr void destroy_extern() { // expected-error {{never produces a constant expression}}
|
|
e.~Extern(); // expected-note {{cannot modify an object that is visible outside}}
|
|
}
|
|
|
|
constexpr A &&a_ref = A(); // expected-note {{temporary created here}}
|
|
constexpr void destroy_extern_2() { // expected-error {{never produces a constant expression}}
|
|
a_ref.~A(); // expected-note {{destruction of temporary is not allowed in a constant expression outside the expression that created the temporary}}
|
|
}
|
|
|
|
struct S {
|
|
constexpr S() { n = 1; }
|
|
constexpr ~S() { n = 0; }
|
|
int n;
|
|
};
|
|
constexpr void destroy_volatile() {
|
|
volatile S s;
|
|
}
|
|
static_assert((destroy_volatile(), true)); // ok, not volatile during construction and destruction
|
|
|
|
constexpr void destroy_null() { // expected-error {{never produces a constant expression}}
|
|
((A*)nullptr)->~A(); // expected-note {{destruction of dereferenced null pointer}}
|
|
}
|
|
|
|
constexpr void destroy_past_end() { // expected-error {{never produces a constant expression}}
|
|
A a;
|
|
(&a+1)->~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}}
|
|
}
|
|
|
|
constexpr void destroy_past_end_array() { // expected-error {{never produces a constant expression}}
|
|
A a[2];
|
|
a[2].~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}}
|
|
}
|
|
|
|
union As {
|
|
A a, b;
|
|
};
|
|
|
|
constexpr void destroy_no_active() { // expected-error {{never produces a constant expression}}
|
|
As as;
|
|
as.b.~A(); // expected-note {{destruction of member 'b' of union with no active member}}
|
|
}
|
|
|
|
constexpr void destroy_inactive() { // expected-error {{never produces a constant expression}}
|
|
As as;
|
|
as.a.n = 1;
|
|
as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}}
|
|
}
|
|
|
|
constexpr void destroy_no_active_2() { // expected-error {{never produces a constant expression}}
|
|
As as;
|
|
as.a.n = 1;
|
|
as.a.~A();
|
|
// FIXME: This diagnostic is wrong; the union has no active member now.
|
|
as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}}
|
|
}
|
|
|
|
constexpr void destroy_pointer() {
|
|
using T = int*;
|
|
T p;
|
|
// We used to think this was an -> member access because its left-hand side
|
|
// is a pointer. Ensure we don't crash.
|
|
p.~T();
|
|
// Put a T back so we can destroy it again.
|
|
std::construct(&p);
|
|
}
|
|
static_assert((destroy_pointer(), true));
|
|
}
|
|
|
|
namespace temp_dtor {
|
|
void f();
|
|
struct A {
|
|
bool b;
|
|
constexpr ~A() { if (b) f(); }
|
|
};
|
|
|
|
// We can't accept either of these unless we start actually registering the
|
|
// destructors of the A temporaries to run on shutdown. It's unclear what the
|
|
// intended standard behavior is so we reject this for now.
|
|
constexpr A &&a = A{false}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}}
|
|
void f() { a.b = true; }
|
|
|
|
constexpr A &&b = A{true}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}}
|
|
|
|
// FIXME: We could in prinicple accept this.
|
|
constexpr const A &c = A{false}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}}
|
|
}
|
|
|
|
namespace value_dependent_init {
|
|
struct A {
|
|
constexpr ~A() {}
|
|
};
|
|
template<typename T> void f() {
|
|
A a = T();
|
|
}
|
|
}
|
|
|
|
namespace mutable_subobjects {
|
|
struct A {
|
|
int m;
|
|
mutable int n; // expected-note 2{{here}}
|
|
constexpr int f() const { return m; }
|
|
constexpr int g() const { return n; } // expected-note {{mutable}}
|
|
};
|
|
|
|
constexpr A a = {1, 2};
|
|
static_assert(a.f() == 1); // OK (PR44958)
|
|
static_assert(a.g() == 2); // expected-error {{constant}} expected-note {{in call}}
|
|
|
|
constexpr A b = a; // expected-error {{constant}} expected-note {{read of mutable member 'n'}} expected-note {{in call}}
|
|
|
|
auto &ti1 = typeid(a);
|
|
auto &ti2 = typeid(a.m);
|
|
auto &ti3 = typeid(a.n);
|
|
|
|
constexpr void destroy1() { // expected-error {{constexpr}}
|
|
a.~A(); // expected-note {{cannot modify an object that is visible outside}}
|
|
}
|
|
using T = int;
|
|
constexpr void destroy2() { // expected-error {{constexpr}}
|
|
a.m.~T(); // expected-note {{cannot modify an object that is visible outside}}
|
|
}
|
|
constexpr void destroy3() { // expected-error {{constexpr}}
|
|
a.n.~T(); // expected-note {{cannot modify an object that is visible outside}}
|
|
}
|
|
|
|
struct X {
|
|
mutable int n = 0;
|
|
virtual constexpr ~X() {}
|
|
};
|
|
struct Y : X {
|
|
};
|
|
constexpr Y y;
|
|
constexpr const X *p = &y;
|
|
constexpr const Y *q = dynamic_cast<const Y*>(p);
|
|
|
|
// FIXME: It's unclear whether this should be accepted. The dynamic_cast is
|
|
// undefined after 'z.y.~Y()`, for example. We essentially assume that all
|
|
// objects that the evaluator can reach have unbounded lifetimes. (We make
|
|
// the same assumption when evaluating member function calls.)
|
|
struct Z {
|
|
mutable Y y;
|
|
};
|
|
constexpr Z z;
|
|
constexpr const X *pz = &z.y;
|
|
constexpr const Y *qz = dynamic_cast<const Y*>(pz);
|
|
auto &zti = typeid(z.y);
|
|
static_assert(&zti == &typeid(Y));
|
|
}
|
|
|
|
namespace PR45133 {
|
|
struct A { long x; };
|
|
|
|
union U;
|
|
constexpr A foo(U *up);
|
|
|
|
union U {
|
|
A a = foo(this); // expected-note {{in call to 'foo(&u)'}}
|
|
int y;
|
|
};
|
|
|
|
constexpr A foo(U *up) {
|
|
up->y = 11; // expected-note {{assignment would change active union member during the initialization of a different member}}
|
|
return {42};
|
|
}
|
|
|
|
constinit U u = {}; // expected-error {{constant init}} expected-note {{constinit}}
|
|
|
|
template<int> struct X {};
|
|
|
|
union V {
|
|
int a, b;
|
|
constexpr V(X<0>) : a(a = 1) {} // ok
|
|
constexpr V(X<1>) : a(b = 1) {} // expected-note {{assignment would change active union member during the initialization of a different member}}
|
|
constexpr V(X<2>) : a() { b = 1; } // ok
|
|
// This case (changing the active member then changing it back) is debatable,
|
|
// but it seems appropriate to reject.
|
|
constexpr V(X<3>) : a((b = 1, a = 1)) {} // expected-note {{assignment would change active union member during the initialization of a different member}}
|
|
};
|
|
constinit V v0 = X<0>();
|
|
constinit V v1 = X<1>(); // expected-error {{constant init}} expected-note {{constinit}} expected-note {{in call}}
|
|
constinit V v2 = X<2>();
|
|
constinit V v3 = X<3>(); // expected-error {{constant init}} expected-note {{constinit}} expected-note {{in call}}
|
|
}
|
|
|
|
namespace PR45350 {
|
|
int q;
|
|
struct V { int n; int *p = &n; constexpr ~V() { *p = *p * 10 + n; }};
|
|
constexpr int f(int n) {
|
|
int k = 0;
|
|
V *p = new V[n];
|
|
for (int i = 0; i != n; ++i) {
|
|
if (p[i].p != &p[i].n) return -1;
|
|
p[i].n = i;
|
|
p[i].p = &k;
|
|
}
|
|
delete[] p;
|
|
return k;
|
|
}
|
|
// [expr.delete]p6:
|
|
// In the case of an array, the elements will be destroyed in order of
|
|
// decreasing address
|
|
static_assert(f(6) == 543210);
|
|
}
|
|
|
|
namespace PR47805 {
|
|
struct A {
|
|
bool bad = true;
|
|
constexpr ~A() { if (bad) throw; }
|
|
};
|
|
constexpr bool f(A a) { a.bad = false; return true; }
|
|
constexpr bool b = f(A());
|
|
|
|
struct B { B *p = this; };
|
|
constexpr bool g(B b) { return &b == b.p; }
|
|
static_assert(g({}));
|
|
}
|
|
|
|
constexpr bool destroy_at_test() {
|
|
int n = 0;
|
|
std::destroy(&n);
|
|
std::construct(&n);
|
|
return true;
|
|
}
|
|
static_assert(destroy_at_test());
|
|
|
|
namespace PR48582 {
|
|
struct S {
|
|
void *p = this;
|
|
constexpr S() {}
|
|
constexpr S(const S&) {}
|
|
};
|
|
constexpr bool b = [a = S(), b = S()] { return a.p == b.p; }();
|
|
static_assert(!b);
|
|
}
|
|
|
|
namespace PR45879 {
|
|
struct A { int n; };
|
|
struct B { A a; };
|
|
constexpr A a = (A() = B().a);
|
|
|
|
union C {
|
|
int n;
|
|
A a;
|
|
};
|
|
|
|
constexpr bool f() {
|
|
C c = {.n = 1};
|
|
c.a = B{2}.a;
|
|
return c.a.n == 2;
|
|
}
|
|
static_assert(f());
|
|
|
|
// Only syntactic assignments change the active union member.
|
|
constexpr bool g() { // expected-error {{never produces a constant expression}}
|
|
C c = {.n = 1};
|
|
c.a.operator=(B{2}.a); // expected-note 2{{member call on member 'a' of union with active member 'n' is not allowed in a constant expression}}
|
|
return c.a.n == 2;
|
|
}
|
|
static_assert(g()); // expected-error {{constant expression}} expected-note {{in call}}
|
|
}
|
|
|
|
namespace GH57431 {
|
|
class B {
|
|
virtual int constexpr f() = 0;
|
|
};
|
|
|
|
class D : B {
|
|
virtual int constexpr f() = default; // expected-error {{only special member functions and comparison operators may be defaulted}}
|
|
};
|
|
}
|
|
|
|
namespace GH57516 {
|
|
class B{
|
|
virtual constexpr ~B() = 0; // expected-note {{overridden virtual function is here}}
|
|
};
|
|
|
|
class D : B{}; // expected-error {{deleted function '~D' cannot override a non-deleted function}}
|
|
// expected-note@-1 {{destructor of 'D' is implicitly deleted because base class 'B' has an inaccessible destructor}}
|
|
}
|
|
|
|
namespace GH67317 {
|
|
constexpr unsigned char a = // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} \
|
|
// expected-note {{subobject of type 'const unsigned char' is not initialized}}
|
|
__builtin_bit_cast(unsigned char, *new char[3][1]);
|
|
};
|
|
|
|
namespace GH150705 {
|
|
struct A { };
|
|
struct B : A { };
|
|
struct C : A {
|
|
constexpr virtual int foo() const { return 0; }
|
|
};
|
|
constexpr auto p = &C::foo;
|
|
constexpr auto q = static_cast<int (A::*)() const>(p);
|
|
constexpr B b;
|
|
constexpr const A& a = b;
|
|
constexpr auto x = (a.*q)(); // expected-error {{constant expression}}
|
|
}
|