[clang] [clang][Interp] Add inline descriptor to global variables (PR #72892)

via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 20 09:07:24 PST 2023


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Timm Baeder (tbaederr)

<details>
<summary>Changes</summary>

Some time ago, I did a similar patch for local variables.

Initializing global variables can fail as well:
```c++
constexpr int a = 1/0;
static_assert(a == 0);
```
... would succeed in the new interpreter, because we never saved the fact that `a` has  not been successfully initialized.

(Side note: The changes in `Pointer.h` make me want to refactor `Pointer::Base` to simply be relative to `Pointee->data()` and not `Pointee->rawData()`. That way, the 'root' value would always be `0` and not `0 || sizeof(InlineDescriptor)`.)

---

Patch is 23.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/72892.diff


11 Files Affected:

- (modified) clang/lib/AST/Interp/ByteCodeExprGen.cpp (+14-1) 
- (modified) clang/lib/AST/Interp/Descriptor.cpp (+14-13) 
- (modified) clang/lib/AST/Interp/Descriptor.h (+4-2) 
- (modified) clang/lib/AST/Interp/Interp.cpp (+16) 
- (modified) clang/lib/AST/Interp/Interp.h (+18-5) 
- (modified) clang/lib/AST/Interp/Pointer.cpp (+3-1) 
- (modified) clang/lib/AST/Interp/Pointer.h (+21-9) 
- (modified) clang/lib/AST/Interp/Program.cpp (+41-15) 
- (modified) clang/test/AST/Interp/cxx17.cpp (+20-3) 
- (modified) clang/test/AST/Interp/cxx23.cpp (+17-10) 
- (modified) clang/test/AST/Interp/literals.cpp (+17) 


``````````diff
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index 5dc1f9dfb10ff32..180749a75ebd22e 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -662,13 +662,26 @@ bool ByteCodeExprGen<Emitter>::VisitInitListExpr(const InitListExpr *E) {
     return this->visitInitList(E->inits(), E);
 
   if (T->isArrayType()) {
-    // FIXME: Array fillers.
     unsigned ElementIndex = 0;
     for (const Expr *Init : E->inits()) {
       if (!this->visitArrayElemInit(ElementIndex, Init))
         return false;
       ++ElementIndex;
     }
+
+    // Expand the filler expression.
+    // FIXME: This should go away.
+    if (const Expr *Filler = E->getArrayFiller()) {
+      const ConstantArrayType *CAT =
+          Ctx.getASTContext().getAsConstantArrayType(E->getType());
+      uint64_t NumElems = CAT->getSize().getZExtValue();
+
+      for (; ElementIndex != NumElems; ++ElementIndex) {
+        if (!this->visitArrayElemInit(ElementIndex, Filler))
+          return false;
+      }
+    }
+
     return true;
   }
 
diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp
index 59a952135a2d809..7330295132618ed 100644
--- a/clang/lib/AST/Interp/Descriptor.cpp
+++ b/clang/lib/AST/Interp/Descriptor.cpp
@@ -243,18 +243,19 @@ Descriptor::Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD,
                        bool IsMutable)
     : Source(D), ElemSize(primSize(Type)), Size(ElemSize * NumElems),
       MDSize(MD.value_or(0)),
-      AllocSize(align(Size) + sizeof(InitMapPtr) + MDSize), IsConst(IsConst),
-      IsMutable(IsMutable), IsTemporary(IsTemporary), IsArray(true),
-      CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)),
-      MoveFn(getMoveArrayPrim(Type)) {
+      AllocSize(align(MDSize) + align(Size) + sizeof(InitMapPtr)),
+      IsConst(IsConst), IsMutable(IsMutable), IsTemporary(IsTemporary),
+      IsArray(true), CtorFn(getCtorArrayPrim(Type)),
+      DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) {
   assert(Source && "Missing source");
 }
 
 /// Primitive unknown-size arrays.
-Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary,
-                       UnknownSize)
-    : Source(D), ElemSize(primSize(Type)), Size(UnknownSizeMark), MDSize(0),
-      AllocSize(alignof(void *) + sizeof(InitMapPtr)), IsConst(true),
+Descriptor::Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD,
+                       bool IsTemporary, UnknownSize)
+    : Source(D), ElemSize(primSize(Type)), Size(UnknownSizeMark),
+      MDSize(MD.value_or(0)),
+      AllocSize(MDSize + sizeof(InitMapPtr) + alignof(void *)), IsConst(true),
       IsMutable(false), IsTemporary(IsTemporary), IsArray(true),
       CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)),
       MoveFn(getMoveArrayPrim(Type)) {
@@ -275,12 +276,12 @@ Descriptor::Descriptor(const DeclTy &D, const Descriptor *Elem, MetadataSize MD,
 }
 
 /// Unknown-size arrays of composite elements.
-Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, bool IsTemporary,
-                       UnknownSize)
+Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD,
+                       bool IsTemporary, UnknownSize)
     : Source(D), ElemSize(Elem->getAllocSize() + sizeof(InlineDescriptor)),
