[clang] [clang][bytecode] Add lifetime information for primitive array elements (PR #173333)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 20 01:05:44 PST 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/173333
>From 7aaf48cb45ef126d35e74f8f530733596d255787 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sat, 13 Dec 2025 19:22:50 +0100
Subject: [PATCH] asdf
---
clang/lib/AST/ByteCode/Descriptor.h | 1 -
clang/lib/AST/ByteCode/InitMap.cpp | 29 ++++-
clang/lib/AST/ByteCode/InitMap.h | 38 ++++--
clang/lib/AST/ByteCode/Interp.cpp | 39 ++++--
clang/lib/AST/ByteCode/Interp.h | 33 +----
clang/lib/AST/ByteCode/Pointer.cpp | 71 ++++++++++-
clang/lib/AST/ByteCode/Pointer.h | 45 ++++---
clang/test/AST/ByteCode/builtin-functions.cpp | 10 +-
clang/test/AST/ByteCode/cxx23.cpp | 33 +++++
clang/test/AST/ByteCode/lifetimes26.cpp | 118 ++++++++++++++++--
10 files changed, 331 insertions(+), 86 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Descriptor.h b/clang/lib/AST/ByteCode/Descriptor.h
index 5bf550ffe1172..8338585a1741b 100644
--- a/clang/lib/AST/ByteCode/Descriptor.h
+++ b/clang/lib/AST/ByteCode/Descriptor.h
@@ -276,7 +276,6 @@ struct Descriptor final {
void dump(llvm::raw_ostream &OS) const;
void dumpFull(unsigned Offset = 0, unsigned Indent = 0) const;
};
-
} // namespace interp
} // namespace clang
diff --git a/clang/lib/AST/ByteCode/InitMap.cpp b/clang/lib/AST/ByteCode/InitMap.cpp
index ae841dd6df189..2fcb61d7b1b00 100644
--- a/clang/lib/AST/ByteCode/InitMap.cpp
+++ b/clang/lib/AST/ByteCode/InitMap.cpp
@@ -10,9 +10,6 @@
using namespace clang;
using namespace clang::interp;
-InitMap::InitMap(unsigned N)
- : UninitFields(N), Data(std::make_unique<T[]>(numFields(N))) {}
-
bool InitMap::initializeElement(unsigned I) {
unsigned Bucket = I / PER_FIELD;
T Mask = T(1) << (I % PER_FIELD);
@@ -29,3 +26,29 @@ bool InitMap::isElementInitialized(unsigned I) const {
unsigned Bucket = I / PER_FIELD;
return data()[Bucket] & (T(1) << (I % PER_FIELD));
}
+
+// Values in the second half of data() are inverted,
+// i.e. 0 means "lifetime started".
+void InitMap::startElementLifetime(unsigned I) {
+ unsigned LifetimeIndex = NumElems + I;
+
+ unsigned Bucket = numFields(NumElems) / 2 + (I / PER_FIELD);
+ T Mask = T(1) << (LifetimeIndex % PER_FIELD);
+ if ((data()[Bucket] & Mask)) {
+ data()[Bucket] &= ~Mask;
+ --DeadFields;
+ }
+}
+
+// Values in the second half of data() are inverted,
+// i.e. 0 means "lifetime started".
+void InitMap::endElementLifetime(unsigned I) {
+ unsigned LifetimeIndex = NumElems + I;
+
+ unsigned Bucket = numFields(NumElems) / 2 + (I / PER_FIELD);
+ T Mask = T(1) << (LifetimeIndex % PER_FIELD);
+ if (!(data()[Bucket] & Mask)) {
+ data()[Bucket] |= Mask;
+ ++DeadFields;
+ }
+}
diff --git a/clang/lib/AST/ByteCode/InitMap.h b/clang/lib/AST/ByteCode/InitMap.h
index 44184f4ba18a0..b11c305e95ba4 100644
--- a/clang/lib/AST/ByteCode/InitMap.h
+++ b/clang/lib/AST/ByteCode/InitMap.h
@@ -11,8 +11,9 @@
#include <cassert>
#include <climits>
-#include <memory>
#include <cstdint>
+#include <limits>
+#include <memory>
namespace clang {
namespace interp {
@@ -24,20 +25,37 @@ struct InitMap final {
using T = uint64_t;
/// Bits stored in a single field.
static constexpr uint64_t PER_FIELD = sizeof(T) * CHAR_BIT;
+ /// Number of fields in the init map.
+ unsigned NumElems;
/// Number of fields not initialized.
unsigned UninitFields;
+ unsigned DeadFields = 0;
std::unique_ptr<T[]> Data;
public:
/// Initializes the map with no fields set.
- explicit InitMap(unsigned N);
+ explicit InitMap(unsigned N)
+ : NumElems(N), UninitFields(N),
+ Data(std::make_unique<T[]>(numFields(N))) {}
+ explicit InitMap(unsigned N, bool AllInitialized)
+ : NumElems(N), UninitFields(AllInitialized ? 0 : N),
+ Data(std::make_unique<T[]>(numFields(N))) {
+ if (AllInitialized) {
+ for (unsigned I = 0; I != (numFields(N) / 2); ++I)
+ Data[I] = std::numeric_limits<T>::max();
+ }
+ }
-private:
- friend class Pointer;
+ void startElementLifetime(unsigned I);
+ void endElementLifetime(unsigned I);
- /// Returns a pointer to storage.
- T *data() { return Data.get(); }
- const T *data() const { return Data.get(); }
+ bool isElementAlive(unsigned I) const {
+ unsigned LifetimeIndex = (NumElems + I);
+ unsigned Bucket = numFields(NumElems) / 2 + (I / PER_FIELD);
+ return !(data()[Bucket] & (T(1) << (LifetimeIndex % PER_FIELD)));
+ }
+
+ bool allElementsAlive() const { return DeadFields == 0; }
/// Initializes an element. Returns true when object if fully initialized.
bool initializeElement(unsigned I);
@@ -45,6 +63,11 @@ struct InitMap final {
/// Checks if an element was initialized.
bool isElementInitialized(unsigned I) const;
+private:
+ /// Returns a pointer to storage.
+ T *data() { return Data.get(); }
+ const T *data() const { return Data.get(); }
+
static constexpr size_t numFields(unsigned N) {
return ((N + PER_FIELD - 1) / PER_FIELD) * 2;
}
@@ -94,7 +117,6 @@ struct InitMapPtr final {
};
};
static_assert(sizeof(InitMapPtr) == sizeof(void *));
-
} // namespace interp
} // namespace clang
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 0205d840fd71e..a0dbf614ebb9c 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1953,10 +1953,6 @@ bool EndLifetime(InterpState &S, CodePtr OpPC) {
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
return false;
- // FIXME: We need per-element lifetime information for primitive arrays.
- if (Ptr.isArrayElement())
- return true;
-
endLifetimeRecurse(Ptr.narrow());
return true;
}
@@ -1967,10 +1963,6 @@ bool EndLifetimePop(InterpState &S, CodePtr OpPC) {
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
return false;
- // FIXME: We need per-element lifetime information for primitive arrays.
- if (Ptr.isArrayElement())
- return true;
-
endLifetimeRecurse(Ptr.narrow());
return true;
}
@@ -1991,6 +1983,9 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
if (!Ptr.isBlockPointer())
return false;
+ if (!CheckRange(S, OpPC, Ptr, AK_Construct))
+ return false;
+
startLifetimeRecurse(Ptr);
// Similar to CheckStore(), but with the additional CheckTemporary() call and
@@ -2370,6 +2365,34 @@ bool FinishInitGlobal(InterpState &S, CodePtr OpPC) {
return true;
}
+bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) {
+ assert(S.Current->getFunction());
+ // FIXME: We iterate the scope once here and then again in the destroy() call
+ // below.
+ for (auto &Local : S.Current->getFunction()->getScope(I).locals_reverse()) {
+ if (!S.Current->getLocalBlock(Local.Offset)->isInitialized())
+ continue;
+ const Pointer &Ptr = S.Current->getLocalPointer(Local.Offset);
+ if (Ptr.getLifetime() == Lifetime::Ended) {
+ // Try to use the declaration for better diagnostics
+ if (const Decl *D = Ptr.getDeclDesc()->asDecl()) {
+ auto *ND = cast<NamedDecl>(D);
+ S.FFDiag(ND->getLocation(),
+ diag::note_constexpr_destroy_out_of_lifetime)
+ << ND->getNameAsString();
+ } else {
+ S.FFDiag(Ptr.getDeclDesc()->getLocation(),
+ diag::note_constexpr_destroy_out_of_lifetime)
+ << Ptr.toDiagnosticString(S.getASTContext());
+ }
+ return false;
+ }
+ }
+
+ S.Current->destroy(I);
+ return true;
+}
+
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0eae7848b4fe0..eaca30b0aa51e 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -122,6 +122,7 @@ bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD);
bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
const FixedPoint &FP);
+bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I);
bool isConstexprUnknown(const Pointer &P);
enum class ShiftDir { Left, Right };
@@ -2469,38 +2470,6 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC, bool ElemSizeIsZero) {
return true;
}
-//===----------------------------------------------------------------------===//
-// Destroy
-//===----------------------------------------------------------------------===//
-
-inline bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) {
- assert(S.Current->getFunction());
-
- // FIXME: We iterate the scope once here and then again in the destroy() call
- // below.
- for (auto &Local : S.Current->getFunction()->getScope(I).locals_reverse()) {
- const Pointer &Ptr = S.Current->getLocalPointer(Local.Offset);
-
- if (Ptr.getLifetime() == Lifetime::Ended) {
- // Try to use the declaration for better diagnostics
- if (const Decl *D = Ptr.getDeclDesc()->asDecl()) {
- auto *ND = cast<NamedDecl>(D);
- S.FFDiag(ND->getLocation(),
- diag::note_constexpr_destroy_out_of_lifetime)
- << ND->getNameAsString();
- } else {
- S.FFDiag(Ptr.getDeclDesc()->getLocation(),
- diag::note_constexpr_destroy_out_of_lifetime)
- << Ptr.toDiagnosticString(S.getASTContext());
- }
- return false;
- }
- }
-
- S.Current->destroy(I);
- return true;
-}
-
inline bool InitScope(InterpState &S, CodePtr OpPC, uint32_t I) {
S.Current->initScope(I);
return true;
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index c5e0fd83021d7..4a773d83b76f6 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -495,6 +495,59 @@ bool Pointer::isElementInitialized(unsigned Index) const {
return isInitialized();
}
+bool Pointer::isElementAlive(unsigned Index) const {
+ assert(getFieldDesc()->isPrimitiveArray());
+
+ InitMapPtr &IM = getInitMap();
+ if (!IM.hasInitMap())
+ return true;
+
+ if (IM.allInitialized())
+ return true;
+
+ return IM->isElementAlive(Index);
+}
+
+void Pointer::startLifetime() const {
+ if (!isBlockPointer())
+ return;
+ if (BS.Base < sizeof(InlineDescriptor))
+ return;
+
+ if (inArray()) {
+ const Descriptor *Desc = getFieldDesc();
+ InitMapPtr &IM = getInitMap();
+ if (!IM.hasInitMap())
+ IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized()));
+
+ IM->startElementLifetime(getIndex());
+ assert(this->getLifetime() == Lifetime::Started);
+ return;
+ }
+
+ getInlineDesc()->LifeState = Lifetime::Started;
+}
+
+void Pointer::endLifetime() const {
+ if (!isBlockPointer())
+ return;
+ if (BS.Base < sizeof(InlineDescriptor))
+ return;
+
+ if (inArray()) {
+ const Descriptor *Desc = getFieldDesc();
+ InitMapPtr &IM = getInitMap();
+ if (!IM.hasInitMap())
+ IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized()));
+
+ IM->endElementLifetime(getIndex());
+ assert(this->getLifetime() == Lifetime::Ended);
+ return;
+ }
+
+ getInlineDesc()->LifeState = Lifetime::Ended;
+}
+
void Pointer::initialize() const {
if (!isBlockPointer())
return;
@@ -529,7 +582,6 @@ void Pointer::initializeElement(unsigned Index) const {
assert(Index < getFieldDesc()->getNumElems());
InitMapPtr &IM = getInitMap();
-
if (IM.allInitialized())
return;
@@ -567,6 +619,23 @@ bool Pointer::allElementsInitialized() const {
return IM.allInitialized();
}
+bool Pointer::allElementsAlive() const {
+ assert(getFieldDesc()->isPrimitiveArray());
+ assert(isArrayRoot());
+
+ if (isStatic() && BS.Base == 0)
+ return true;
+
+ if (isRoot() && BS.Base == sizeof(GlobalInlineDescriptor) &&
+ Offset == BS.Base) {
+ const auto &GD = block()->getBlockDesc<GlobalInlineDescriptor>();
+ return GD.InitState == GlobalInitState::Initialized;
+ }
+
+ InitMapPtr &IM = getInitMap();
+ return IM.allInitialized() || (IM.hasInitMap() && IM->allElementsAlive());
+}
+
void Pointer::activate() const {
// Field has its bit in an inline descriptor.
assert(BS.Base != 0 && "Only composite fields can be activated");
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index 202508cb71e5a..67fe16b12916b 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -15,6 +15,7 @@
#include "Descriptor.h"
#include "FunctionPointer.h"
+#include "InitMap.h"
#include "InterpBlock.h"
#include "clang/AST/ComparisonCategories.h"
#include "clang/AST/Decl.h"
@@ -722,6 +723,9 @@ class Pointer {
/// Like isInitialized(), but for primitive arrays.
bool isElementInitialized(unsigned Index) const;
bool allElementsInitialized() const;
+ bool allElementsAlive() const;
+ bool isElementAlive(unsigned Index) const;
+
/// Activats a field.
void activate() const;
/// Deactivates an entire strurcutre.
@@ -732,25 +736,33 @@ class Pointer {
return Lifetime::Started;
if (BS.Base < sizeof(InlineDescriptor))
return Lifetime::Started;
- return getInlineDesc()->LifeState;
- }
- void endLifetime() const {
- if (!isBlockPointer())
- return;
- if (BS.Base < sizeof(InlineDescriptor))
- return;
- getInlineDesc()->LifeState = Lifetime::Ended;
- }
+ if (inArray() && !isArrayRoot()) {
+ InitMapPtr &IM = getInitMap();
- void startLifetime() const {
- if (!isBlockPointer())
- return;
- if (BS.Base < sizeof(InlineDescriptor))
- return;
- getInlineDesc()->LifeState = Lifetime::Started;
+ if (!IM.hasInitMap()) {
+ if (IM.allInitialized())
+ return Lifetime::Started;
+ return getArray().getLifetime();
+ }
+
+ return IM->isElementAlive(getIndex()) ? Lifetime::Started
+ : Lifetime::Ended;
+ }
+
+ return getInlineDesc()->LifeState;
}
+ /// Start the lifetime of this pointer. This works for pointer with an
+ /// InlineDescriptor as well as primitive array elements. Pointers are usually
+ /// alive by default, unless the underlying object has been allocated with
+ /// std::allocator. This function is used by std::construct_at.
+ void startLifetime() const;
+ /// Ends the lifetime of the pointer. This works for pointer with an
+ /// InlineDescriptor as well as primitive array elements. This function is
+ /// used by std::destroy_at.
+ void endLifetime() const;
+
/// Compare two pointers.
ComparisonCategoryResult compare(const Pointer &Other) const {
if (!hasSameBase(*this, Other))
@@ -792,7 +804,6 @@ class Pointer {
friend class DeadBlock;
friend class MemberPointer;
friend class InterpState;
- friend struct InitMap;
friend class DynamicAllocator;
friend class Program;
@@ -839,6 +850,8 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) {
OS << ' ';
if (const Descriptor *D = P.getFieldDesc())
D->dump(OS);
+ if (P.isArrayElement())
+ OS << " index " << P.getIndex();
return OS;
}
diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp
index 3cde5a2b42e3d..116d6aef8350a 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -1852,11 +1852,9 @@ namespace WithinLifetime {
} xstd; // both-error {{is not a constant expression}} \
// both-note {{in call to}}
- /// FIXME: We do not have per-element lifetime information for primitive arrays.
- /// See https://github.com/llvm/llvm-project/issues/147528
consteval bool test_dynamic(bool read_after_deallocate) {
std::allocator<int> a;
- int* p = a.allocate(1); // expected-note 2{{allocation performed here was not deallocated}}
+ int* p = a.allocate(1);
// a.allocate starts the lifetime of an array,
// the complete object of *p has started its lifetime
if (__builtin_is_within_lifetime(p))
@@ -1869,12 +1867,12 @@ namespace WithinLifetime {
return false;
a.deallocate(p, 1);
if (read_after_deallocate)
- __builtin_is_within_lifetime(p); // ref-note {{read of heap allocated object that has been deleted}}
+ __builtin_is_within_lifetime(p); // both-note {{read of heap allocated object that has been deleted}}
return true;
}
- static_assert(test_dynamic(false)); // expected-error {{not an integral constant expression}}
+ static_assert(test_dynamic(false));
static_assert(test_dynamic(true)); // both-error {{not an integral constant expression}} \
- // ref-note {{in call to}}
+ // both-note {{in call to}}
}
#ifdef __SIZEOF_INT128__
diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp
index eb35cd904c6db..8139451400cae 100644
--- a/clang/test/AST/ByteCode/cxx23.cpp
+++ b/clang/test/AST/ByteCode/cxx23.cpp
@@ -12,6 +12,8 @@ inline constexpr void* operator new(__SIZE_TYPE__, void* p) noexcept { return p;
namespace std {
template<typename T, typename... Args>
constexpr T* construct_at(T* p, Args&&... args) { return ::new((void*)p) T(static_cast<Args&&>(args)...); }
+template<typename T>
+constexpr void destroy_at(T *p) { p->~T(); } // #destroy
}
constexpr int f(int n) { // all20-error {{constexpr function never produces a constant expression}}
@@ -528,6 +530,37 @@ namespace InactiveLocalsInConditionalOp {
static_assert(test2(true));
}
+
+namespace PrimitiveArrayLifetimes {
+ consteval int test1() {
+ int a[3];
+ assert_active(a[0]);
+ assert_active(a[1]);
+ assert_active(a[2]);
+ std::destroy_at(&a[0]);
+ assert_inactive(a[0]);
+
+ std::construct_at(&a[0]);
+ assert_active(a[0]);
+ return a[0];
+ }
+ static_assert(test1() == 0);
+
+ consteval int test2() {
+ int a[3] = {1,2,3};
+ assert_active(a[0]);
+ assert_active(a[1]);
+ assert_active(a[2]);
+ std::destroy_at(&a[0]);
+ assert_inactive(a[0]);
+
+ std::construct_at(&a[0]);
+ assert_active(a[0]);
+ return a[0];
+ }
+ static_assert(test2() == 0);
+}
+
#endif
namespace UnknownParams {
diff --git a/clang/test/AST/ByteCode/lifetimes26.cpp b/clang/test/AST/ByteCode/lifetimes26.cpp
index c3163f8a562bf..8db4ddb1ec51d 100644
--- a/clang/test/AST/ByteCode/lifetimes26.cpp
+++ b/clang/test/AST/ByteCode/lifetimes26.cpp
@@ -1,8 +1,6 @@
// RUN: %clang_cc1 -verify=expected,both -std=c++26 %s -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -verify=ref,both -std=c++26 %s
-// both-no-diagnostics
-
namespace std {
struct type_info;
struct destroying_delete_t {
@@ -13,12 +11,26 @@ namespace std {
} inline constexpr nothrow{};
using size_t = decltype(sizeof(0));
enum class align_val_t : size_t {};
+
+ using size_t = decltype(sizeof(0));
+ template<typename T> struct allocator {
+ constexpr T *allocate(size_t N) {
+ return (T*)__builtin_operator_new(sizeof(T) * N);
+ }
+ constexpr void deallocate(void *p) {
+ __builtin_operator_delete(p);
+ }
+ };
+ template<typename T, typename ...Args>
+ constexpr void construct_at(void *p, Args &&...args) {
+ new (p) T((Args&&)args...);
+ }
};
constexpr void *operator new(std::size_t, void *p) { return p; }
namespace std {
template<typename T> constexpr T *construct_at(T *p) { return new (p) T; }
- template<typename T> constexpr void destroy_at(T *p) { p->~T(); }
+ template<typename T> constexpr void destroy_at(T *p) { p->~T(); } // #destroy
}
constexpr bool foo() {
@@ -49,18 +61,102 @@ static_assert((destroy_pointer(), true));
namespace DestroyArrayElem {
- /// This is proof that std::destroy_at'ing an array element
- /// ends the lifetime of the entire array.
- /// See https://github.com/llvm/llvm-project/issues/147528
- /// Using destroy_at on array elements is currently a no-op due to this.
- constexpr int test() {
+
+ constexpr int test1() {
+ int a[4] = {};
+ std::destroy_at(&a); // both-error@#destroy {{object expression of non-scalar type 'int[4]' cannot be used in a pseudo-destructor expression}} \
+ // both-note {{in instantiation of function template specialization 'std::destroy_at<int[4]>' requested here}} \
+ // both-note {{subexpression not valid}}
+ return 0;
+ }
+ static_assert(test1() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+ constexpr int test2() {
+
int a[4] = {};
+ std::destroy_at(&a[1]);
+ int r = a[1]; // both-note {{read of object outside its lifetime}}
+ std::construct_at(&a[1]);
+ return 0;
+ }
+ static_assert(test2() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
- std::destroy_at(&a[3]);
- int r = a[1];
+ constexpr int test3() {
+ int a[4]; /// Array with no init map.
std::construct_at(&a[3]);
+ return a[3]; // both-note {{read of uninitialized object}}
+ }
+ static_assert(test3() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+ constexpr int test4(bool b) {
+ int a[4];
+ a[0] = 12;
+ std::construct_at(&a[3]);
+ return b ? a[0] : a[3]; // both-note {{read of uninitialized object}}
+ }
+ static_assert(test4(true) == 12);
+ static_assert(test4(false) == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+
+ constexpr int test5() {
+ int *a = std::allocator<int>{}.allocate(3);
+ a[0] = 1; // both-note {{assignment to object outside its lifetime}}
+ int b = a[1];
+ std::allocator<int>{}.deallocate(a);
+ return b;
+ }
+ static_assert(test5() == 1); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+ constexpr int test6() {
+ int *a = std::allocator<int>{}.allocate(3);
+ std::construct_at<int>(&a[0]);
+ a[0] = 1;
+ std::construct_at<int>(&a[1]);
+ a[1] = 2;
+ std::construct_at<int>(&a[2]);
+ a[2] = 3;
+
+ int b = a[1];
+ std::allocator<int>{}.deallocate(a);
+ return b;
+ }
+ static_assert(test6() == 2);
+
+ constexpr int test7() {
+ int *a = std::allocator<int>{}.allocate(3);
+ int *b = a + 3;
+ new (a + 0) int();
+ new (a + 1) int();
+ new (a + 2) int();
+
+ b = b - 1;
+ new (b) int(0);
+
+ bool r = a[0] == 0;
+
+ std::allocator<int>{}.deallocate(a);
+ return r;
+ }
+ static_assert(test7());
+
+ constexpr int test8() {
+ int *a = std::allocator<int>{}.allocate(3);
+ int *b = a + 3;
+ new (a + 0) int();
+ new (a + 1) int();
+ new (a + 2) int();
+
+ new (a) int();
+ std::destroy_at(b - 1);
+ bool r = *a == 0;
+ std::allocator<int>{}.deallocate(a);
return r;
}
- static_assert(test() == 0);
+ static_assert(test8());
}
More information about the cfe-commits
mailing list