Skip to content

Commit 5286c57

Browse files
committed
[clang][bytecode] Implement placement-new
1 parent b6597f5 commit 5286c57

File tree

6 files changed

+407
-45
lines changed

6 files changed

+407
-45
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2821,12 +2821,11 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
28212821
QualType ElementType = E->getAllocatedType();
28222822
std::optional<PrimType> ElemT = classify(ElementType);
28232823
unsigned PlacementArgs = E->getNumPlacementArgs();
2824+
const FunctionDecl *OperatorNew = E->getOperatorNew();
2825+
const Expr *PlacementDest = nullptr;
28242826
bool IsNoThrow = false;
28252827

2826-
// FIXME: Better diagnostic. diag::note_constexpr_new_placement
28272828
if (PlacementArgs != 0) {
2828-
// The only new-placement list we support is of the form (std::nothrow).
2829-
//
28302829
// FIXME: There is no restriction on this, but it's not clear that any
28312830
// other form makes any sense. We get here for cases such as:
28322831
//
@@ -2835,27 +2834,43 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
28352834
// (which should presumably be valid only if N is a multiple of
28362835
// alignof(int), and in any case can't be deallocated unless N is
28372836
// alignof(X) and X has new-extended alignment).
2838-
if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
2839-
return this->emitInvalid(E);
2837+
if (PlacementArgs == 1) {
2838+
const Expr *Arg1 = E->getPlacementArg(0);
2839+
if (Arg1->getType()->isNothrowT()) {
2840+
if (!this->discard(Arg1))
2841+
return false;
2842+
IsNoThrow = true;
2843+
} else if (Ctx.getLangOpts().CPlusPlus26 &&
2844+
OperatorNew->isReservedGlobalPlacementOperator()) {
2845+
// If we have a placement-new destination, we'll later use that instead
2846+
// of allocating.
2847+
PlacementDest = Arg1;
2848+
} else {
2849+
return this->emitInvalidNewDeleteExpr(E, E);
2850+
}
28402851

2841-
if (!this->discard(E->getPlacementArg(0)))
2842-
return false;
2843-
IsNoThrow = true;
2852+
} else {
2853+
return this->emitInvalid(E);
2854+
}
2855+
} else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
2856+
return this->emitInvalidNewDeleteExpr(E, E);
28442857
}
28452858

