23 changes: 0 additions & 23 deletions clang/lib/AST/Interp/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,9 @@ 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;
Expand All @@ -155,14 +147,9 @@ 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) {
// Never allow reading from a non-const pointer, unless the memory
Expand All @@ -185,32 +172,22 @@ 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;
}

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.getSourceType())) {
EvalResult.setValue(*APV);
Expand Down
72 changes: 0 additions & 72 deletions clang/lib/AST/Interp/EvaluationResult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include "InterpState.h"
#include "Record.h"
#include "clang/AST/ExprCXX.h"
#include "llvm/ADT/SetVector.h"

namespace clang {
namespace interp {
Expand Down Expand Up @@ -153,11 +152,6 @@ 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();
Expand All @@ -174,71 +168,5 @@ 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
6 changes: 0 additions & 6 deletions clang/lib/AST/Interp/EvaluationResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,7 @@ 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);

QualType getSourceType() const {
if (const auto *D =
Expand All @@ -118,7 +113,6 @@ class EvaluationResult final {
void dump() const;

friend class EvalEmitter;
friend class InterpState;
};

} // namespace interp
Expand Down
52 changes: 0 additions & 52 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,58 +693,6 @@ 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, static_cast<uint64_t>(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) {
Expand Down
151 changes: 0 additions & 151 deletions clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

#include "../ExprConstShared.h"
#include "Boolean.h"
#include "DynamicAllocator.h"
#include "Floating.h"
#include "Function.h"
#include "FunctionPointer.h"
Expand Down Expand Up @@ -123,20 +122,6 @@ 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,
Expand Down Expand Up @@ -204,30 +189,6 @@ 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,
Expand Down Expand Up @@ -2806,118 +2767,6 @@ inline bool CheckDecl(InterpState &S, CodePtr OpPC, const VarDecl *VD) {
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, S.Ctx.getEvalID());
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),
S.Ctx.getEvalID());
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),
S.Ctx.getEvalID());
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
//===----------------------------------------------------------------------===//
Expand Down
11 changes: 3 additions & 8 deletions clang/lib/AST/Interp/InterpBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ class Block final {
Block(unsigned EvalID, const std::optional<unsigned> &DeclID,
const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false)
: EvalID(EvalID), DeclID(DeclID), IsStatic(IsStatic), IsExtern(IsExtern),
IsDynamic(false), Desc(Desc) {
Desc(Desc) {
assert(Desc);
}

Block(unsigned EvalID, const Descriptor *Desc, bool IsStatic = false,
bool IsExtern = false)
: EvalID(EvalID), DeclID((unsigned)-1), IsStatic(IsStatic),
IsExtern(IsExtern), IsDynamic(false), Desc(Desc) {
IsExtern(IsExtern), Desc(Desc) {
assert(Desc);
}

Expand All @@ -73,7 +73,6 @@ 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.
Expand Down Expand Up @@ -136,12 +135,11 @@ class Block final {
friend class Pointer;
friend class DeadBlock;
friend class InterpState;
friend class DynamicAllocator;

Block(unsigned EvalID, const Descriptor *Desc, bool IsExtern, bool IsStatic,
bool IsDead)
: EvalID(EvalID), IsStatic(IsStatic), IsExtern(IsExtern), IsDead(true),
IsDynamic(false), Desc(Desc) {
Desc(Desc) {
assert(Desc);
}

Expand Down Expand Up @@ -171,9 +169,6 @@ 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;
};
Expand Down
17 changes: 0 additions & 17 deletions clang/lib/AST/Interp/InterpState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ void InterpState::cleanup() {
P->PointeeStorage.BS.Pointee = nullptr;
}
}

Alloc.cleanup();
}

Frame *InterpState::getCurrentFrame() {
Expand Down Expand Up @@ -83,18 +81,3 @@ 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;
}
11 changes: 0 additions & 11 deletions clang/lib/AST/Interp/InterpState.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#define LLVM_CLANG_AST_INTERP_INTERPSTATE_H

#include "Context.h"
#include "DynamicAllocator.h"
#include "Function.h"
#include "InterpFrame.h"
#include "InterpStack.h"
Expand Down Expand Up @@ -103,23 +102,13 @@ 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.
Expand Down
24 changes: 1 addition & 23 deletions clang/lib/AST/Interp/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,12 @@ 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 ArgVarDecl : ArgType { let Name = "const VarDecl*"; }
def ArgDesc : ArgType { let Name = "const Descriptor *"; }
def ArgPrimType : ArgType { let Name = "PrimType"; }

//===----------------------------------------------------------------------===//
// Classes of types instructions operate on.
Expand Down Expand Up @@ -749,23 +747,3 @@ 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];
}
1 change: 0 additions & 1 deletion clang/lib/AST/Interp/Pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,6 @@ class Pointer {
friend class MemberPointer;
friend class InterpState;
friend struct InitMap;
friend class DynamicAllocator;

Pointer(Block *Pointee, unsigned Base, uint64_t Offset);

Expand Down
488 changes: 0 additions & 488 deletions clang/test/AST/Interp/new-delete.cpp

This file was deleted.

2 changes: 1 addition & 1 deletion clang/test/Rewriter/rewrite-modern-catch.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp
// 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);
Expand Down
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/delete.cpp
Original file line number Diff line number Diff line change
@@ -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 -fexperimental-new-constant-interpreter | FileCheck %s
// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s

// Test with PCH
// RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h
Expand Down
24 changes: 2 additions & 22 deletions clang/test/SemaCXX/new-delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23
// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c

// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,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,cxx98-23,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,cxx98-23,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,cxx98-23,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,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP

// FIXME Location is (frontend)
// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}}

Expand Down Expand Up @@ -661,22 +653,10 @@ 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 && !defined(NEW_INTERP)
#if __cplusplus >= 201402L
// expected-error@-2 {{array size is not a constant expression}}
// expected-note@-3 {{cannot refer to element 12 of non-array}}
#elif __cplusplus < 201103L && !defined(NEW_INTERP)
#elif __cplusplus < 201103L
// expected-error@-5 {{cannot allocate object of variably modified type}}
// expected-warning@-6 {{variable length arrays in C++ are a Clang extension}}
#endif
#ifdef NEW_INTERP
#if __cplusplus >= 201402L
// expected-error@-10 {{array size is not a constant expression}}
// expected-note@-11 {{cannot refer to element 12 of non-array}}
#elif __cplusplus >= 201103L
// expected-error@-13 {{only the first dimension of an allocated array may have dynamic size}}
// expected-note@-14 {{cannot refer to element 12 of non-array}}
#else
// expected-error@-16 {{only the first dimension of an allocated array may have dynamic size}}
// expected-note@-17 {{cannot refer to element 12 of non-array}}
#endif
#endif