[clang] [clang][Interp] Implement dynamic memory allocation handling (PR #70306)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 19 08:53:11 PDT 2024
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/70306 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/70306
>From 02256cbbd7860d788d86864e83ec2ddcedb9f0ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 18 Jun 2024 16:45:15 +0200
Subject: [PATCH 1/4] Fixup! --
---
.../clang/Analysis/Analyses/ThreadSafety.h | 2 +
clang/lib/Analysis/ThreadSafety.cpp | 42 +++++++++++++++++++
clang/lib/Sema/AnalysisBasedWarnings.cpp | 5 +++
3 files changed, 49 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafety.h b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
index 0866b09bab299..79c6f5959afe4 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafety.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
@@ -230,6 +230,8 @@ class ThreadSafetyHandler {
/// Warn that there is a cycle in acquired_before/after dependencies.
virtual void handleBeforeAfterCycle(Name L1Name, SourceLocation Loc) {}
+ virtual void handleAttributeMismatch() {}
+
/// Called by the analysis when starting analysis of a function.
/// Used to issue suggestions for changes to annotations.
virtual void enterFunction(const FunctionDecl *FD) {}
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index e25b843c9bf83..a42c4ca98ea9b 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2260,6 +2260,45 @@ static bool neverReturns(const CFGBlock *B) {
return false;
}
+template<typename AttrT>
+static SmallVector<const Expr *> collectAttrArgs(const FunctionDecl *FD) {
+ SmallVector<const Expr *>Args;
+ for (const AttrT *A : FD->specific_attrs<AttrT>()) {
+ for (const Expr *E : A->args())
+ Args.push_back(E);
+ }
+
+ return Args;
+}
+
+static void diagnoseMismatchedFunctionAttrs(const FunctionDecl *FD, ThreadSafetyHandler &Handler) {
+ llvm::errs() << __PRETTY_FUNCTION__ << "\n";
+ assert(FD);
+ FD = FD->getDefinition();
+ assert(FD);
+
+ FD->dump();
+
+ auto FDArgs = collectAttrArgs<RequiresCapabilityAttr>(FD);
+
+ for (const FunctionDecl *D = FD->getPreviousDecl(); D; D = D->getPreviousDecl()) {
+ D->dump();
+ auto DArgs = collectAttrArgs<RequiresCapabilityAttr>(D);
+
+ llvm::errs() << "FD Args: " << FDArgs.size() << "\n";
+ llvm::errs() << " D Args: " << DArgs.size() << "\n";
+
+ for (const Expr *E : FDArgs) {
+ if (!llvm::is_contained(DArgs, E)) {
+ // FD requires E, but D doesn't.
+ llvm::errs() << "OMG\n";
+ Handler.handleAttributeMismatch();
+ }
+ }
+ llvm::errs() << "---\n";
+ }
+}
+
/// Check a function's CFG for thread-safety violations.
///
/// We traverse the blocks in the CFG, compute the set of mutexes that are held
@@ -2279,6 +2318,9 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
const NamedDecl *D = walker.getDecl();
CurrentFunction = dyn_cast<FunctionDecl>(D);
+ if (CurrentFunction)
+ diagnoseMismatchedFunctionAttrs(CurrentFunction, Handler);
+
if (D->hasAttr<NoThreadSafetyAnalysisAttr>())
return;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f604c61fa3af..9eac80603ff88 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2073,6 +2073,11 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
Warnings.emplace_back(std::move(Warning), getNotes());
}
+ void handleAttributeMismatch() override {
+ llvm::errs() << __PRETTY_FUNCTION__ << "\n";
+
+ }
+
void enterFunction(const FunctionDecl* FD) override {
CurrentFunction = FD;
}
>From 837ad17e1b1cc2465cd24bebbf5187e430310049 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 18 Jun 2024 20:42:18 +0200
Subject: [PATCH 2/4] [clang][Interp][NFC] Fix initializing union APValues
The InitElem op assumes an array.
---
clang/lib/AST/Interp/ByteCodeExprGen.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index d47f6f23c8bc1..61a7394854d40 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -3507,7 +3507,7 @@ bool ByteCodeExprGen<Emitter>::visitAPValueInitializer(const APValue &Val,
PrimType T = classifyPrim(RF->Decl->getType());
if (!this->visitAPValue(F, T, E))
return false;
- return this->emitInitElem(T, 0, E);
+ return this->emitInitField(T, RF->Offset, E);
}
// TODO: Other types.
>From ff5713200186b909b55cdc55321f0e25464a1af0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 19 Jun 2024 10:09:06 +0200
Subject: [PATCH 3/4] [clang][Interp][NFC] Loosen an assertion
---
clang/lib/AST/Interp/ByteCodeExprGen.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index 61a7394854d40..06e54e4baaaf5 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -2838,7 +2838,9 @@ template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitExtVectorElementExpr(
const ExtVectorElementExpr *E) {
const Expr *Base = E->getBase();
- assert(Base->getType()->isVectorType());
+ assert(
+ Base->getType()->isVectorType() ||
+ Base->getType()->getAs<PointerType>()->getPointeeType()->isVectorType());
SmallVector<uint32_t, 4> Indices;
E->getEncodedElementAccess(Indices);
>From 76ad65922adb448ace22bc0e81ca2ea59c3b3a39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 25 Oct 2023 08:33:30 +0200
Subject: [PATCH 4/4] [clang][Interp] Implement dynamic memory allocation
handling
---
clang/lib/AST/CMakeLists.txt | 1 +
clang/lib/AST/Interp/ByteCodeExprGen.cpp | 103 +++++
clang/lib/AST/Interp/ByteCodeExprGen.h | 2 +
clang/lib/AST/Interp/Context.cpp | 2 +
clang/lib/AST/Interp/DynamicAllocator.cpp | 118 +++++
clang/lib/AST/Interp/DynamicAllocator.h | 95 ++++
clang/lib/AST/Interp/EvalEmitter.cpp | 23 +
clang/lib/AST/Interp/EvaluationResult.cpp | 72 +++
clang/lib/AST/Interp/EvaluationResult.h | 6 +
clang/lib/AST/Interp/Interp.cpp | 52 +++
clang/lib/AST/Interp/Interp.h | 149 +++++++
clang/lib/AST/Interp/InterpBlock.h | 21 +-
clang/lib/AST/Interp/InterpState.cpp | 17 +-
clang/lib/AST/Interp/InterpState.h | 11 +
clang/lib/AST/Interp/Opcodes.td | 24 +-
clang/lib/AST/Interp/Pointer.h | 1 +
clang/test/AST/Interp/new-delete.cpp | 488 +++++++++++++++++++++
clang/test/Rewriter/rewrite-modern-catch.m | 2 +-
clang/test/SemaCXX/delete.cpp | 2 +-
clang/test/SemaCXX/new-delete.cpp | 22 +-
20 files changed, 1198 insertions(+), 13 deletions(-)
create mode 100644 clang/lib/AST/Interp/DynamicAllocator.cpp
create mode 100644 clang/lib/AST/Interp/DynamicAllocator.h
create mode 100644 clang/test/AST/Interp/new-delete.cpp
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index 0328666d59b1f..df27f1239abe4 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -76,6 +76,7 @@ add_clang_library(clangAST
Interp/InterpBuiltin.cpp
Interp/Floating.cpp
Interp/EvaluationResult.cpp
+ Interp/DynamicAllocator.cpp
Interp/Interp.cpp
Interp/InterpBlock.cpp
Interp/InterpFrame.cpp
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index 06e54e4baaaf5..4eb258c12cafd 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -2656,6 +2656,109 @@ bool ByteCodeExprGen<Emitter>::VisitCXXInheritedCtorInitExpr(
return this->emitCall(F, 0, E);
}
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
+ assert(classifyPrim(E->getType()) == PT_Ptr);
+ const Expr *Init = E->getInitializer();
+ QualType ElementType = E->getAllocatedType();
+ std::optional<PrimType> ElemT = classify(ElementType);
+ unsigned PlacementArgs = E->getNumPlacementArgs();
+ bool IsNoThrow = false;
+
+ // FIXME: Better diagnostic. diag::note_constexpr_new_placement
+ if (PlacementArgs != 0) {
+ // The only new-placement list we support is of the form (std::nothrow).
+ //
+ // FIXME: There is no restriction on this, but it's not clear that any
+ // other form makes any sense. We get here for cases such as:
+ //
+ // new (std::align_val_t{N}) X(int)
+ //
+ // (which should presumably be valid only if N is a multiple of
+ // alignof(int), and in any case can't be deallocated unless N is
+ // alignof(X) and X has new-extended alignment).
+ if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
+ return this->emitInvalid(E);
+
+ if (!this->discard(E->getPlacementArg(0)))
+ return false;
+ IsNoThrow = true;
+ }
+
+ const Descriptor *Desc;
+ if (ElemT) {
+ if (E->isArray())
+ Desc = nullptr; // We're not going to use it in this case.
+ else
+ Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
+ /*IsConst=*/false, /*IsTemporary=*/false,
+ /*IsMutable=*/false);
+ } else {
+ Desc = P.createDescriptor(
+ E, ElementType.getTypePtr(),
+ E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
+ /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
+ }
+
+ if (E->isArray()) {
+ std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
+ if (!ArraySizeExpr)
+ return false;
+ PrimType SizeT = classifyPrim((*ArraySizeExpr)->getType());
+
+ if (!this->visit(*ArraySizeExpr))
+ return false;
+
+ if (ElemT) {
+ // N primitive elements.
+ if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
+ return false;
+ } else {
+ // N Composite elements.
+ if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
+ return false;
+ }
+
+ if (Init && !this->visitInitializer(Init))
+ return false;
+
+ } else {
+ // Allocate just one element.
+ if (!this->emitAlloc(Desc, E))
+ return false;
+
+ if (Init) {
+ if (ElemT) {
+ if (!this->visit(Init))
+ return false;
+
+ if (!this->emitInit(*ElemT, E))
+ return false;
+ } else {
+ // Composite.
+ if (!this->visitInitializer(Init))
+ return false;
+ }
+ }
+ }
+
+ if (DiscardResult)
+ return this->emitPopPtr(E);
+
+ return true;
+}
+
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
+ const Expr *Arg = E->getArgument();
+
+ // Arg must be an lvalue.
+ if (!this->visit(Arg))
+ return false;
+
+ return this->emitFree(E->isArrayForm(), E);
+}
+
template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitExpressionTraitExpr(
const ExpressionTraitExpr *E) {
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h
index 19cbbc432e4b1..1579ebd48594d 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.h
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.h
@@ -130,6 +130,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
bool VisitShuffleVectorExpr(const ShuffleVectorExpr *E);
bool VisitExtVectorElementExpr(const ExtVectorElementExpr *E);
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
+ bool VisitCXXNewExpr(const CXXNewExpr *E);
+ bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
protected:
bool visitExpr(const Expr *E) override;
diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp
index 98d1837204ebc..6773dd1321945 100644
--- a/clang/lib/AST/Interp/Context.cpp
+++ b/clang/lib/AST/Interp/Context.cpp
@@ -91,6 +91,8 @@ bool Context::evaluate(State &Parent, const Expr *E, APValue &Result) {
bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,
APValue &Result) {
+ llvm::errs() << __PRETTY_FUNCTION__ << "\n";
+ VD->dump();
bool Recursing = !Stk.empty();
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk);
diff --git a/clang/lib/AST/Interp/DynamicAllocator.cpp b/clang/lib/AST/Interp/DynamicAllocator.cpp
new file mode 100644
index 0000000000000..2e745941a24ca
--- /dev/null
+++ b/clang/lib/AST/Interp/DynamicAllocator.cpp
@@ -0,0 +1,118 @@
+//==-------- DynamicAllocator.cpp - Dynamic allocations ----------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DynamicAllocator.h"
+#include "InterpBlock.h"
+#include "InterpState.h"
+
+using namespace clang;
+using namespace clang::interp;
+
+DynamicAllocator::~DynamicAllocator() { cleanup(); }
+
+void DynamicAllocator::cleanup() {
+ // Invoke destructors of all the blocks and as a last restort,
+ // reset all the pointers pointing to them to null pointees.
+ // This should never show up in diagnostics, but it's necessary
+ // for us to not cause use-after-free problems.
+ for (auto &Iter : AllocationSites) {
+ auto &AllocSite = Iter.second;
+ for (auto &Alloc : AllocSite.Allocations) {
+ Block *B = reinterpret_cast<Block *>(Alloc.Memory.get());
+ B->invokeDtor();
+ if (B->hasPointers()) {
+ while (B->Pointers) {
+ Pointer *Next = B->Pointers->Next;
+ B->Pointers->PointeeStorage.BS.Pointee = nullptr;
+ B->Pointers = Next;
+ }
+ B->Pointers = nullptr;
+ }
+ }
+ }
+
+ AllocationSites.clear();
+}
+
+Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
+ size_t NumElements) {
+ // Create a new descriptor for an array of the specified size and
+ // element type.
+ const Descriptor *D = allocateDescriptor(
+ Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
+ /*IsTemporary=*/false, /*IsMutable=*/false);
+
+ return allocate(D);
+}
+
+Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
+ size_t NumElements) {
+ // Create a new descriptor for an array of the specified size and
+ // element type.
+ const Descriptor *D = allocateDescriptor(
+ ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
+ /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
+ return allocate(D);
+}
+
+Block *DynamicAllocator::allocate(const Descriptor *D) {
+ assert(D->asExpr());
+
+ auto Memory =
+ std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
+ auto *B = new (Memory.get()) Block(D, /*isStatic=*/false);
+ B->invokeCtor();
+
+ InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
+ ID->Desc = D;
+ ID->IsActive = true;
+ ID->Offset = sizeof(InlineDescriptor);
+ ID->IsBase = false;
+ ID->IsFieldMutable = false;
+ ID->IsConst = false;
+ ID->IsInitialized = false;
+ assert(ID->Desc);
+
+ B->IsDynamic = true;
+
+ if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
+ It->second.Allocations.emplace_back(std::move(Memory));
+ else
+ AllocationSites.insert(
+ {D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
+ return B;
+}
+
+bool DynamicAllocator::deallocate(const Expr *Source,
+ const Block *BlockToDelete, InterpState &S) {
+ auto It = AllocationSites.find(Source);
+ if (It == AllocationSites.end())
+ return false;
+
+ auto &Site = It->second;
+ assert(Site.size() > 0);
+
+ // Find the Block to delete.
+ auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
+ const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
+ return BlockToDelete == B;
+ });
+
+ assert(AllocIt != Site.Allocations.end());
+
+ Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
+ B->invokeDtor();
+
+ S.deallocate(B);
+ Site.Allocations.erase(AllocIt);
+
+ if (Site.size() == 0)
+ AllocationSites.erase(It);
+
+ return true;
+}
diff --git a/clang/lib/AST/Interp/DynamicAllocator.h b/clang/lib/AST/Interp/DynamicAllocator.h
new file mode 100644
index 0000000000000..807a94e18bcd1
--- /dev/null
+++ b/clang/lib/AST/Interp/DynamicAllocator.h
@@ -0,0 +1,95 @@
+//==--------- DynamicAllocator.h - Dynamic allocations ------------*- C++ -*-=//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
+#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
+
+#include "Descriptor.h"
+#include "InterpBlock.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/iterator_range.h"
+#include "llvm/Support/Allocator.h"
+
+namespace clang {
+class Expr;
+namespace interp {
+class Block;
+class InterpState;
+
+/// Manages dynamic memory allocations done during bytecode interpretation.
+///
+/// We manage allocations as a map from their new-expression to a list
+/// of allocations. This is called an AllocationSite. For each site, we
+/// record whether it was allocated using new or new[], the
+/// IsArrayAllocation flag.
+///
+/// For all array allocations, we need to allocat new Descriptor instances,
+/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
+class DynamicAllocator final {
+ struct Allocation {
+ std::unique_ptr<std::byte[]> Memory;
+ Allocation(std::unique_ptr<std::byte[]> Memory)
+ : Memory(std::move(Memory)) {}
+ };
+
+ struct AllocationSite {
+ llvm::SmallVector<Allocation> Allocations;
+ bool IsArrayAllocation = false;
+
+ AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
+ : IsArrayAllocation(Array) {
+ Allocations.push_back({std::move(Memory)});
+ }
+
+ size_t size() const { return Allocations.size(); }
+ };
+
+public:
+ DynamicAllocator() = default;
+ ~DynamicAllocator();
+
+ void cleanup();
+
+ unsigned getNumAllocations() const { return AllocationSites.size(); }
+
+ /// Allocate ONE element of the given descriptor.
+ Block *allocate(const Descriptor *D);
+ /// Allocate \p NumElements primitive elements of the given type.
+ Block *allocate(const Expr *Source, PrimType T, size_t NumElements);
+ /// Allocate \p NumElements elements of the given descriptor.
+ Block *allocate(const Descriptor *D, size_t NumElements);
+
+ /// Deallocate the given source+block combination.
+ /// Returns \c true if anything has been deallocatd, \c false otherwise.
+ bool deallocate(const Expr *Source, const Block *BlockToDelete,
+ InterpState &S);
+
+ /// Checks whether the allocation done at the given source is an array
+ /// allocation.
+ bool isArrayAllocation(const Expr *Source) const {
+ if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
+ return It->second.IsArrayAllocation;
+ return false;
+ }
+
+ // FIXME: Public because I'm not sure how to expose an iterator to it.
+ llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
+
+private:
+ using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
+ PoolAllocTy DescAllocator;
+
+ /// Allocates a new descriptor.
+ template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
+ return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
+ }
+};
+
+} // namespace interp
+} // namespace clang
+#endif
diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp
index 77ff901634a46..e5d67d54affb0 100644
--- a/clang/lib/AST/Interp/EvalEmitter.cpp
+++ b/clang/lib/AST/Interp/EvalEmitter.cpp
@@ -129,9 +129,17 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
return true;
}
+static bool checkReturnState(InterpState &S) {
+ return S.maybeDiagnoseDanglingAllocations();
+}
+
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
if (!isActive())
return true;
+
+ if (!checkReturnState(S))
+ return false;
+
using T = typename PrimConv<OpType>::T;
EvalResult.setValue(S.Stk.pop<T>().toAPValue());
return true;
@@ -143,9 +151,14 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
+ if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
+ return false;
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
return false;
+ if (!checkReturnState(S))
+ return false;
+
// Implicitly convert lvalue to rvalue, if requested.
if (ConvertResultToRValue) {
if (std::optional<APValue> V = Ptr.toRValue(Ctx)) {
@@ -162,12 +175,17 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) {
if (!isActive())
return true;
+
+ if (!checkReturnState(S))
+ return false;
// Function pointers cannot be converted to rvalues.
EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>());
return true;
}
bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
+ if (!checkReturnState(S))
+ return false;
EvalResult.setValid();
return true;
}
@@ -175,9 +193,14 @@ bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
bool EvalEmitter::emitRetValue(const SourceInfo &Info) {
const auto &Ptr = S.Stk.pop<Pointer>();
+ if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
+ return false;
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
return false;
+ if (!checkReturnState(S))
+ return false;
+
if (std::optional<APValue> APV = Ptr.toRValue(S.getCtx())) {
EvalResult.setValue(*APV);
return true;
diff --git a/clang/lib/AST/Interp/EvaluationResult.cpp b/clang/lib/AST/Interp/EvaluationResult.cpp
index a62f3f635e6e0..8e924535108a5 100644
--- a/clang/lib/AST/Interp/EvaluationResult.cpp
+++ b/clang/lib/AST/Interp/EvaluationResult.cpp
@@ -10,6 +10,7 @@
#include "InterpState.h"
#include "Record.h"
#include "clang/AST/ExprCXX.h"
+#include "llvm/ADT/SetVector.h"
namespace clang {
namespace interp {
@@ -152,6 +153,11 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S,
if (Ptr.isZero())
return true;
+ // We can't inspect dead pointers at all. Return true here so we can
+ // diagnose them later.
+ if (!Ptr.isLive())
+ return true;
+
SourceLocation InitLoc;
if (const auto *D = Source.dyn_cast<const Decl *>())
InitLoc = cast<VarDecl>(D)->getAnyInitializer()->getExprLoc();
@@ -168,5 +174,71 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S,
return true;
}
+static void collectBlocks(const Pointer &Ptr,
+ llvm::SetVector<const Block *> &Blocks) {
+ auto isUsefulPtr = [](const Pointer &P) -> bool {
+ return P.isLive() && !P.isZero() && !P.isDummy() &&
+ !P.isUnknownSizeArray() && !P.isOnePastEnd() && P.isBlockPointer();
+ };
+
+ if (!isUsefulPtr(Ptr))
+ return;
+
+ Blocks.insert(Ptr.block());
+
+ const Descriptor *Desc = Ptr.getFieldDesc();
+ if (!Desc)
+ return;
+
+ if (const Record *R = Desc->ElemRecord) {
+ for (const Record::Field &F : R->fields()) {
+ const Pointer &FieldPtr = Ptr.atField(F.Offset);
+ assert(FieldPtr.block() == Ptr.block());
+ collectBlocks(FieldPtr, Blocks);
+ }
+ } else if (Desc->isPrimitive() && Desc->getPrimType() == PT_Ptr) {
+ const Pointer &Pointee = Ptr.deref<Pointer>();
+ if (isUsefulPtr(Pointee) && !Blocks.contains(Pointee.block()))
+ collectBlocks(Pointee, Blocks);
+
+ } else if (Desc->isPrimitiveArray() && Desc->getPrimType() == PT_Ptr) {
+ for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
+ const Pointer &ElemPointee = Ptr.atIndex(I).deref<Pointer>();
+ if (isUsefulPtr(ElemPointee) && !Blocks.contains(ElemPointee.block()))
+ collectBlocks(ElemPointee, Blocks);
+ }
+ } else if (Desc->isCompositeArray()) {
+ for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
+ const Pointer &ElemPtr = Ptr.atIndex(I).narrow();
+ collectBlocks(ElemPtr, Blocks);
+ }
+ }
+}
+
+bool EvaluationResult::checkReturnValue(InterpState &S, const Context &Ctx,
+ const Pointer &Ptr,
+ const SourceInfo &Info) {
+ // Collect all blocks that this pointer (transitively) points to and
+ // return false if any of them is a dynamic block.
+ llvm::SetVector<const Block *> Blocks;
+
+ collectBlocks(Ptr, Blocks);
+
+ for (const Block *B : Blocks) {
+ if (B->isDynamic()) {
+ assert(B->getDescriptor());
+ assert(B->getDescriptor()->asExpr());
+
+ S.FFDiag(Info, diag::note_constexpr_dynamic_alloc)
+ << Ptr.getType()->isReferenceType() << !Ptr.isRoot();
+ S.Note(B->getDescriptor()->asExpr()->getExprLoc(),
+ diag::note_constexpr_dynamic_alloc_here);
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // namespace interp
} // namespace clang
diff --git a/clang/lib/AST/Interp/EvaluationResult.h b/clang/lib/AST/Interp/EvaluationResult.h
index ecf2250074cc9..09e73072d075d 100644
--- a/clang/lib/AST/Interp/EvaluationResult.h
+++ b/clang/lib/AST/Interp/EvaluationResult.h
@@ -98,12 +98,18 @@ class EvaluationResult final {
/// LValue and we can't read from it.
std::optional<APValue> toRValue() const;
+ /// Check that all subobjects of the given pointer have been initialized.
bool checkFullyInitialized(InterpState &S, const Pointer &Ptr) const;
+ /// Check that none of the blocks the given pointer (transitively) points
+ /// to are dynamically allocated.
+ bool checkReturnValue(InterpState &S, const Context &Ctx, const Pointer &Ptr,
+ const SourceInfo &Info);
/// Dump to stderr.
void dump() const;
friend class EvalEmitter;
+ friend class InterpState;
};
} // namespace interp
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index 49015b1dd63d3..75d021f46148d 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -676,6 +676,58 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
return true;
}
+bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
+ if (S.getLangOpts().CPlusPlus20)
+ return true;
+
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.FFDiag(E, diag::note_constexpr_new);
+ return false;
+}
+
+bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
+ bool DeleteIsArray, const Descriptor *D,
+ const Expr *NewExpr) {
+ if (NewWasArray == DeleteIsArray)
+ return true;
+
+ QualType TypeToDiagnose;
+ // We need to shuffle things around a bit here to get a better diagnostic,
+ // because the expression we allocated the block for was of type int*,
+ // but we want to get the array size right.
+ if (D->isArray()) {
+ QualType ElemQT = D->getType()->getPointeeType();
+ TypeToDiagnose = S.getCtx().getConstantArrayType(
+ ElemQT, APInt(64, D->getNumElems(), false), nullptr,
+ ArraySizeModifier::Normal, 0);
+ } else
+ TypeToDiagnose = D->getType()->getPointeeType();
+
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
+ << DeleteIsArray << 0 << TypeToDiagnose;
+ S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
+ << NewExpr->getSourceRange();
+ return false;
+}
+
+bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
+ const Pointer &Ptr) {
+ if (Source && isa<CXXNewExpr>(Source))
+ return true;
+
+ // Whatever this is, we didn't heap allocate it.
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc)
+ << Ptr.toDiagnosticString(S.getCtx());
+
+ if (Ptr.isTemporary())
+ S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
+ else
+ S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
+ return false;
+}
+
/// We aleady know the given DeclRefExpr is invalid for some reason,
/// now figure out why and print appropriate diagnostics.
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index fea83de829261..4e31c29db0190 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -15,6 +15,7 @@
#include "../ExprConstShared.h"
#include "Boolean.h"
+#include "DynamicAllocator.h"
#include "Floating.h"
#include "Function.h"
#include "FunctionPointer.h"
@@ -125,6 +126,20 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD);
bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
const CallExpr *CE, unsigned ArgSize);
+/// Checks if dynamic memory allocation is available in the current
+/// language mode.
+bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC);
+
+/// Diagnose mismatched new[]/delete or new/delete[] pairs.
+bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
+ bool DeleteIsArray, const Descriptor *D,
+ const Expr *NewExpr);
+
+/// Check the source of the pointer passed to delete/delete[] has actually
+/// been heap allocated by us.
+bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
+ const Pointer &Ptr);
+
/// Sets the given integral value to the pointer, which is of
/// a std::{weak,partial,strong}_ordering type.
bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC,
@@ -192,6 +207,30 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) {
return true;
}
+template <typename SizeT>
+bool CheckArraySize(InterpState &S, CodePtr OpPC, SizeT *NumElements,
+ bool IsNoThrow) {
+ // FIXME: Both the SizeT::from() as well as the
+ // NumElements.toAPSInt() in this function are rather expensive.
+
+ // FIXME: GH63562
+ // APValue stores array extents as unsigned,
+ // so anything that is greater that unsigned would overflow when
+ // constructing the array, we catch this here.
+ SizeT MaxElements = SizeT::from(std::numeric_limits<unsigned>::max());
+ if (NumElements->toAPSInt().getActiveBits() >
+ ConstantArrayType::getMaxSizeBits(S.getCtx()) ||
+ *NumElements > MaxElements) {
+ if (!IsNoThrow) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_new_too_large)
+ << NumElements->toDiagnosticString(S.getCtx());
+ }
+ return false;
+ }
+ return true;
+}
+
/// Checks if the result of a floating-point operation is valid
/// in the current context.
bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
@@ -2698,6 +2737,116 @@ inline bool DecayPtr(InterpState &S, CodePtr OpPC) {
return true;
}
+inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
+ assert(Desc);
+
+ if (!CheckDynamicMemoryAllocation(S, OpPC))
+ return false;
+
+ DynamicAllocator &Allocator = S.getAllocator();
+ Block *B = Allocator.allocate(Desc);
+ assert(B);
+
+ S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
+
+ return true;
+}
+
+template <PrimType Name, class SizeT = typename PrimConv<Name>::T>
+inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
+ bool IsNoThrow) {
+ if (!CheckDynamicMemoryAllocation(S, OpPC))
+ return false;
+
+ SizeT NumElements = S.Stk.pop<SizeT>();
+ if (!CheckArraySize(S, OpPC, &NumElements, IsNoThrow)) {
+ if (!IsNoThrow)
+ return false;
+
+ // If this failed and is nothrow, just return a null ptr.
+ S.Stk.push<Pointer>(0, nullptr);
+ return true;
+ }
+
+ DynamicAllocator &Allocator = S.getAllocator();
+ Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements));
+ assert(B);
+ S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
+
+ return true;
+}
+
+template <PrimType Name, class SizeT = typename PrimConv<Name>::T>
+inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
+ bool IsNoThrow) {
+ if (!CheckDynamicMemoryAllocation(S, OpPC))
+ return false;
+
+ SizeT NumElements = S.Stk.pop<SizeT>();
+ if (!CheckArraySize(S, OpPC, &NumElements, IsNoThrow)) {
+ if (!IsNoThrow)
+ return false;
+
+ // If this failed and is nothrow, just return a null ptr.
+ S.Stk.push<Pointer>(0, ElementDesc);
+ return true;
+ }
+
+ DynamicAllocator &Allocator = S.getAllocator();
+ Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements));
+ assert(B);
+
+ S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
+
+ return true;
+}
+
+static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
+
+ if (!CheckDynamicMemoryAllocation(S, OpPC))
+ return false;
+
+ const Expr *Source = nullptr;
+ const Block *BlockToDelete = nullptr;
+ {
+ // Extra scope for this so the block doesn't have this pointer
+ // pointing to it when we destroy it.
+ const Pointer &Ptr = S.Stk.pop<Pointer>();
+
+ // Deleteing nullptr is always fine.
+ if (Ptr.isZero())
+ return true;
+
+ if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
+ << Ptr.toDiagnosticString(S.getCtx()) << Ptr.isOnePastEnd();
+ return false;
+ }
+
+ Source = Ptr.getDeclDesc()->asExpr();
+ BlockToDelete = Ptr.block();
+
+ if (!CheckDeleteSource(S, OpPC, Source, Ptr))
+ return false;
+ }
+ assert(Source);
+ assert(BlockToDelete);
+
+ DynamicAllocator &Allocator = S.getAllocator();
+ bool WasArrayAlloc = Allocator.isArrayAllocation(Source);
+ const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
+
+ if (!Allocator.deallocate(Source, BlockToDelete, S)) {
+ // Nothing has been deallocated, this must be a double-delete.
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_double_delete);
+ return false;
+ }
+ return CheckNewDeleteForms(S, OpPC, WasArrayAlloc, DeleteIsArrayForm,
+ BlockDesc, Source);
+}
+
//===----------------------------------------------------------------------===//
// Read opcode arguments
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Interp/InterpBlock.h b/clang/lib/AST/Interp/InterpBlock.h
index 2bb195648a9a9..7b772239e5a87 100644
--- a/clang/lib/AST/Interp/InterpBlock.h
+++ b/clang/lib/AST/Interp/InterpBlock.h
@@ -51,15 +51,16 @@ class Block final {
/// Creates a new block.
Block(const std::optional<unsigned> &DeclID, const Descriptor *Desc,
bool IsStatic = false, bool IsExtern = false)
- : DeclID(DeclID), IsStatic(IsStatic), IsExtern(IsExtern), Desc(Desc) {
- assert(Desc);
- }
+ : DeclID(DeclID), IsStatic(IsStatic), IsExtern(IsExtern),
+ IsDynamic(false), Desc(Desc) {
+ assert(Desc);
+ }
Block(const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false)
: DeclID((unsigned)-1), IsStatic(IsStatic), IsExtern(IsExtern),
- Desc(Desc) {
- assert(Desc);
- }
+ IsDynamic(false), Desc(Desc) {
+ assert(Desc);
+ }
/// Returns the block's descriptor.
const Descriptor *getDescriptor() const { return Desc; }
@@ -71,6 +72,7 @@ class Block final {
bool isStatic() const { return IsStatic; }
/// Checks if the block is temporary.
bool isTemporary() const { return Desc->IsTemporary; }
+ bool isDynamic() const { return IsDynamic; }
/// Returns the size of the block.
unsigned getSize() const { return Desc->getAllocSize(); }
/// Returns the declaration ID.
@@ -129,9 +131,11 @@ class Block final {
friend class Pointer;
friend class DeadBlock;
friend class InterpState;
+ friend class DynamicAllocator;
Block(const Descriptor *Desc, bool IsExtern, bool IsStatic, bool IsDead)
- : IsStatic(IsStatic), IsExtern(IsExtern), IsDead(true), Desc(Desc) {
+ : IsStatic(IsStatic), IsExtern(IsExtern), IsDead(true), IsDynamic(false),
+ Desc(Desc) {
assert(Desc);
}
@@ -160,6 +164,9 @@ class Block final {
/// Flag indicating if the block contents have been initialized
/// via invokeCtor.
bool IsInitialized = false;
+ /// Flag indicating if this block has been allocated via dynamic
+ /// memory allocation (e.g. malloc).
+ bool IsDynamic = false;
/// Pointer to the stack slot descriptor.
const Descriptor *Desc;
};
diff --git a/clang/lib/AST/Interp/InterpState.cpp b/clang/lib/AST/Interp/InterpState.cpp
index 550bc9f1a84b9..0c80e25c18c1a 100644
--- a/clang/lib/AST/Interp/InterpState.cpp
+++ b/clang/lib/AST/Interp/InterpState.cpp
@@ -33,7 +33,7 @@ InterpState::~InterpState() {
}
}
-void InterpState::cleanup() {}
+void InterpState::cleanup() { Alloc.cleanup(); }
Frame *InterpState::getCurrentFrame() {
if (Current && Current->Caller)
@@ -73,3 +73,18 @@ void InterpState::deallocate(Block *B) {
B->invokeDtor();
}
}
+
+bool InterpState::maybeDiagnoseDanglingAllocations() {
+ bool NoAllocationsLeft = (Alloc.getNumAllocations() == 0);
+
+ if (!checkingPotentialConstantExpression()) {
+ for (const auto &It : Alloc.AllocationSites) {
+ assert(It.second.size() > 0);
+
+ const Expr *Source = It.first;
+ CCEDiag(Source->getExprLoc(), diag::note_constexpr_memory_leak)
+ << (It.second.size() - 1) << Source->getSourceRange();
+ }
+ }
+ return NoAllocationsLeft;
+}
diff --git a/clang/lib/AST/Interp/InterpState.h b/clang/lib/AST/Interp/InterpState.h
index 0938a723a76d0..eb06de71734e9 100644
--- a/clang/lib/AST/Interp/InterpState.h
+++ b/clang/lib/AST/Interp/InterpState.h
@@ -14,6 +14,7 @@
#define LLVM_CLANG_AST_INTERP_INTERPSTATE_H
#include "Context.h"
+#include "DynamicAllocator.h"
#include "Function.h"
#include "InterpFrame.h"
#include "InterpStack.h"
@@ -102,13 +103,23 @@ class InterpState final : public State, public SourceMapper {
void setEvalLocation(SourceLocation SL) { this->EvalLocation = SL; }
+ DynamicAllocator &getAllocator() { return Alloc; }
+
+ /// Diagnose any dynamic allocations that haven't been freed yet.
+ /// Will return \c false if there were any allocations to diagnose,
+ /// \c true otherwise.
+ bool maybeDiagnoseDanglingAllocations();
+
private:
+ friend class EvaluationResult;
/// AST Walker state.
State &Parent;
/// Dead block chain.
DeadBlock *DeadBlocks = nullptr;
/// Reference to the offset-source mapping.
SourceMapper *M;
+ /// Allocator used for dynamic allocations performed via the program.
+ DynamicAllocator Alloc;
public:
/// Reference to the module containing all bytecode.
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index ddd955fc4cfa4..5e0b566809199 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -58,11 +58,13 @@ def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; }
def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; }
def ArgCastKind : ArgType { let Name = "CastKind"; }
def ArgCallExpr : ArgType { let Name = "const CallExpr *"; }
+def ArgExpr : ArgType { let Name = "const Expr *"; }
def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; }
def ArgDeclRef : ArgType { let Name = "const DeclRefExpr *"; }
-def ArgDesc : ArgType { let Name = "const Descriptor *"; }
def ArgCCI : ArgType { let Name = "const ComparisonCategoryInfo *"; }
def ArgDecl : ArgType { let Name = "const Decl*"; }
+def ArgDesc : ArgType { let Name = "const Descriptor *"; }
+def ArgPrimType : ArgType { let Name = "PrimType"; }
//===----------------------------------------------------------------------===//
// Classes of types instructions operate on.
@@ -741,3 +743,23 @@ def GetMemberPtrDecl : Opcode;
// Debugging.
//===----------------------------------------------------------------------===//
def Dump : Opcode;
+
+def Alloc : Opcode {
+ let Args = [ArgDesc];
+}
+
+def AllocN : Opcode {
+ let Types = [IntegerTypeClass];
+ let Args = [ArgPrimType, ArgExpr, ArgBool];
+ let HasGroup = 1;
+}
+
+def AllocCN : Opcode {
+ let Types = [IntegerTypeClass];
+ let Args = [ArgDesc, ArgBool];
+ let HasGroup = 1;
+}
+
+def Free : Opcode {
+ let Args = [ArgBool];
+}
diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h
index 609367a462b00..1cd0e67b65b85 100644
--- a/clang/lib/AST/Interp/Pointer.h
+++ b/clang/lib/AST/Interp/Pointer.h
@@ -618,6 +618,7 @@ class Pointer {
friend class DeadBlock;
friend class MemberPointer;
friend struct InitMap;
+ friend class DynamicAllocator;
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
diff --git a/clang/test/AST/Interp/new-delete.cpp b/clang/test/AST/Interp/new-delete.cpp
new file mode 100644
index 0000000000000..b7ef24e526621
--- /dev/null
+++ b/clang/test/AST/Interp/new-delete.cpp
@@ -0,0 +1,488 @@
+// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
+// RUN: %clang_cc1 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
+// RUN: %clang_cc1 -verify=ref,both %s
+// RUN: %clang_cc1 -std=c++20 -verify=ref,both %s
+
+#if __cplusplus >= 202002L
+
+constexpr int *Global = new int(12); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{pointer to heap-allocated object}} \
+ // both-note {{heap allocation performed here}}
+
+static_assert(*(new int(12)) == 12); // both-error {{not an integral constant expression}} \
+ // both-note {{allocation performed here was not deallocated}}
+
+
+constexpr int a() {
+ new int(12); // both-note {{allocation performed here was not deallocated}}
+ return 1;
+}
+static_assert(a() == 1, ""); // both-error {{not an integral constant expression}}
+
+constexpr int b() {
+ int *i = new int(12);
+ int m = *i;
+ delete(i);
+ return m;
+}
+static_assert(b() == 12, "");
+
+
+struct S {
+ int a;
+ int b;
+
+ static constexpr S *create(int a, int b) {
+ return new S(a, b);
+ }
+};
+
+constexpr int c() {
+ S *s = new S(12, 13);
+
+ int i = s->a;
+ delete s;
+
+ return i;
+}
+static_assert(c() == 12, "");
+
+/// Dynamic allocation in function ::create(), freed in function d().
+constexpr int d() {
+ S* s = S::create(12, 14);
+
+ int sum = s->a + s->b;
+ delete s;
+ return sum;
+}
+static_assert(d() == 26);
+
+
+/// Test we emit the right diagnostic for several allocations done on
+/// the same site.
+constexpr int loop() {
+ for (int i = 0; i < 10; ++i) {
+ int *a = new int[10]; // both-note {{not deallocated (along with 9 other memory leaks)}}
+ }
+
+ return 1;
+}
+static_assert(loop() == 1, ""); // both-error {{not an integral constant expression}}
+
+/// No initializer.
+constexpr int noInit() {
+ int *i = new int;
+ delete i;
+ return 0;
+}
+static_assert(noInit() == 0, "");
+
+/// Try to delete a pointer that hasn't been heap allocated.
+constexpr int notHeapAllocated() { // both-error {{never produces a constant expression}}
+ int A = 0; // both-note 2{{declared here}}
+ delete &A; // ref-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}} \
+ // expected-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}}
+
+ return 1;
+}
+static_assert(notHeapAllocated() == 1, ""); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'notHeapAllocated()'}}
+
+consteval int deleteNull() {
+ int *A = nullptr;
+ delete A;
+ return 1;
+}
+static_assert(deleteNull() == 1, "");
+
+consteval int doubleDelete() { // both-error {{never produces a constant expression}}
+ int *A = new int;
+ delete A;
+ delete A; // both-note 2{{delete of pointer that has already been deleted}}
+ return 1;
+}
+static_assert(doubleDelete() == 1); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'doubleDelete()'}}
+
+constexpr int AutoArray() {
+ auto array = new int[]{0, 1, 2, 3};
+ int ret = array[3];
+ delete [] array;
+ return ret;
+}
+
+static_assert(AutoArray() == 3);
+
+#if 0
+consteval int largeArray1(bool b) {
+ if (b) {
+ int *a = new int[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
+ delete[] a;
+ }
+ return 1;
+}
+static_assert(largeArray1(false) == 1, "");
+static_assert(largeArray1(true) == 1, ""); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'largeArray1(true)'}}
+
+consteval int largeArray2(bool b) {
+ if (b) {
+ S *a = new S[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
+ delete[] a;
+ }
+ return 1;
+}
+static_assert(largeArray2(false) == 1, "");
+static_assert(largeArray2(true) == 1, ""); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'largeArray2(true)'}}
+#endif
+namespace Arrays {
+ constexpr int d() {
+ int *Arr = new int[12];
+
+ Arr[0] = 1;
+ Arr[1] = 5;
+
+ int sum = Arr[0] + Arr[1];
+ delete[] Arr;
+ return sum;
+ }
+ static_assert(d() == 6);
+
+
+ constexpr int mismatch1() { // both-error {{never produces a constant expression}}
+ int *i = new int(12); // both-note {{allocated with 'new' here}} \
+ // both-note 2{{heap allocation performed here}}
+ delete[] i; // both-warning {{'delete[]' applied to a pointer that was allocated with 'new'}} \
+ // both-note 2{{array delete used to delete pointer to non-array object of type 'int'}}
+ return 6;
+ }
+ static_assert(mismatch1() == 6); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'mismatch1()'}}
+
+ constexpr int mismatch2() { // both-error {{never produces a constant expression}}
+ int *i = new int[12]; // both-note {{allocated with 'new[]' here}} \
+ // both-note 2{{heap allocation performed here}}
+ delete i; // both-warning {{'delete' applied to a pointer that was allocated with 'new[]'}} \
+ // both-note 2{{non-array delete used to delete pointer to array object of type 'int[12]'}}
+ return 6;
+ }
+ static_assert(mismatch2() == 6); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'mismatch2()'}}
+ /// Array of composite elements.
+ constexpr int foo() {
+ S *ss = new S[12];
+
+ ss[0].a = 12;
+
+ int m = ss[0].a;
+
+ delete[] ss;
+ return m;
+ }
+ static_assert(foo() == 12);
+
+
+
+ constexpr int ArrayInit() {
+ auto array = new int[4]{0, 1, 2, 3};
+ int ret = array[0];
+ delete [] array;
+ return ret;
+ }
+ static_assert(ArrayInit() == 0, "");
+
+ struct S {
+ float F;
+ };
+ constexpr float ArrayInit2() {
+ auto array = new S[4]{};
+ float ret = array[0].F;
+ delete [] array;
+ return ret;
+ }
+ static_assert(ArrayInit2() == 0.0f, "");
+}
+
+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;
+
+struct placement_new_arg {};
+void *operator new(std::size_t, placement_new_arg);
+void operator delete(void*, placement_new_arg);
+
+
+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(); }
+}
+
+
+
+/// FIXME: The new interpreter produces the wrong diagnostic.
+namespace PlacementNew {
+ constexpr int foo() { // both-error {{never produces a constant expression}}
+ char c[sizeof(int)];
+ new (c) int{12}; // ref-note {{call to placement 'operator new'}} \
+ // expected-note {{subexpression not valid in a constant expression}}
+ return 0;
+ }
+}
+
+namespace NowThrowNew {
+ constexpr bool erroneous_array_bound_nothrow(long long n) {
+ int *p = new (std::nothrow) int[n];
+ bool result = p != nullptr;
+ 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) == 0);
+ static_assert(!erroneous_array_bound_nothrow(1LL << 62));
+
+ struct S { int a; };
+ constexpr bool erroneous_array_bound_nothrow2(long long n) {
+ S *p = new (std::nothrow) S[n];
+ bool result = p != nullptr;
+ delete[] p;
+ return result;
+ }
+ /// This needs support for CXXConstrucExprs with non-constant array sizes.
+ static_assert(erroneous_array_bound_nothrow2(3)); // expected-error {{not an integral constant expression}}
+ static_assert(erroneous_array_bound_nothrow2(0));// expected-error {{not an integral constant expression}}
+ static_assert(erroneous_array_bound_nothrow2(-1) == 0);// expected-error {{not an integral constant expression}}
+ static_assert(!erroneous_array_bound_nothrow2(1LL << 62));// expected-error {{not an integral constant expression}}
+
+ constexpr bool evaluate_nothrow_arg() {
+ bool ok = false;
+ delete new ((ok = true, std::nothrow)) int;
+ return ok;
+ }
+ static_assert(evaluate_nothrow_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());
+
+ /// FIXME: Diagnosting placement new.
+ constexpr bool bad(int which) {
+ switch (which) {
+ case 0:
+ delete new (placement_new_arg{}) int; // ref-note {{call to placement 'operator new'}} \
+ // expected-note {{subexpression not valid in a constant expression}}
+ break;
+
+ case 1:
+ delete new ClassSpecificNew; // ref-note {{call to class-specific 'operator new'}}
+ break;
+
+ case 2:
+ delete new ClassSpecificDelete; // ref-note {{call to class-specific 'operator delete'}}
+ break;
+
+ case 3:
+ delete new DestroyingDelete; // ref-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; // ref-note {{placement new expression is not yet supported}} \
+ // expected-note {{subexpression not valid in a constant expression}}
+ break;
+ }
+
+ return true;
+ }
+ static_assert(bad(0)); // both-error {{constant expression}} \
+ // both-note {{in call}}
+ static_assert(bad(1)); // ref-error {{constant expression}} ref-note {{in call}}
+ static_assert(bad(2)); // ref-error {{constant expression}} ref-note {{in call}}
+ static_assert(bad(3)); // ref-error {{constant expression}} ref-note {{in call}}
+ static_assert(bad(4)); // both-error {{constant expression}} \
+ // both-note {{in call}}
+}
+
+
+
+
+namespace delete_random_things {
+ static_assert((delete new int, true));
+ static_assert((delete (int*)0, true));
+ int n; // both-note {{declared here}}
+ static_assert((delete &n, true)); // both-error {{}} \
+ // both-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)); // both-error {{}} \
+ // both-note {{delete of pointer to subobject }}
+ static_assert((delete (new int + 1), true)); // both-error {{}} \
+ // ref-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} \
+ // expected-note {{delete of pointer '&new int' that does not point to complete object}}
+ static_assert((delete[] (new int[3] + 1), true)); // both-error {{}} \
+ // both-note {{delete of pointer to subobject}}
+ static_assert((delete &(int&)(int&&)0, true)); // both-error {{}} \
+ // both-note {{delete of pointer '&0' that does not point to a heap-allocated object}} \
+ // both-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)); // both-error {{}} both-note {{allocation performed here was not deallocated}}
+
+ constexpr bool *f() { return new bool(true); } // both-note {{allocation performed here was not deallocated}}
+ static_assert(*f()); // both-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
+}
+
+/// From test/SemaCXX/cxx2a-consteval.cpp
+
+namespace std {
+template <typename T> struct remove_reference { using type = T; };
+template <typename T> struct remove_reference<T &> { using type = T; };
+template <typename T> struct remove_reference<T &&> { using type = T; };
+template <typename T>
+constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
+ return static_cast<typename std::remove_reference<T>::type &&>(t);
+}
+}
+
+namespace cxx2a {
+struct A {
+ int* p = new int(42); // both-note 7{{heap allocation performed here}}
+ consteval int ret_i() const { return p ? *p : 0; }
+ consteval A ret_a() const { return A{}; }
+ constexpr ~A() { delete p; }
+};
+
+consteval int by_value_a(A a) { return a.ret_i(); }
+
+consteval int const_a_ref(const A &a) {
+ return a.ret_i();
+}
+
+consteval int rvalue_ref(const A &&a) {
+ return a.ret_i();
+}
+
+consteval const A &to_lvalue_ref(const A &&a) {
+ return a;
+}
+
+void test() {
+ constexpr A a{ nullptr };
+ { int k = A().ret_i(); }
+
+ { A k = A().ret_a(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{heap-allocated object is not a constant expression}}
+ { A k = to_lvalue_ref(A()); } // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
+ // both-note {{reference to temporary is not a constant expression}} \
+ // both-note {{temporary created here}}
+ { A k = to_lvalue_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{heap-allocated object is not a constant expression}} \
+ // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
+ // both-note {{reference to temporary is not a constant expression}} \
+ // both-note {{temporary created here}}
+ { int k = A().ret_a().ret_i(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{heap-allocated object is not a constant expression}}
+ { int k = by_value_a(A()); }
+ { int k = const_a_ref(A()); }
+ { int k = const_a_ref(a); }
+ { int k = rvalue_ref(A()); }
+ { int k = rvalue_ref(std::move(a)); }
+ { int k = const_a_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{is not a constant expression}}
+ { int k = const_a_ref(to_lvalue_ref(A().ret_a())); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{is not a constant expression}}
+ { int k = const_a_ref(to_lvalue_ref(std::move(a))); }
+ { int k = by_value_a(A().ret_a()); }
+ { int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
+ { int k = (A().ret_a(), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{is not a constant expression}} \
+ // both-warning {{left operand of comma operator has no effect}}
+ { int k = (const_a_ref(A().ret_a()), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
+ // both-note {{is not a constant expression}} \
+ // both-warning {{left operand of comma operator has no effect}}
+}
+}
+
+constexpr int *const &p = new int; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{pointer to heap-allocated object}} \
+ // both-note {{allocation performed here}}
+
+constexpr const int *A[] = {nullptr, nullptr, new int{12}}; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{pointer to heap-allocated object}} \
+ // both-note {{allocation performed here}}
+
+struct Sp {
+ const int *p;
+};
+constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{pointer to heap-allocated object}} \
+ // both-note {{allocation performed here}}
+
+
+
+
+#else
+/// Make sure we reject this prior to C++20
+constexpr int a() { // both-error {{never produces a constant expression}}
+ delete new int(12); // both-note 2{{dynamic memory allocation is not permitted in constant expressions until C++20}}
+ return 1;
+}
+static_assert(a() == 1, ""); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'a()'}}
+#endif
diff --git a/clang/test/Rewriter/rewrite-modern-catch.m b/clang/test/Rewriter/rewrite-modern-catch.m
index 1900301e91129..621c7ec45bae8 100644
--- a/clang/test/Rewriter/rewrite-modern-catch.m
+++ b/clang/test/Rewriter/rewrite-modern-catch.m
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp
+// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -fexceptions -Wno-address-of-temporary -D"id=void*" -D"SEL=void*" -D"__declspec(X)=" %t-rw.cpp
void foo(id arg);
diff --git a/clang/test/SemaCXX/delete.cpp b/clang/test/SemaCXX/delete.cpp
index 08cc1766e9f7e..7d1f51cb218ce 100644
--- a/clang/test/SemaCXX/delete.cpp
+++ b/clang/test/SemaCXX/delete.cpp
@@ -1,5 +1,5 @@
// Test without PCH
-// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
// Test with PCH
// RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 1a99c6aac604f..9b4b79d215e50 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -3,6 +3,12 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20
+//
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++98 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++11 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP
// FIXME Location is (frontend)
// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}}
@@ -645,10 +651,22 @@ int *fail = dependent_array_size("hello"); // expected-note {{instantiation of}}
// FIXME: Our behavior here is incredibly inconsistent. GCC allows
// constant-folding in array bounds in new-expressions.
int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold];
-#if __cplusplus >= 201402L
+#if __cplusplus >= 201402L && !defined(NEW_INTERP)
// expected-error at -2 {{array size is not a constant expression}}
// expected-note at -3 {{cannot refer to element 12 of non-array}}
-#elif __cplusplus < 201103L
+#elif __cplusplus < 201103L && !defined(NEW_INTERP)
// expected-error at -5 {{cannot allocate object of variably modified type}}
// expected-warning at -6 {{variable length arrays in C++ are a Clang extension}}
#endif
+#ifdef NEW_INTERP
+#if __cplusplus >= 201402L
+// expected-error at -10 {{array size is not a constant expression}}
+// expected-note at -11 {{cannot refer to element 12 of non-array}}
+#elif __cplusplus >= 201103L
+// expected-error at -13 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -14 {{cannot refer to element 12 of non-array}}
+#else
+// expected-error at -16 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -17 {{cannot refer to element 12 of non-array}}
+#endif
+#endif
More information about the cfe-commits
mailing list