28462859
const Descriptor *Desc;
2847-
if (ElemT) {
2848-
if (E->isArray())
2849-
Desc = nullptr; // We're not going to use it in this case.
2850-
else
2851-
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2852-
/*IsConst=*/false, /*IsTemporary=*/false,
2853-
/*IsMutable=*/false);
2854-
} else {
2855-
Desc = P.createDescriptor(
2856-
E, ElementType.getTypePtr(),
2857-
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2858-
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2860+
if (!PlacementDest) {
2861+
if (ElemT) {
2862+
if (E->isArray())
2863+
Desc = nullptr; // We're not going to use it in this case.
2864+
else
2865+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2866+
/*IsConst=*/false, /*IsTemporary=*/false,
2867+
/*IsMutable=*/false);
2868+
} else {
2869+
Desc = P.createDescriptor(
2870+
E, ElementType.getTypePtr(),
2871+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2872+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2873+
}
28592874
}
28602875

28612876
if (E->isArray()) {
@@ -2872,26 +2887,42 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
28722887

28732888
PrimType SizeT = classifyPrim(Stripped->getType());
28742889

2875-
if (!this->visit(Stripped))
2876-
return false;
2877-
2878-
if (ElemT) {
2879-
// N primitive elements.
2880-
if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
2890+
if (PlacementDest) {
2891+
if (!this->visit(PlacementDest))
2892+
return false;
2893+
if (!this->visit(Stripped))
2894+
return false;
2895+
if (!this->emitCheckNewTypeMismatchArray(SizeT, E, E))
28812896
return false;
28822897
} else {
2883-
// N Composite elements.
2884-
if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
2898+
if (!this->visit(Stripped))
28852899
return false;
2900+
2901+
if (ElemT) {
2902+
// N primitive elements.
2903+
if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
2904+
return false;
2905+
} else {
2906+
// N Composite elements.
2907+
if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
2908+
return false;
2909+
}
28862910
}
28872911

28882912
if (Init && !this->visitInitializer(Init))
28892913
return false;
28902914

28912915
} else {
2892-
// Allocate just one element.
2893-
if (!this->emitAlloc(Desc, E))
2894-
return false;
2916+
if (PlacementDest) {
2917+
if (!this->visit(PlacementDest))
2918+
return false;
2919+
if (!this->emitCheckNewTypeMismatch(E, E))
2920+
return false;
2921+
} else {
2922+
// Allocate just one element.
2923+
if (!this->emitAlloc(Desc, E))
2924+
return false;
2925+
}
28952926

28962927
if (Init) {
28972928
if (ElemT) {
@@ -2918,6 +2949,11 @@ template <class Emitter>
29182949
bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
29192950
const Expr *Arg = E->getArgument();
29202951

2952+
const FunctionDecl *OperatorDelete = E->getOperatorDelete();
2953+
2954+
if (!OperatorDelete->isReplaceableGlobalAllocationFunction())
2955+
return this->emitInvalidNewDeleteExpr(E, E);
2956+
29212957
// Arg must be an lvalue.
29222958
if (!this->visit(Arg))
29232959
return false;

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,81 @@ void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
986986
}
987987
}
988988

989+
bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
990+
std::optional<uint64_t> ArraySize) {
991+
const Pointer &Ptr = S.Stk.peek<Pointer>();
992+
993+
if (!CheckStore(S, OpPC, Ptr))
994+
return false;
995+
996+
const auto *NewExpr = cast<CXXNewExpr>(E);
997+
QualType StorageType = Ptr.getType();
998+
999+
if (isa_and_nonnull<CXXNewExpr>(Ptr.getFieldDesc()->asExpr())) {
1000+
// FIXME: Are there other cases where this is a problem?
1001+
StorageType = StorageType->getPointeeType();
1002+
}
1003+
1004+
const ASTContext &ASTCtx = S.getASTContext();
1005+
QualType AllocType;
1006+
if (ArraySize) {
1007+
AllocType = ASTCtx.getConstantArrayType(
1008+
NewExpr->getAllocatedType(),
1009+
APInt(64, static_cast<uint64_t>(*ArraySize), false), nullptr,
1010+
ArraySizeModifier::Normal, 0);
1011+
} else {
1012+
AllocType = NewExpr->getAllocatedType();
1013+
}
1014+
1015+
unsigned StorageSize = 1;
1016+
unsigned AllocSize = 1;
1017+
if (const auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
1018+
AllocSize = CAT->getZExtSize();
1019+
if (const auto *CAT = dyn_cast<ConstantArrayType>(StorageType))
1020+
StorageSize = CAT->getZExtSize();
1021+
1022+
if (AllocSize > StorageSize ||
1023+
!ASTCtx.hasSimilarType(ASTCtx.getBaseElementType(AllocType),
1024+
ASTCtx.getBaseElementType(StorageType))) {
1025+
S.FFDiag(S.Current->getLocation(OpPC),
1026+
diag::note_constexpr_placement_new_wrong_type)
1027+
<< StorageType << AllocType;
1028+
return false;
1029+
}
1030+
1031+
return true;
1032+
}
1033+
1034+
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E) {
1035+
assert(E);
1036+
const auto &Loc = S.Current->getSource(OpPC);
1037+
1038+
if (const auto *NewExpr = dyn_cast<CXXNewExpr>(E)) {
1039+
const FunctionDecl *OperatorNew = NewExpr->getOperatorNew();
1040+
1041+
if (!S.getLangOpts().CPlusPlus26 && NewExpr->getNumPlacementArgs() > 0) {
1042+
S.FFDiag(Loc, diag::note_constexpr_new_placement)
1043+
<< /*C++26 feature*/ 1 << E->getSourceRange();
1044+
} else if (NewExpr->getNumPlacementArgs() == 1 &&
1045+
!OperatorNew->isReservedGlobalPlacementOperator()) {
1046+
S.FFDiag(Loc, diag::note_constexpr_new_placement)
1047+
<< /*Unsupported*/ 0 << E->getSourceRange();
1048+
} else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
1049+
S.FFDiag(Loc, diag::note_constexpr_new_non_replaceable)
1050+
<< isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
1051+
}
1052+
} else {
1053+
const auto *DeleteExpr = cast<CXXDeleteExpr>(E);
1054+
const FunctionDecl *OperatorDelete = DeleteExpr->getOperatorDelete();
1055+
if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) {
1056+
S.FFDiag(Loc, diag::note_constexpr_new_non_replaceable)
1057+
<< isa<CXXMethodDecl>(OperatorDelete) << OperatorDelete;
1058+
}
1059+
}
1060+
1061+
return false;
1062+
}
1063+
9891064
bool Interpret(InterpState &S, APValue &Result) {
9901065
// The current stack frame when we started Interpret().
9911066
// This is being used by the ops to determine wheter

clang/lib/AST/ByteCode/Interp.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3151,6 +3151,17 @@ inline bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T) {
31513151
return false;
31523152
}
31533153

3154+
/// Check if the initializer and storage types of a placement-new expression
3155+
/// match.
3156+
bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
3157+
std::optional<uint64_t> ArraySize = std::nullopt);
3158+
3159+
template <PrimType Name, class T = typename PrimConv<Name>::T>
3160+
bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) {
3161+
const auto &Size = S.Stk.pop<T>();
3162+
return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size));
3163+
}
3164+
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E);
31543165
//===----------------------------------------------------------------------===//
31553166
// Read opcode arguments
31563167
//===----------------------------------------------------------------------===//