-      Size(UnknownSizeMark), MDSize(0),
-      AllocSize(alignof(void *) + sizeof(InitMapPtr)), ElemDesc(Elem),
-      IsConst(true), IsMutable(false), IsTemporary(IsTemporary), IsArray(true),
+      Size(UnknownSizeMark), MDSize(MD.value_or(0)),
+      AllocSize(MDSize + alignof(void *)), ElemDesc(Elem), IsConst(true),
+      IsMutable(false), IsTemporary(IsTemporary), IsArray(true),
       CtorFn(ctorArrayDesc), DtorFn(dtorArrayDesc), MoveFn(moveArrayDesc) {
   assert(Source && "Missing source");
 }
diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h
index 8135f3d12f7035c..b0ffc21b7ea960f 100644
--- a/clang/lib/AST/Interp/Descriptor.h
+++ b/clang/lib/AST/Interp/Descriptor.h
@@ -128,14 +128,16 @@ struct Descriptor final {
              bool IsConst, bool IsTemporary, bool IsMutable);
 
   /// Allocates a descriptor for an array of primitives of unknown size.
-  Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, UnknownSize);
+  Descriptor(const DeclTy &D, PrimType Type, MetadataSize MDSize,
+             bool IsTemporary, UnknownSize);
 
   /// Allocates a descriptor for an array of composites.
   Descriptor(const DeclTy &D, const Descriptor *Elem, MetadataSize MD,
              unsigned NumElems, bool IsConst, bool IsTemporary, bool IsMutable);
 
   /// Allocates a descriptor for an array of composites of unknown size.
-  Descriptor(const DeclTy &D, Descriptor *Elem, bool IsTemporary, UnknownSize);
+  Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD,
+             bool IsTemporary, UnknownSize);
 
   /// Allocates a descriptor for a record.
   Descriptor(const DeclTy &D, Record *R, MetadataSize MD, bool IsConst,
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index 13b77e9a87725c7..69d6504696311df 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -289,6 +289,22 @@ bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
   return false;
 }
 
+bool CheckGlobalInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (Ptr.isInitialized())
+    return true;
+
+  const VarDecl *VD = cast<VarDecl>(Ptr.getDeclDesc()->asValueDecl());
+  if ((S.getLangOpts().CPlusPlus && !VD->hasConstantInitialization() &&
+       VD->mightBeUsableInConstantExpressions(S.getCtx())) ||
+      ((S.getLangOpts().CPlusPlus || S.getLangOpts().OpenCL) &&
+       !S.getLangOpts().CPlusPlus11 && !VD->hasICEInitializer(S.getCtx()))) {
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
+    S.Note(VD->getLocation(), diag::note_declared_at);
+  }
+  return false;
+}
+
 bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
   if (!CheckDummy(S, OpPC, Ptr))
     return false;
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 4f7778bdd2ff333..3a665c12d663b25 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -86,6 +86,8 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
 
 bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
                       AccessKinds AK);
+/// Check if a global variable is initialized.
+bool CheckGlobalInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
 
 /// Checks if a value can be stored in a block.
 bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
@@ -1004,10 +1006,16 @@ bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) {
 
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
-  const Block *B = S.P.getGlobal(I);
-  if (B->isExtern())
+  const Pointer &Ptr = S.P.getPtrGlobal(I);
+  if (Ptr.isExtern())
     return false;
-  S.Stk.push<T>(B->deref<T>());
+
+  // If a global variable is uninitialized, that means the initialize we've
+  // compiled for it wasn't a constant expression. Diagnose that.
+  if (!CheckGlobalInitialized(S, OpPC, Ptr))
+    return false;
+
+  S.Stk.push<T>(Ptr.deref<T>());
   return true;
 }
 
@@ -1019,7 +1027,9 @@ bool SetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
 
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
-  S.P.getGlobal(I)->deref<T>() = S.Stk.pop<T>();
+  const Pointer &P = S.P.getGlobal(I);
+  P.deref<T>() = S.Stk.pop<T>();
+  P.initialize();
   return true;
 }
 
