Author: aokblast Date: 2026-09-28T10:14:30+08:00 New Revision: c52392a6f464e5aa6bff198e4056ca58e43e0656
URL: https://github.com/llvm/llvm-project/commit/c52392a6f464e5aa6bff198e4056ca58e43e0656 DIFF: https://github.com/llvm/llvm-project/commit/c52392a6f464e5aa6bff198e4056ca58e43e0656.diff LOG: [Clang][ExprConst] Drop PRValue for nothrow new (#226753) A user defined operator new can accept prvalue for nothrow. However, it should not be a ConstExpr. Early returns by isUsableAsGlobalAllocationFunctionInConstantEvaluation instead of doing LValue evaluation. Also, move CheckPlacement new logic into new OpCode. This decouples checking from Interp.cpp to Compiler.cpp. Added: clang/test/SemaCXX/new-nothrow-by-value.cpp Modified: clang/lib/AST/ByteCode/Compiler.cpp clang/lib/AST/ByteCode/Interp.cpp clang/lib/AST/ByteCode/Interp.h clang/lib/AST/ByteCode/Opcodes.td clang/lib/AST/ExprConstant.cpp Removed: ################################################################################ diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 81c8fb0b9f17d..353fd45d8049c 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -4275,19 +4275,21 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) { // alignof(X) and X has new-extended alignment). if (PlacementArgs == 1) { const Expr *Arg1 = E->getPlacementArg(0); - if (Arg1->getType()->isNothrowT()) { + if (OperatorNew->isReservedGlobalPlacementOperator()) { + if (!this->emitCheckPlacementNew(E, E)) + return false; + PlacementDest = Arg1; + } else if ( + Arg1->getType()->isNothrowT() && + OperatorNew + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { if (!this->discard(Arg1)) return false; IsNoThrow = true; } else { - // Invalid unless we have C++26 or are in a std:: function. - if (!this->emitInvalidNewDeleteExpr(E, E)) - return false; - - // If we have a placement-new destination, we'll later use that instead - // of allocating. - if (OperatorNew->isReservedGlobalPlacementOperator()) - PlacementDest = Arg1; + // Any other placement list is invalid. This includes a user-declared + // allocation function taking std::nothrow_t, e.g. by value. + return this->emitInvalidNewDeleteExpr(E, E); } } else { // Always invalid. diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index f885c0dc9cdb2..35a5eaa9abb67 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -2884,9 +2884,6 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E, if (!S.inConstantContext() && isConstexprUnknown(Ptr)) return false; - if (!InvalidNewDeleteExpr(S, OpPC, E)) - return false; - const auto *NewExpr = cast<CXXNewExpr>(E); const ASTContext &ASTCtx = S.getASTContext(); QualType StorageType = Ptr.getType(); @@ -2930,48 +2927,46 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E, bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E) { assert(E); + const SourceInfo &Loc = S.Current->getSource(OpPC); if (const auto *NewExpr = dyn_cast<CXXNewExpr>(E)) { const FunctionDecl *OperatorNew = NewExpr->getOperatorNew(); - if (NewExpr->getNumPlacementArgs() > 0) { - // This is allowed pre-C++26, but only an std function or if - // [[msvc::constexpr]] was used. - if (S.getLangOpts().CPlusPlus26 || S.Current->isStdFunction() || - S.Current->MSVCConstexprAllowed) - return true; - - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement) - << /*C++26 feature*/ 1 << E->getSourceRange(); - } else if ( - !OperatorNew - ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_new_non_replaceable) - << isa<CXXMethodDecl>(OperatorNew) << OperatorNew; - return false; - } else if (!S.getLangOpts().CPlusPlus26 && - NewExpr->getNumPlacementArgs() == 1 && - !OperatorNew->isReservedGlobalPlacementOperator()) { - if (!S.getLangOpts().CPlusPlus26) { - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement) - << /*Unsupported*/ 0 << E->getSourceRange(); - return false; - } - return true; - } - } else { - const auto *DeleteExpr = cast<CXXDeleteExpr>(E); - const FunctionDecl *OperatorDelete = DeleteExpr->getOperatorDelete(); - if (!OperatorDelete - ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_new_non_replaceable) - << isa<CXXMethodDecl>(OperatorDelete) << OperatorDelete; + // The only new-placement list we support is (std::nothrow), and only for + // the replaceable global allocation functions. + bool IsNothrowForm = NewExpr->getNumPlacementArgs() == 1 && + NewExpr->getPlacementArg(0)->getType()->isNothrowT(); + if (NewExpr->getNumPlacementArgs() > 0 && !IsNothrowForm) { + S.FFDiag(Loc, diag::note_constexpr_new_placement) + << /*Unsupported*/ 0 << E->getSourceRange(); return false; } + + assert( + !OperatorNew->isUsableAsGlobalAllocationFunctionInConstantEvaluation()); + S.FFDiag(Loc, diag::note_constexpr_new_non_replaceable) + << isa<CXXMethodDecl>(OperatorNew) << OperatorNew; + return false; } + const auto *DeleteExpr = cast<CXXDeleteExpr>(E); + const FunctionDecl *OperatorDelete = DeleteExpr->getOperatorDelete(); + assert(!OperatorDelete + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()); + S.FFDiag(Loc, diag::note_constexpr_new_non_replaceable) + << isa<CXXMethodDecl>(OperatorDelete) << OperatorDelete; + return false; +} + +bool CheckPlacementNew(InterpState &S, CodePtr OpPC, const Expr *E) { + // Placement new is allowed in C++26. Before that, it is only allowed in a + // std:: function or if [[msvc::constexpr]] was used. + if (S.getLangOpts().CPlusPlus26 || S.Current->isStdFunction() || + S.Current->MSVCConstexprAllowed) + return true; + + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement) + << /*C++26 feature*/ 1 << E->getSourceRange(); return false; } diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index a106295022f9b..aed1e17a97479 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -4079,6 +4079,7 @@ bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) { return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size)); } bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E); +bool CheckPlacementNew(InterpState &S, CodePtr OpPC, const Expr *E); template <PrimType Name, class T = typename PrimConv<Name>::T> inline bool BitCastPrim(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index fcbd7461b6b5b..8b92654adfd6f 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -1025,6 +1025,7 @@ def CheckNewTypeMismatchArray : Opcode { let HasGroup = 1; } def InvalidNewDeleteExpr : Opcode { let Args = [ArgExpr]; } +def CheckPlacementNew : Opcode { let Args = [ArgExpr]; } def IsConstantContext: SuccessOpcode; def CheckAllocations : Opcode { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 72baa9406cdde..f7faffc767c1c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -11018,23 +11018,21 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { bool IsNothrow = false; bool IsPlacement = false; - if (E->getNumPlacementArgs() == 1 && - E->getPlacementArg(0)->getType()->isNothrowT()) { - // 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). - LValue Nothrow; - if (!EvaluateLValue(E->getPlacementArg(0), Nothrow, Info)) - return false; - IsNothrow = true; - } else if (OperatorNew->isReservedGlobalPlacementOperator()) { + // The only new-placement list we support (other than the reserved placement + // form) 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). + bool HasNothrowArg = E->getNumPlacementArgs() == 1 && + E->getPlacementArg(0)->getType()->isNothrowT(); + + if (OperatorNew->isReservedGlobalPlacementOperator()) { if (Info.CurrentCall->isStdFunction() || Info.getLangOpts().CPlusPlus26 || (Info.CurrentCall->CanEvalMSConstexpr && OperatorNew->hasAttr<MSConstexprAttr>())) { @@ -11049,15 +11047,23 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { << /*C++26 feature*/ 1 << E->getSourceRange(); return false; } - } else if (E->getNumPlacementArgs()) { + } else if (E->getNumPlacementArgs() && !HasNothrowArg) { Info.FFDiag(E, diag::note_constexpr_new_placement) << /*Unsupported*/ 0 << E->getSourceRange(); return false; } else if (!OperatorNew ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { + // [expr.const] only permits new-expressions that select a replaceable + // global allocation function. Check this before evaluating a + // (std::nothrow) placement argument. Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) << isa<CXXMethodDecl>(OperatorNew) << OperatorNew; return false; + } else if (HasNothrowArg) { + LValue Nothrow; + if (!EvaluateLValue(E->getPlacementArg(0), Nothrow, Info)) + return false; + IsNothrow = true; } const Expr *Init = E->getInitializer(); diff --git a/clang/test/SemaCXX/new-nothrow-by-value.cpp b/clang/test/SemaCXX/new-nothrow-by-value.cpp new file mode 100644 index 0000000000000..c2985ad582219 --- /dev/null +++ b/clang/test/SemaCXX/new-nothrow-by-value.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s -fexperimental-new-constant-interpreter + +// [expr.const] only permits a new-expression in a constant expression when it +// selects a replaceable global allocation function. None of them take +// std::nothrow_t by value, so a user-declared allocation function that does +// must be rejected before examining its placement argument. The constant +// evaluator used to assume the argument was always an lvalue and crashed on +// the prvalue produced here. + +namespace std { +inline constexpr struct nothrow_t { +} nothrow; +} // namespace std + +void *operator new[](__SIZE_TYPE__, std::nothrow_t) noexcept; +void *operator new(__SIZE_TYPE__, std::nothrow_t) noexcept; + +void set(int *p) { + p = (1 ? new (std::nothrow) int[1] : nullptr); + p = (1 ? new (std::nothrow) int : nullptr); +} + +constexpr bool by_value() { // expected-error {{constexpr function never produces a constant expression}} + int *p = new (std::nothrow) int; // expected-note 2{{call to placement 'operator new'}} + delete p; + return true; +} +static_assert(by_value()); // expected-error {{not an integral constant expression}} expected-note {{in call to 'by_value()'}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