clang/lib/AST/ByteCode/Opcodes.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,4 +786,18 @@ def Free : Opcode {
786786
let Args = [ArgBool];
787787
}
788788

789+
def CheckNewTypeMismatch : Opcode {
790+
let Args = [ArgExpr];
791+
}
792+
793+
def InvalidNewDeleteExpr : Opcode {
794+
let Args = [ArgExpr];
795+
}
796+
797+
def CheckNewTypeMismatchArray : Opcode {
798+
let Types = [IntegerTypeClass];
799+
let Args = [ArgExpr];
800+
let HasGroup = 1;
801+
}
802+
789803
def IsConstantContext: Opcode;

clang/test/AST/ByteCode/new-delete.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,10 @@ namespace std {
241241

242242

243243

244-
/// FIXME: The new interpreter produces the wrong diagnostic.
245244
namespace PlacementNew {
246245
constexpr int foo() { // both-error {{never produces a constant expression}}
247246
char c[sizeof(int)];
248-
new (c) int{12}; // ref-note {{this placement new expression is not supported in constant expressions before C++2c}} \
249-
// expected-note {{subexpression not valid in a constant expression}}
247+
new (c) int{12}; // both-note {{this placement new expression is not supported in constant expressions before C++2c}}
250248
return 0;
251249
}
252250
}
@@ -305,41 +303,38 @@ namespace placement_new_delete {
305303
}
306304
static_assert(ok());
307305

308-
/// FIXME: Diagnosting placement new.
309306
constexpr bool bad(int which) {
310307
switch (which) {
311308
case 0:
312-
delete new (placement_new_arg{}) int; // ref-note {{this placement new expression is not supported in constant expressions}} \
313-
// expected-note {{subexpression not valid in a constant expression}}
309+
delete new (placement_new_arg{}) int; // both-note {{this placement new expression is not supported in constant expressions}}
314310
break;
315311

316312
case 1:
317-
delete new ClassSpecificNew; // ref-note {{call to class-specific 'operator new'}}
313+
delete new ClassSpecificNew; // both-note {{call to class-specific 'operator new'}}
318314
break;
319315

320316
case 2:
321-
delete new ClassSpecificDelete; // ref-note {{call to class-specific 'operator delete'}}
317+
delete new ClassSpecificDelete; // both-note {{call to class-specific 'operator delete'}}
322318
break;
323319

324320
case 3:
325-
delete new DestroyingDelete; // ref-note {{call to class-specific 'operator delete'}}
321+
delete new DestroyingDelete; // both-note {{call to class-specific 'operator delete'}}
326322
break;
327323

328324
case 4:
329325
// FIXME: This technically follows the standard's rules, but it seems
330326
// unreasonable to expect implementations to support this.
331-
delete new (std::align_val_t{64}) Overaligned; // ref-note {{this placement new expression is not supported in constant expressions}} \
332-
// expected-note {{subexpression not valid in a constant expression}}
327+
delete new (std::align_val_t{64}) Overaligned; // both-note {{this placement new expression is not supported in constant expressions}}
333328
break;
334329
}
335330

336331
return true;
337332
}
338333
static_assert(bad(0)); // both-error {{constant expression}} \
339334
// both-note {{in call}}
340-
static_assert(bad(1)); // ref-error {{constant expression}} ref-note {{in call}}
341-
static_assert(bad(2)); // ref-error {{constant expression}} ref-note {{in call}}
342-
static_assert(bad(3)); // ref-error {{constant expression}} ref-note {{in call}}
335+
static_assert(bad(1)); // both-error {{constant expression}} both-note {{in call}}
336+
static_assert(bad(2)); // both-error {{constant expression}} both-note {{in call}}
337+
static_assert(bad(3)); // both-error {{constant expression}} both-note {{in call}}
343338
static_assert(bad(4)); // both-error {{constant expression}} \
344339
// both-note {{in call}}
345340
}
@@ -586,6 +581,18 @@ constexpr void use_after_free_2() { // both-error {{never produces a constant ex
586581
p->f(); // both-note {{member call on heap allocated object that has been deleted}}
587582
}
588583

584+
/// Just test that we reject placement-new expressions before C++2c.
585+
/// Tests for successful expressions are in placement-new.cpp
586+
namespace Placement {
587+
consteval auto ok1() { // both-error {{never produces a constant expression}}
588+
bool b;
589+
new (&b) bool(true); // both-note 2{{this placement new expression is not supported in constant expressions before C++2c}}
590+
return b;
591+
}
592+
static_assert(ok1()); // both-error {{not an integral constant expression}} \
593+
// both-note {{in call to}}
594+
}
595+
589596
#else
590597
/// Make sure we reject this prior to C++20
591598
constexpr int a() { // both-error {{never produces a constant expression}}

0 commit comments

Comments
 (0)