@@ -1035,7 +1045,10 @@ bool InitGlobalTemp(InterpState &S, CodePtr OpPC, uint32_t I,
   APValue *Cached = Temp->getOrCreateValue(true);
   *Cached = APV;
 
-  S.P.getGlobal(I)->deref<T>() = S.Stk.pop<T>();
+  const Pointer &P = S.P.getGlobal(I);
+  P.deref<T>() = S.Stk.pop<T>();
+  P.initialize();
+
   return true;
 }
 
diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp
index e979b99b0fdd0a0..88b945b7902cf22 100644
--- a/clang/lib/AST/Interp/Pointer.cpp
+++ b/clang/lib/AST/Interp/Pointer.cpp
@@ -19,7 +19,9 @@
 using namespace clang;
 using namespace clang::interp;
 
-Pointer::Pointer(Block *Pointee) : Pointer(Pointee, 0, 0) {}
+Pointer::Pointer(Block *Pointee)
+    : Pointer(Pointee, Pointee->getDescriptor()->getMetadataSize(),
+              Pointee->getDescriptor()->getMetadataSize()) {}
 
 Pointer::Pointer(Block *Pointee, unsigned BaseAndOffset)
     : Pointer(Pointee, BaseAndOffset, BaseAndOffset) {}
diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h
index a8f6e62fa76d356..f5e76b253931c8b 100644
--- a/clang/lib/AST/Interp/Pointer.h
+++ b/clang/lib/AST/Interp/Pointer.h
@@ -134,7 +134,8 @@ class Pointer {
 
     // Pointer to an array of base types - enter block.
     if (Base == RootPtrMark)
-      return Pointer(Pointee, 0, Offset == 0 ? Offset : PastEndMark);
+      return Pointer(Pointee, sizeof(InlineDescriptor),
+                     Offset == 0 ? Offset : PastEndMark);
 
     // Pointer is one past end - magic offset marks that.
     if (isOnePastEnd())
@@ -179,7 +180,7 @@ class Pointer {
       return *this;
 
     // If at base, point to an array of base types.
-    if (Base == 0)
+    if (Base == 0 || Base == sizeof(InlineDescriptor))
       return Pointer(Pointee, RootPtrMark, 0);
 
     // Step into the containing array, if inside one.
@@ -196,7 +197,10 @@ class Pointer {
   /// Checks if the pointer is live.
   bool isLive() const { return Pointee && !Pointee->IsDead; }
   /// Checks if the item is a field in an object.
-  bool isField() const { return Base != 0 && Base != RootPtrMark; }
+  bool isField() const {
+    return Base != 0 && Base != sizeof(InlineDescriptor) &&
+           Base != RootPtrMark && getFieldDesc()->asDecl();
+  }
 
   /// Accessor for information about the declaration site.
   const Descriptor *getDeclDesc() const {
@@ -227,7 +231,7 @@ class Pointer {
 
   /// Accessors for information about the innermost field.
   const Descriptor *getFieldDesc() const {
-    if (Base == 0 || Base == RootPtrMark)
+    if (Base == 0 || Base == sizeof(InlineDescriptor) || Base == RootPtrMark)
       return getDeclDesc();
     return getInlineDesc()->Desc;
   }
@@ -282,7 +286,9 @@ class Pointer {
   bool isArrayElement() const { return inArray() && Base != Offset; }
   /// Pointer points directly to a block.
   bool isRoot() const {
-    return (Base == 0 || Base == RootPtrMark) && Offset == 0;
+    return (Base == 0 || Base == sizeof(InlineDescriptor) ||
+            Base == RootPtrMark) &&
+           Offset == 0;
   }
 
   /// Returns the record descriptor of a class.
@@ -315,12 +321,16 @@ class Pointer {
 
   /// Checks if the field is mutable.
   bool isMutable() const {
-    return Base != 0 && getInlineDesc()->IsFieldMutable;
+    return Base != 0 && Base != sizeof(InlineDescriptor) &&
+           getInlineDesc()->IsFieldMutable;
   }
   /// Checks if an object was initialized.
   bool isInitialized() const;
   /// Checks if the object is active.
-  bool isActive() const { return Base == 0 || getInlineDesc()->IsActive; }
+  bool isActive() const {
+    return Base == 0 || Base == sizeof(InlineDescriptor) ||
+           getInlineDesc()->IsActive;
+  }
   /// Checks if a structure is a base class.
   bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; }
   /// Checks if the pointer pointers to a dummy value.
@@ -328,7 +338,9 @@ class Pointer {
 
   /// Checks if an object or a subfield is mutable.
   bool isConst() const {
-    return Base == 0 ? getDeclDesc()->IsConst : getInlineDesc()->IsConst;
+    return (Base == 0 || Base == sizeof(InlineDescriptor))
+               ? getDeclDesc()->IsConst
+               : getInlineDesc()->IsConst;
   }
 
   /// Returns the declaration ID.
@@ -353,7 +365,7 @@ class Pointer {
       return 1;
 
     // narrow()ed element in a composite array.
-    if (Base > 0 && Base == Offset)
+    if (Base > sizeof(InlineDescriptor) && Base == Offset)
       return 0;
 
     if (auto ElemSize = elemSize())
diff --git a/clang/lib/AST/Interp/Program.cpp b/clang/lib/AST/Interp/Program.cpp
index 52e13398163ecf7..fbcc358f9ae9348 100644
--- a/clang/lib/AST/Interp/Program.cpp
+++ b/clang/lib/AST/Interp/Program.cpp
@@ -54,11 +54,11 @@ unsigned Program::createGlobalString(const StringLiteral *S) {
   }
 
   // Create a descriptor for the string.
-  Descriptor *Desc =
-      allocateDescriptor(S, CharType, std::nullopt, S->getLength() + 1,
-                         /*isConst=*/true,
-                         /*isTemporary=*/false,
-                         /*isMutable=*/false);
+  Descriptor *Desc = allocateDescriptor(S, CharType, Descriptor::InlineDescMD,
+                                        S->getLength() + 1,
+                                        /*isConst=*/true,
+                                        /*isTemporary=*/false,
+                                        /*isMutable=*/false);
 
   // Allocate storage for the string.
   // The byte length does not include the null terminator.
@@ -67,6 +67,16 @@ unsigned Program::createGlobalString(const StringLiteral *S) {
   auto *G = new (Allocator, Sz) Global(Desc, /*isStatic=*/true,
                                        /*isExtern=*/false);
   G->block()->invokeCtor();
+
+  InlineDescriptor *ID =
+      reinterpret_cast<InlineDescriptor *>(G->block()->rawData());
+  ID->Offset = sizeof(InlineDescriptor);
+  ID->Desc = Desc;
+  ID->IsConst = true;
+  ID->IsInitialized = true;
+  ID->IsBase = false;
+  ID->IsActive = true;
+  ID->IsFieldMutable = false;
   Globals.push_back(G);
 
   // Construct the string in storage.
@@ -78,16 +88,19 @@ unsigned Program::createGlobalString(const StringLiteral *S) {
       case PT_Sint8: {
         using T = PrimConv<PT_Sint8>::T;
         Field.deref<T>() = T::from(CodePoint, BitWidth);
+        Field.initialize();
         break;
       }
       case PT_Uint16: {
         using T = PrimConv<PT_Uint16>::T;
         Field.deref<T>() = T::from(CodePoint, BitWidth);
+        Field.initialize();
         break;
       }
       case PT_Uint32: {
         using T = PrimConv<PT_Uint32>::T;
         Field.deref<T>() = T::from(CodePoint, BitWidth);
+        Field.initialize();
         break;
       }
       default:
@@ -190,12 +203,13 @@ std::optional<unsigned> Program::createGlobal(const DeclTy &D, QualType Ty,
   Descriptor *Desc;
   const bool IsConst = Ty.isConstQualified();
   const bool IsTemporary = D.dyn_cast<const Expr *>();
-  if (auto T = Ctx.classify(Ty)) {
-    Desc = createDescriptor(D, *T, std::nullopt, IsConst, IsTemporary);
-  } else {
-    Desc = createDescriptor(D, Ty.getTypePtr(), std::nullopt, IsConst,
-                            IsTemporary);
-  }
+  if (std::optional<PrimType> T = Ctx.classify(Ty))
+    Desc =
+        createDescriptor(D, *T, Descriptor::InlineDescMD, IsConst, IsTemporary);
+  else
+    Desc = createDescriptor(D, Ty.getTypePtr(), Descriptor::InlineDescMD,
+                            IsConst, IsTemporary);
+
   if (!Desc)
     return std::nullopt;
 
@@ -206,6 +220,18 @@ std::optional<unsigned> Program::createGlobal(const DeclTy &D, QualType Ty,
       Global(getCurrentDecl(), Desc, IsStatic, IsExtern);
   G->block()->invokeCtor();
 
+  // Initialize InlineDescriptor fields.
+  InlineDescriptor *ID =
+      reinterpret_cast<InlineDescriptor *>(G->block()->rawData());
+  ID->Offset = sizeof(InlineDescriptor);
+  ID->Desc = Desc;
+  ID->IsConst = true;
+  ID->IsInitialized = true;
+  ID->IsInitialized = false;
+  ID->IsBase = false;
+  ID->IsActive = true;
+  ID->IsFieldMutable = false;
+
   Globals.push_back(G);
 
   return I;
@@ -338,8 +364,8 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty,
       } else {
         // Arrays of composites. In this case, the array is a list of pointers,
         // followed by the actual elements.
-        Descriptor *ElemDesc = createDescriptor(
-            D, ElemTy.getTypePtr(), std::nullopt, IsConst, IsTemporary);
+        Descriptor *ElemDesc = createDescriptor(D, ElemTy.getTypePtr(), MDSize,
+                                                IsConst, IsTemporary);
         if (!ElemDesc)
           return nullptr;
         unsigned ElemSize =
@@ -355,14 +381,14 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty,
     // is forbidden on pointers to such objects.
     if (isa<IncompleteArrayType>(ArrayType)) {
       if (std::optional<PrimType> T = Ctx.classify(ElemTy)) {
-        return allocateDescriptor(D, *T, IsTemporary,
+        return allocateDescriptor(D, *T, MDSize, IsTemporary,
                                   Descriptor::UnknownSize{});
       } else {
         Descriptor *Desc = createDescriptor(D, ElemTy.getTypePtr(), MDSize,
                                             IsConst, IsTemporary);
         if (!Desc)
           return nullptr;
-        return allocateDescriptor(D, Desc, IsTemporary,
+        return allocateDescriptor(D, Desc, MDSize, IsTemporary,
                                   Descriptor::UnknownSize{});
       }
     }
diff --git a/clang/test/AST/Interp/cxx17.cpp b/clang/test/AST/Interp/cxx17.cpp
index e1f578a4418d9fe..76d985eb22e178a 100644
--- a/clang/test/AST/Interp/cxx17.cpp
+++ b/clang/test/AST/Interp/cxx17.cpp
@@ -1,9 +1,6 @@
 // RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -verify %s
 // RUN: %clang_cc1 -std=c++17 -verify=ref %s
 
-// ref-no-diagnostics
-// expected-no-diagnostics
-
 struct F { int a; int b;};
 constexpr F getF() {
   return {12, 3};
@@ -83,3 +80,23 @@ constexpr int b() {
   return a[0] + a[1];
 }
 static_assert(b() == 11);
+
+/// The diagnostics between the two interpreters are different here.
+struct S { int a; };
+constexpr S getS() { // expected-error {{constexpr function never produces a constant expression}} \\
+                     // ref-error {{constexpr function never produces a constant expression}}
+  (void)(1/0); // expected-note 2{{division by zero}} \
+               // expected-warning {{division by zero}} \
+               // ref-note 2{{division by zero}} \
+               // ref-warning {{division by zero}}
+  return S{12};
+}
+constexpr S s = getS(); // expected-error {{must be initialized by a constant expression}} \
+                        // expected-note {{in call to 'getS()'}} \
+                        // ref-error {{must be initialized by a constant expression}} \\
+                        // ref-note {{in call to 'getS()'}} \
+                        // ref-note {{declared here}}
+static_assert(s.a == 12, ""); // expected-error {{not an integral constant expression}} \
+                              // expected-note {{read of uninitialized object}} \
+                              // ref-error {{not an integral constant expression}} \
+                              // ref-note {{initializer of 's' is not a constant expression}}
diff --git a/clang/test/AST/Interp/cxx23.cpp b/clang/test/AST/Interp/cxx23.cpp
index e284a66626fb331..8f9ffbe5d100b9d 100644
--- a/clang/test/AST/Interp/cxx23.cpp
+++ b/clang/test/AST/Interp/cxx23.cpp
@@ -3,28 +3,35 @@
 // RUN: %clang_cc1 -std=c++20 -fsyntax-only -fcxx-exceptions -verify=expected20 %s -fexperimental-new-constant-interpreter
 // RUN: %clang_cc1 -std=c++23 -fsyntax-only -fcxx-exceptions -verify=expected23 %s -fexperimental-new-constant-interpreter
 
-
-// expected23-no-diagnostics
-
-
 /// FIXME: The new interpreter is missing all the 'control flows through...' diagnostics.
 
 constexpr int f(int n) {  // ref20-error {{constexpr function never produces a constant expression}} \
-                          // ref23-error {{constexpr function never produces a constant expression}}
+                          // ref23-error {{constexpr function never produces a constant expression}} \
+                          // expected20-error {{constexpr function never produces a constant expression}} \
+                          // expected23-error {{constexpr function never produces a constant expression}}
   static const int m = n; // ref20-note {{control flows through the definition of a static v...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/72892


More information about the cfe-commits mailing list