git: ca3747034ef4 - main - Merge commit c52392a6f464 from llvm-project (by ShengYi Hung):

From: Dimitry Andric <dim_at_FreeBSD.org>
Date: Wed, 30 Sep 2026 16:33:01 UTC
The branch main has been updated by dim:

URL: https://cgit.FreeBSD.org/src/commit/?id=ca3747034ef484b024a202d213a2e08b16066791

commit ca3747034ef484b024a202d213a2e08b16066791
Author:     Dimitry Andric <dim@FreeBSD.org>
AuthorDate: 2026-09-28 18:12:33 +0000
Commit:     Dimitry Andric <dim@FreeBSD.org>
CommitDate: 2026-09-30 16:32:47 +0000

    Merge commit c52392a6f464 from llvm-project (by ShengYi Hung):
    
      [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.
    
    This fixes "Assertion failed: (E->isGLValue() ||
    E->getType()->isFunctionType() || E->getType()->isVoidType() ||
    isa<ObjCSelectorExpr>(E->IgnoreParens())), function EvaluateLValue" when
    building the databases/mariadb123-server port.
    
    MFC after:      1 week
---
 .../clang/lib/AST/ByteCode/Compiler.cpp            | 20 ++++---
 .../llvm-project/clang/lib/AST/ByteCode/Interp.cpp | 65 +++++++++++-----------
 .../llvm-project/clang/lib/AST/ByteCode/Interp.h   |  1 +
 .../llvm-project/clang/lib/AST/ByteCode/Opcodes.td |  4 ++
 .../llvm-project/clang/lib/AST/ExprConstant.cpp    | 42 ++++++++------
 5 files changed, 71 insertions(+), 61 deletions(-)

diff --git a/contrib/llvm-project/clang/lib/AST/ByteCode/Compiler.cpp b/contrib/llvm-project/clang/lib/AST/ByteCode/Compiler.cpp
index afa3b7ea7de7..04c3d6458de0 100644
--- a/contrib/llvm-project/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/contrib/llvm-project/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3456,19 +3456,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/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.cpp b/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.cpp
index 457de2bed37d..987e32911f81 100644
--- a/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.cpp
+++ b/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.cpp
@@ -1802,9 +1802,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);
   QualType StorageType = Ptr.getFieldDesc()->getDataType(S.getASTContext());
   const ASTContext &ASTCtx = S.getASTContext();
@@ -1843,45 +1840,45 @@ 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.
-      if (S.getLangOpts().CPlusPlus26 || S.Current->isStdFunction())
-        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())
+    return true;
+
+  S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement)
+      << /*C++26 feature*/ 1 << E->getSourceRange();
   return false;
 }
 
diff --git a/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.h b/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.h
index 9d17f96c97c8..17850c8510b1 100644
--- a/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.h
+++ b/contrib/llvm-project/clang/lib/AST/ByteCode/Interp.h
@@ -3398,6 +3398,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/contrib/llvm-project/clang/lib/AST/ByteCode/Opcodes.td b/contrib/llvm-project/clang/lib/AST/ByteCode/Opcodes.td
index 57e01f7bd9da..7d41c2c63334 100644
--- a/contrib/llvm-project/clang/lib/AST/ByteCode/Opcodes.td
+++ b/contrib/llvm-project/clang/lib/AST/ByteCode/Opcodes.td
@@ -851,6 +851,10 @@ def InvalidNewDeleteExpr : Opcode {
   let Args = [ArgExpr];
 }
 
+def CheckPlacementNew : Opcode {
+  let Args = [ArgExpr];
+}
+
 def CheckNewTypeMismatchArray : Opcode {
   let Types = [IntegerTypeClass];
   let Args = [ArgExpr];
diff --git a/contrib/llvm-project/clang/lib/AST/ExprConstant.cpp b/contrib/llvm-project/clang/lib/AST/ExprConstant.cpp
index bfecae972940..fa2c42ab509c 100644
--- a/contrib/llvm-project/clang/lib/AST/ExprConstant.cpp
+++ b/contrib/llvm-project/clang/lib/AST/ExprConstant.cpp
@@ -10322,23 +10322,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>())) {
@@ -10353,15 +10351,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();