https://github.com/Servall4 updated 
https://github.com/llvm/llvm-project/pull/229697

>From 87b9ce63ce79665d20d72a7492fb713f83c44702 Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Wed, 7 Oct 2026 11:17:49 +0300
Subject: [PATCH 1/8] [clang] Diagnose VLA types from an enclosing function in
 local classes and blocks

A variably modified type whose array bound is evaluated in an enclosing
function cannot be used in a member function of a local class or in a
block: the size is only computed in the enclosing function, so CodeGen
crashed when emitting such a declaration.

Record the context in which each VLA bound is evaluated, and diagnose
variable and parameter declarations whose type has such a bound when they
appear across a local class or block boundary.

Fixes #229694
---
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 ++
 clang/include/clang/Sema/Sema.h               |   9 ++
 clang/lib/Sema/SemaDecl.cpp                   |  12 +++
 clang/lib/Sema/SemaExpr.cpp                   | 100 ++++++++++++++++--
 clang/lib/Sema/SemaType.cpp                   |   8 ++
 .../test/Sema/vla-enclosing-function-blocks.c |  18 ++++
 clang/test/SemaCXX/vla-enclosing-function.cpp |  56 ++++++++++
 7 files changed, 198 insertions(+), 11 deletions(-)
 create mode 100644 clang/test/Sema/vla-enclosing-function-blocks.c
 create mode 100644 clang/test/SemaCXX/vla-enclosing-function.cpp

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9208aba1445d7e..c5d1ff7a213953 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8314,6 +8314,12 @@ def err_unexpected_interface : Error<
 def err_ref_non_value : Error<"%0 does not refer to a value">;
 def err_ref_vm_type : Error<
   "cannot refer to declaration with a variably modified type inside block">;
+def err_vm_type_from_enclosing_function : Error<
+  "variably modified type %0 cannot be used in a "
+  "%select{local class|block}1 because its size is evaluated in an "
+  "enclosing function">;
+def note_vm_type_size_evaluated_here : Note<
+  "size expression is evaluated here">;
 def err_ref_flexarray_type : Error<
   "cannot refer to declaration of structure variable with flexible array 
member "
   "inside block">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7fbcc4aae7d6c5..fc8d7ce6bb425c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -7222,6 +7222,11 @@ class Sema final : public SemaBase {
   /// variable will have in the given scope.
   QualType getCapturedDeclRefType(ValueDecl *Var, SourceLocation Loc);
 
+  /// Diagnose a use of the variably modified type \p T in a local class or
+  /// block when one of its array bounds is evaluated in an enclosing function.
+  /// Returns false if a diagnostic was emitted.
+  bool CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc);
+
   /// Mark all of the declarations referenced within a particular AST node as
   /// referenced. Used when template instantiation instantiates a non-dependent
   /// type -- entities referenced by the type are now referenced.
@@ -8435,6 +8440,10 @@ class Sema final : public SemaBase {
 private:
   static BinaryOperatorKind ConvertTokenKindToBinaryOpcode(tok::TokenKind 
Kind);
 
+  /// Maps the size expression of each VLA type created inside a function,
+  /// block or lambda to the context in which that size is evaluated.
+  llvm::DenseMap<const Expr *, const DeclContext *> VLASizeExprOwners;
+
   /// Methods for marking which expressions involve dereferencing a pointer
   /// marked with the 'noderef' attribute. Expressions are checked bottom up as
   /// they are parsed, meaning that a noderef pointer may not be accessed. For
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fdbe1521165122..2db8fa88676daa 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -9002,6 +9002,14 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
   if (NewVD->isInvalidDecl())
     return;
 
+  SourceLocation VMLoc = NewVD->getLocation();
+  if (TypeSourceInfo *TSI = NewVD->getTypeSourceInfo())
+    VMLoc = TSI->getTypeLoc().getBeginLoc();
+  if (!CheckVariablyModifiedTypeUse(NewVD->getType(), VMLoc)) {
+    NewVD->setInvalidDecl();
+    return;
+  }
+
   QualType T = NewVD->getType();
 
   // Defer checking an 'auto' type until its initializer is attached.
@@ -16135,6 +16143,10 @@ ParmVarDecl *Sema::CheckParameter(DeclContext *DC, 
SourceLocation StartLoc,
                                          Context.getAdjustedParameterType(T),
                                          TSInfo, SC, nullptr);
 
+  SourceLocation VMLoc = TSInfo ? TSInfo->getTypeLoc().getBeginLoc() : NameLoc;
+  if (!CheckVariablyModifiedTypeUse(T, VMLoc))
+    New->setInvalidDecl();
+
   // Make a note if we created a new pack in the scope of a lambda, so that
   // we know that references to that pack must also be expanded within the
   // lambda scope.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 9b3384cdbdac8c..d7650ae54452a5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4652,10 +4652,12 @@ bool Sema::CheckVecStepExpr(Expr *E) {
   return CheckUnaryExprOrTypeTraitOperand(E, UETT_VecStep);
 }
 
-static void captureVariablyModifiedType(ASTContext &Context, QualType T,
-                                        CapturingScopeInfo *CSI) {
+/// Walk down into the variably modified type \p T and call \p Fn for each
+/// variable length array type in it.
+static void forEachVariableArrayType(
+    ASTContext &Context, QualType T,
+    llvm::function_ref<void(const VariableArrayType *)> Fn) {
   assert(T->isVariablyModifiedType());
-  assert(CSI != nullptr);
 
   // We're going to walk down into the type and look for VLA expressions.
   do {
@@ -4716,14 +4718,7 @@ static void captureVariablyModifiedType(ASTContext 
&Context, QualType T,
     case Type::VariableArray: {
       // Losing element qualification here is fine.
       const VariableArrayType *VAT = cast<VariableArrayType>(Ty);
-
-      // Unknown size indication requires no size computation.
-      // Otherwise, evaluate and record it.
-      auto Size = VAT->getSizeExpr();
-      if (Size && !CSI->isVLATypeCaptured(VAT) &&
-          (isa<CapturedRegionScopeInfo>(CSI) || isa<LambdaScopeInfo>(CSI)))
-        CSI->addVLATypeCapture(Size->getExprLoc(), VAT, Context.getSizeType());
-
+      Fn(VAT);
       T = VAT->getElementType();
       break;
     }
@@ -4774,6 +4769,89 @@ static void captureVariablyModifiedType(ASTContext 
&Context, QualType T,
   } while (!T.isNull() && T->isVariablyModifiedType());
 }
 
+static void captureVariablyModifiedType(ASTContext &Context, QualType T,
+                                        CapturingScopeInfo *CSI) {
+  assert(CSI != nullptr);
+  forEachVariableArrayType(Context, T, [&](const VariableArrayType *VAT) {
+    // Unknown size indication requires no size computation.
+    // Otherwise, evaluate and record it.
+    auto Size = VAT->getSizeExpr();
+    if (Size && !CSI->isVLATypeCaptured(VAT) &&
+        (isa<CapturedRegionScopeInfo>(CSI) || isa<LambdaScopeInfo>(CSI)))
+      CSI->addVLATypeCapture(Size->getExprLoc(), VAT, Context.getSizeType());
+  });
+}
+
+/// Find what separates \p DC from \p Owner, the context in which a VLA bound
+/// is evaluated: 0 for a local class, 1 for a block, or std::nullopt if the
+/// bound is available in \p DC or \p Owner does not enclose it.
+static std::optional<unsigned> findVLABoundBarrier(const DeclContext *DC,
+                                                   const DeclContext *Owner) {
+  std::optional<unsigned> Barrier;
+  for (; DC; DC = DC->getParent()) {
+    if (DC == Owner)
+      return Barrier;
+    // Lambdas and captured regions can capture a VLA bound (see
+    // captureVariablyModifiedType), so they are not barriers here.
+    if (isLambdaCallOperator(DC) || isa<CapturedDecl>(DC))
+      continue;
+    if (const auto *RD = dyn_cast<CXXRecordDecl>(DC); RD && RD->isLambda())
+      continue;
+    if (isa<BlockDecl>(DC)) {
+      if (!Barrier)
+        Barrier = 1;
+      continue;
+    }
+    if (isa<RecordDecl>(DC) && !Barrier)
+      Barrier = 0;
+  }
+  return std::nullopt;
+}
+
+static bool isInLocalClassOrBlock(const DeclContext *DC) {
+  for (; DC; DC = DC->getParent()) {
+    if (isa<BlockDecl>(DC))
+      return true;
+    if (const auto *RD = dyn_cast<RecordDecl>(DC)) {
+      if (const auto *CRD = dyn_cast<CXXRecordDecl>(RD); CRD && 
CRD->isLambda())
+        continue;
+      if (RD->getParentFunctionOrMethod())
+        return true;
+    }
+  }
+  return false;
+}
+
+bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) {
+  if (VLASizeExprOwners.empty() || T.isNull())
+    return true;
+  // Deduced types (auto, __auto_type) do not propagate the variably modified
+  // bit, so look at the canonical type.
+  QualType CanonT = T.getCanonicalType();
+  if (!CanonT->isVariablyModifiedType() || !isInLocalClassOrBlock(CurContext))
+    return true;
+
+  const Expr *BadSize = nullptr;
+  std::optional<unsigned> Barrier;
+  forEachVariableArrayType(Context, CanonT, [&](const VariableArrayType *VAT) {
+    const Expr *Size = VAT->getSizeExpr();
+    if (BadSize || !Size)
+      return;
+    auto It = VLASizeExprOwners.find(Size);
+    if (It == VLASizeExprOwners.end())
+      return;
+    Barrier = findVLABoundBarrier(CurContext, It->second);
+    if (Barrier)
+      BadSize = Size;
+  });
+  if (!BadSize)
+    return true;
+
+  Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << *Barrier;
+  Diag(BadSize->getBeginLoc(), diag::note_vm_type_size_evaluated_here);
+  return false;
+}
+
 bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType,
                                             SourceLocation OpLoc,
                                             SourceRange ExprRange,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 202bd486321f94..3bcc09a571c765 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -2370,6 +2370,14 @@ QualType Sema::BuildArrayType(QualType T, 
ArraySizeModifier ASM,
     }
   }
 
+  // Remember where the bound of a VLA is evaluated. Bounds in a function
+  // prototype are evaluated in that function, which doesn't exist yet;
+  // those are not recorded and never diagnosed.
+  if (const VariableArrayType *VAT = Context.getAsVariableArrayType(T))
+    if (CurContext->isFunctionOrMethod())
+      if (const Expr *Size = VAT->getSizeExpr())
+        VLASizeExprOwners.try_emplace(Size, CurContext);
+
   return T;
 }
 
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
new file mode 100644
index 00000000000000..42961dcf3bd1c8
--- /dev/null
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+
+// GH229694
+int size_tab(void);
+int main(void) {
+  __auto_type tab = (int(*)[size_tab()])0; // expected-note {{size expression 
is evaluated here}}
+  (^ int(__typeof(tab) arr) { return sizeof(*arr); })(tab); // expected-error 
{{cannot be used in a block}}
+}
+
+void var_in_block(void) {
+  typedef int X[size_tab()]; // expected-note {{size expression is evaluated 
here}}
+  ^{ X x; }(); // expected-error {{cannot be used in a block}}
+}
+
+// Valid: VLA declared inside the block itself.
+int own_vla_in_block(void) {
+  return (^ int(void) { int a[size_tab()]; return sizeof(a); })();
+}
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
new file mode 100644
index 00000000000000..3e9ec40a766a01
--- /dev/null
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -0,0 +1,56 @@
+// RUN: %clang_cc1 -fsyntax-only -Wno-vla-cxx-extension -verify %s
+
+// GH229694
+int foo();
+
+void ctor() {
+  using X = int[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class because its size is evaluated in an enclosing function}}
+}
+
+void sizeof_enclosing_vla_var() {
+  int a[foo()]; // expected-note {{'a' declared here}}
+  struct S { unsigned long f() { return sizeof(a); } }; // expected-error 
{{reference to local variable 'a' declared in enclosing function}}
+}
+
+void decltype_var() {
+  int a[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S { void f() { decltype(a) b; } }; // expected-error {{cannot be used 
in a local class}}
+}
+
+void nested_local_class() {
+  using X = int[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S {
+    void f() { struct T { T() { X x; } }; } // expected-error {{cannot be used 
in a local class}}
+  };
+}
+
+void pointer_to_vla() {
+  using P = int (*)[foo()]; // expected-note {{size expression is evaluated 
here}}
+  struct S { unsigned long f(P p) { return sizeof(*p); } }; // expected-error 
{{cannot be used in a local class}}
+}
+
+template <typename T> void in_template() {
+  using X = int[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}
+}
+
+// Valid: no diagnostics.
+int same_function() {
+  using X = int[foo()];
+  X x;
+  return sizeof(X) + sizeof(x);
+}
+
+void own_vla_in_local_class() {
+  struct S { void f() { int a[foo()]; (void)sizeof(a); } };
+}
+
+void param_vla_in_local_class() {
+  struct S { unsigned long f(int n, int (*p)[n]) { return sizeof(*p); } };
+}
+
+int lambda_sizeof() {
+  using X = int[foo()];
+  return [&] { return (int)sizeof(X); }();
+}

>From 91c1b302f647de239c1aea348c8e4f9f050cd38a Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Thu, 8 Oct 2026 09:57:42 +0300
Subject: [PATCH 2/8] Also reject member typedefs of such types in local
 classes

Diagnose a typedef or alias member of a class that names a variably
modified type whose bound is evaluated in an enclosing function, as GCC
does, and don't diagnose uses of such a typedef again.

Add the example from GH55686 and reference that issue in the tests.
---
 clang/lib/Sema/SemaDecl.cpp                     |  9 +++++++++
 clang/lib/Sema/SemaExpr.cpp                     |  5 +++++
 clang/test/Sema/vla-enclosing-function-blocks.c |  2 +-
 clang/test/SemaCXX/vla-enclosing-function.cpp   | 17 ++++++++++++++++-
 4 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2db8fa88676daa..e43ac27c4110f4 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7017,6 +7017,15 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, 
TypedefNameDecl *NewTD) {
   // that redeclarations will match.
   TypeSourceInfo *TInfo = NewTD->getTypeSourceInfo();
   QualType T = TInfo->getType();
+
+  // A member typedef cannot name a variably modified type whose bound is
+  // evaluated in an enclosing function.
+  if (NewTD->getDeclContext()->isRecord() &&
+      !CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) {
+    NewTD->setInvalidDecl();
+    return;
+  }
+
   if (T->isVariablyModifiedType()) {
     setFunctionHasBranchProtectedScope();
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d7650ae54452a5..40a967ec8eb63c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4847,6 +4847,11 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, 
SourceLocation Loc) {
   if (!BadSize)
     return true;
 
+  // A member typedef naming such a type has already been diagnosed.
+  if (const auto *TT = T->getAs<TypedefType>();
+      TT && TT->getDecl()->isInvalidDecl())
+    return false;
+
   Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << *Barrier;
   Diag(BadSize->getBeginLoc(), diag::note_vm_type_size_evaluated_here);
   return false;
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
index 42961dcf3bd1c8..2306087f68768e 100644
--- a/clang/test/Sema/vla-enclosing-function-blocks.c
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
 
-// GH229694
+// GH55686
 int size_tab(void);
 int main(void) {
   __auto_type tab = (int(*)[size_tab()])0; // expected-note {{size expression 
is evaluated here}}
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
index 3e9ec40a766a01..5ee408c7544dee 100644
--- a/clang/test/SemaCXX/vla-enclosing-function.cpp
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -Wno-vla-cxx-extension -verify %s
 
-// GH229694
+// GH55686
 int foo();
 
 void ctor() {
@@ -35,6 +35,21 @@ template <typename T> void in_template() {
   struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}
 }
 
+namespace GH55686 {
+void f(int n) {
+  using T = int[n]; // expected-note 2 {{size expression is evaluated here}}
+  struct A {
+    using U = T; // expected-error {{cannot be used in a local class}}
+    void f() { U u; }
+    void g() { T t; } // expected-error {{cannot be used in a local class}}
+  };
+  A::U u;
+  A a;
+  a.f();
+  a.g();
+}
+} // namespace GH55686
+
 // Valid: no diagnostics.
 int same_function() {
   using X = int[foo()];

>From 4225f28176f3f882f267c5a6e02ae1941cecc656 Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Wed, 14 Oct 2026 10:20:25 +0300
Subject: [PATCH 3/8] Handle VLA bound captures and diagnostics in one function

---
 clang/include/clang/Sema/Sema.h               |   7 +-
 clang/lib/Sema/SemaDecl.cpp                   |   5 +-
 clang/lib/Sema/SemaExpr.cpp                   | 125 +++++++++---------
 .../vla-lambda-enclosing-function.cpp         |  27 ++++
 .../test/Sema/vla-enclosing-function-blocks.c |   5 +
 clang/test/SemaCXX/vla-enclosing-function.cpp |   5 +
 6 files changed, 101 insertions(+), 73 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index fc8d7ce6bb425c..858f763ceaf306 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -7222,9 +7222,10 @@ class Sema final : public SemaBase {
   /// variable will have in the given scope.
   QualType getCapturedDeclRefType(ValueDecl *Var, SourceLocation Loc);
 
-  /// Diagnose a use of the variably modified type \p T in a local class or
-  /// block when one of its array bounds is evaluated in an enclosing function.
-  /// Returns false if a diagnostic was emitted.
+  /// Handle a use of the variably modified type \p T in the current context.
+  /// Array bounds evaluated in an enclosing context are captured by the
+  /// lambdas in between; if a local class or a block is in between instead,
+  /// the use is diagnosed. Returns false if the use is invalid.
   bool CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc);
 
   /// Mark all of the declarations referenced within a particular AST node as
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index e43ac27c4110f4..3c0dd3740e9ddf 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7018,10 +7018,7 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, 
TypedefNameDecl *NewTD) {
   TypeSourceInfo *TInfo = NewTD->getTypeSourceInfo();
   QualType T = TInfo->getType();
 
-  // A member typedef cannot name a variably modified type whose bound is
-  // evaluated in an enclosing function.
-  if (NewTD->getDeclContext()->isRecord() &&
-      !CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) {
+  if (!CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) {
     NewTD->setInvalidDecl();
     return;
   }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 40a967ec8eb63c..bfad1c3cf32468 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4782,44 +4782,20 @@ static void captureVariablyModifiedType(ASTContext 
&Context, QualType T,
   });
 }
 
-/// Find what separates \p DC from \p Owner, the context in which a VLA bound
-/// is evaluated: 0 for a local class, 1 for a block, or std::nullopt if the
-/// bound is available in \p DC or \p Owner does not enclose it.
-static std::optional<unsigned> findVLABoundBarrier(const DeclContext *DC,
-                                                   const DeclContext *Owner) {
-  std::optional<unsigned> Barrier;
-  for (; DC; DC = DC->getParent()) {
-    if (DC == Owner)
-      return Barrier;
-    // Lambdas and captured regions can capture a VLA bound (see
-    // captureVariablyModifiedType), so they are not barriers here.
-    if (isLambdaCallOperator(DC) || isa<CapturedDecl>(DC))
-      continue;
-    if (const auto *RD = dyn_cast<CXXRecordDecl>(DC); RD && RD->isLambda())
-      continue;
-    if (isa<BlockDecl>(DC)) {
-      if (!Barrier)
-        Barrier = 1;
-      continue;
-    }
-    if (isa<RecordDecl>(DC) && !Barrier)
-      Barrier = 0;
-  }
-  return std::nullopt;
-}
-
-static bool isInLocalClassOrBlock(const DeclContext *DC) {
-  for (; DC; DC = DC->getParent()) {
-    if (isa<BlockDecl>(DC))
-      return true;
-    if (const auto *RD = dyn_cast<RecordDecl>(DC)) {
-      if (const auto *CRD = dyn_cast<CXXRecordDecl>(RD); CRD && 
CRD->isLambda())
-        continue;
-      if (RD->getParentFunctionOrMethod())
-        return true;
-    }
+/// Find the capturing scope that is being built for the lambda call operator
+/// or captured declaration \p DC, if any.
+static CapturingScopeInfo *
+findCapturingScope(ArrayRef<FunctionScopeInfo *> Scopes,
+                   const DeclContext *DC) {
+  for (FunctionScopeInfo *FSI : llvm::reverse(Scopes)) {
+    if (auto *LSI = dyn_cast<LambdaScopeInfo>(FSI);
+        LSI && LSI->CallOperator == DC)
+      return LSI;
+    if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(FSI);
+        CRSI && CRSI->TheCapturedDecl == DC)
+      return CRSI;
   }
-  return false;
+  return nullptr;
 }
 
 bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) {
@@ -4828,11 +4804,11 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, 
SourceLocation Loc) {
   // Deduced types (auto, __auto_type) do not propagate the variably modified
   // bit, so look at the canonical type.
   QualType CanonT = T.getCanonicalType();
-  if (!CanonT->isVariablyModifiedType() || !isInLocalClassOrBlock(CurContext))
+  if (!CanonT->isVariablyModifiedType())
     return true;
 
   const Expr *BadSize = nullptr;
-  std::optional<unsigned> Barrier;
+  unsigned BadKind = 0;
   forEachVariableArrayType(Context, CanonT, [&](const VariableArrayType *VAT) {
     const Expr *Size = VAT->getSizeExpr();
     if (BadSize || !Size)
@@ -4840,19 +4816,56 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, 
SourceLocation Loc) {
     auto It = VLASizeExprOwners.find(Size);
     if (It == VLASizeExprOwners.end())
       return;
-    Barrier = findVLABoundBarrier(CurContext, It->second);
-    if (Barrier)
+
+    // Walk from the use to the context in which the bound is evaluated.
+    // Lambdas and captured regions on the way capture the bound; a local
+    // class or a block cannot see it.
+    SmallVector<CapturingScopeInfo *, 2> Capturers;
+    std::optional<unsigned> Barrier;
+    const DeclContext *DC = CurContext;
+    for (; DC && DC != It->second; DC = DC->getParent()) {
+      if (isLambdaCallOperator(DC) || isa<CapturedDecl>(DC)) {
+        if (CapturingScopeInfo *CSI = findCapturingScope(FunctionScopes, DC))
+          Capturers.push_back(CSI);
+      } else if (isa<BlockDecl>(DC)) {
+        Barrier = Barrier.value_or(1);
+      } else if (const auto *RD = dyn_cast<RecordDecl>(DC)) {
+        if (const auto *CRD = dyn_cast<CXXRecordDecl>(RD);
+            !CRD || !CRD->isLambda())
+          Barrier = Barrier.value_or(0);
+      }
+    }
+    // Nothing to do if the bound is not evaluated in an enclosing context.
+    if (!DC)
+      return;
+    if (Barrier) {
       BadSize = Size;
+      BadKind = *Barrier;
+      return;
+    }
+    // VAT is a canonical type; the bound may already have been captured
+    // through a different type node with the same size expression.
+    for (CapturingScopeInfo *CSI : Capturers)
+      if (llvm::none_of(CSI->Captures, [&](const Capture &C) {
+            return C.isVLATypeCapture() &&
+                   C.getCapturedVLAType()->getSizeExpr() == Size;
+          }))
+        CSI->addVLATypeCapture(Size->getExprLoc(), VAT, Context.getSizeType());
   });
   if (!BadSize)
     return true;
 
-  // A member typedef naming such a type has already been diagnosed.
+  // Default arguments and default member initializers are evaluated where
+  // they are used, and unevaluated operands are not evaluated at all.
+  if (isUnevaluatedContext() || isCheckingDefaultArgumentOrInitializer())
+    return true;
+
+  // A typedef naming such a type has already been diagnosed.
   if (const auto *TT = T->getAs<TypedefType>();
       TT && TT->getDecl()->isInvalidDecl())
     return false;
 
-  Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << *Barrier;
+  Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << BadKind;
   Diag(BadSize->getBeginLoc(), diag::note_vm_type_size_evaluated_here);
   return false;
 }
@@ -4935,29 +4948,9 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType 
ExprType,
                                        ExprKind))
     return true;
 
-  if (ExprType->isVariablyModifiedType() && FunctionScopes.size() > 1) {
-    if (auto *TT = ExprType->getAs<TypedefType>()) {
-      for (auto I = FunctionScopes.rbegin(),
-                E = std::prev(FunctionScopes.rend());
-           I != E; ++I) {
-        auto *CSI = dyn_cast<CapturingScopeInfo>(*I);
-        if (CSI == nullptr)
-          break;
-        DeclContext *DC = nullptr;
-        if (auto *LSI = dyn_cast<LambdaScopeInfo>(CSI))
-          DC = LSI->CallOperator;
-        else if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(CSI))
-          DC = CRSI->TheCapturedDecl;
-        else if (auto *BSI = dyn_cast<BlockScopeInfo>(CSI))
-          DC = BSI->TheDecl;
-        if (DC) {
-          if (DC->containsDecl(TT->getDecl()))
-            break;
-          captureVariablyModifiedType(Context, ExprType, CSI);
-        }
-      }
-    }
-  }
+  if (ExprType->getAs<TypedefType>() &&
+      !CheckVariablyModifiedTypeUse(ExprType, OpLoc))
+    return true;
 
   return false;
 }
diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp 
b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
new file mode 100644
index 00000000000000..d3d245eed22fdd
--- /dev/null
+++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -Wno-vla-cxx-extension 
-emit-llvm -o - %s | FileCheck %s
+
+// A lambda that declares a variable of a VLA type from the enclosing function
+// captures the array bound.
+
+int foo();
+
+// CHECK-LABEL: define {{.*}} @_Z9by_copy_tv(
+// CHECK: [[SIZE:%.*]] = call noundef i32 @_Z3foov()
+// CHECK: [[EXT:%.*]] = zext i32 [[SIZE]] to i64
+// CHECK: store i64 [[EXT]], ptr {{%.*}}
+// CHECK-LABEL: define internal {{.*}} @"_ZZ9by_copy_tvENK3$_0clEv"(
+// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}}
+// CHECK: alloca i32, i64 [[BOUND]]
+int by_copy_t() {
+  using X = int[foo()];
+  return [] { X x; return (int)sizeof(x); }();
+}
+
+// CHECK-LABEL: define {{.*}} @_Z8nested_tv(
+// CHECK-LABEL: define internal {{.*}} 
@"_ZZZ8nested_tvENK3$_0clEvENKUlvE_clEv"(
+// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}}
+// CHECK: alloca i32, i64 [[BOUND]]
+int nested_t() {
+  using X = int[foo()];
+  return [&] { return [&] { X x; return (int)sizeof(x); }(); }();
+}
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
index 2306087f68768e..7534089d6bf053 100644
--- a/clang/test/Sema/vla-enclosing-function-blocks.c
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -12,6 +12,11 @@ void var_in_block(void) {
   ^{ X x; }(); // expected-error {{cannot be used in a block}}
 }
 
+void typedef_in_block(void) {
+  typedef int X[size_tab()]; // expected-note {{size expression is evaluated 
here}}
+  ^{ typedef X Y; }(); // expected-error {{cannot be used in a block}}
+}
+
 // Valid: VLA declared inside the block itself.
 int own_vla_in_block(void) {
   return (^ int(void) { int a[size_tab()]; return sizeof(a); })();
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
index 5ee408c7544dee..516a3ee604cd8b 100644
--- a/clang/test/SemaCXX/vla-enclosing-function.cpp
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -30,6 +30,11 @@ void pointer_to_vla() {
   struct S { unsigned long f(P p) { return sizeof(*p); } }; // expected-error 
{{cannot be used in a local class}}
 }
 
+void typedef_in_member_function() {
+  using X = int[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S { void f() { typedef X Y; } }; // expected-error {{cannot be used 
in a local class}}
+}
+
 template <typename T> void in_template() {
   using X = int[foo()]; // expected-note {{size expression is evaluated here}}
   struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}

>From 87ecda45fb525993a3d96c8465d7e91ccf161a44 Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Wed, 14 Oct 2026 11:21:53 +0300
Subject: [PATCH 4/8] Use CheckVariablyModifiedTypeUse for array subscripts as
 well

---
 clang/lib/Sema/SemaExpr.cpp | 30 ++++--------------------------
 1 file changed, 4 insertions(+), 26 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index bfad1c3cf32468..04b5dda1c9f763 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5718,32 +5718,10 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, 
SourceLocation LLoc,
   assert(VK == VK_PRValue || LangOpts.CPlusPlus ||
          !ResultType.isCForbiddenLValueType());
 
-  if (LHSExp->IgnoreParenImpCasts()->getType()->isVariablyModifiedType() &&
-      FunctionScopes.size() > 1) {
-    if (auto *TT =
-            LHSExp->IgnoreParenImpCasts()->getType()->getAs<TypedefType>()) {
-      for (auto I = FunctionScopes.rbegin(),
-                E = std::prev(FunctionScopes.rend());
-           I != E; ++I) {
-        auto *CSI = dyn_cast<CapturingScopeInfo>(*I);
-        if (CSI == nullptr)
-          break;
-        DeclContext *DC = nullptr;
-        if (auto *LSI = dyn_cast<LambdaScopeInfo>(CSI))
-          DC = LSI->CallOperator;
-        else if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(CSI))
-          DC = CRSI->TheCapturedDecl;
-        else if (auto *BSI = dyn_cast<BlockScopeInfo>(CSI))
-          DC = BSI->TheDecl;
-        if (DC) {
-          if (DC->containsDecl(TT->getDecl()))
-            break;
-          captureVariablyModifiedType(
-              Context, LHSExp->IgnoreParenImpCasts()->getType(), CSI);
-        }
-      }
-    }
-  }
+  QualType BaseType = LHSExp->IgnoreParenImpCasts()->getType();
+  if (BaseType->getAs<TypedefType>() &&
+      !CheckVariablyModifiedTypeUse(BaseType, LLoc))
+    return ExprError();
 
   return new (Context)
       ArraySubscriptExpr(LHSExp, RHSExp, ResultType, VK, OK, RLoc);

>From 311ae64ec9d4260ae4443f6aa4fe82c5077cedda Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Sun, 18 Oct 2026 09:15:53 +0300
Subject: [PATCH 5/8] Don't record VLA bounds in prototypes; capture on casts
 instead of subscripts

---
 clang/lib/Sema/SemaCast.cpp                   | 11 ++++++++++
 clang/lib/Sema/SemaExpr.cpp                   |  5 -----
 clang/lib/Sema/SemaType.cpp                   |  7 ++++---
 .../vla-lambda-enclosing-function.cpp         | 18 ++++++++++++++++
 .../test/Sema/vla-enclosing-function-blocks.c | 10 +++++++++
 clang/test/SemaCXX/vla-enclosing-function.cpp | 21 +++++++++++++++++++
 6 files changed, 64 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp
index c797bdb11e0cbd..03df93370dd394 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -340,6 +340,9 @@ Sema::BuildCXXNamedCast(SourceLocation OpLoc, 
tok::TokenKind Kind,
                         SourceRange AngleBrackets, SourceRange Parens) {
   ExprResult Ex = E;
   QualType DestType = DestTInfo->getType();
+  if (!CheckVariablyModifiedTypeUse(DestType,
+                                    DestTInfo->getTypeLoc().getBeginLoc()))
+    return ExprError();
 
   // If the type is dependent, we won't do the semantic analysis now.
   bool TypeDependent =
@@ -3477,6 +3480,10 @@ ExprResult Sema::BuildCStyleCastExpr(SourceLocation 
LPLoc,
                                      TypeSourceInfo *CastTypeInfo,
                                      SourceLocation RPLoc,
                                      Expr *CastExpr) {
+  if (!CheckVariablyModifiedTypeUse(CastTypeInfo->getType(),
+                                    CastTypeInfo->getTypeLoc().getBeginLoc()))
+    return ExprError();
+
   CastOperation Op(*this, CastTypeInfo->getType(), CastExpr);
   Op.DestRange = CastTypeInfo->getTypeLoc().getSourceRange();
   Op.OpRange = CastOperation::OpRangeType(LPLoc, LPLoc, CastExpr->getEndLoc());
@@ -3507,6 +3514,10 @@ ExprResult 
Sema::BuildCXXFunctionalCastExpr(TypeSourceInfo *CastTypeInfo,
                                             Expr *CastExpr,
                                             SourceLocation RPLoc) {
   assert(LPLoc.isValid() && "List-initialization shouldn't get here.");
+  if (!CheckVariablyModifiedTypeUse(Type,
+                                    CastTypeInfo->getTypeLoc().getBeginLoc()))
+    return ExprError();
+
   CastOperation Op(*this, Type, CastExpr);
   Op.DestRange = CastTypeInfo->getTypeLoc().getSourceRange();
   Op.OpRange =
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 04b5dda1c9f763..6311f89baf459a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5718,11 +5718,6 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, 
SourceLocation LLoc,
   assert(VK == VK_PRValue || LangOpts.CPlusPlus ||
          !ResultType.isCForbiddenLValueType());
 
-  QualType BaseType = LHSExp->IgnoreParenImpCasts()->getType();
-  if (BaseType->getAs<TypedefType>() &&
-      !CheckVariablyModifiedTypeUse(BaseType, LLoc))
-    return ExprError();
-
   return new (Context)
       ArraySubscriptExpr(LHSExp, RHSExp, ResultType, VK, OK, RLoc);
 }
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3bcc09a571c765..e3ed5b0eff097b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -2371,10 +2371,11 @@ QualType Sema::BuildArrayType(QualType T, 
ArraySizeModifier ASM,
   }
 
   // Remember where the bound of a VLA is evaluated. Bounds in a function
-  // prototype are evaluated in that function, which doesn't exist yet;
-  // those are not recorded and never diagnosed.
+  // prototype are evaluated in that function, which doesn't exist yet, so they
+  // are not recorded and their uses are not diagnosed.
   if (const VariableArrayType *VAT = Context.getAsVariableArrayType(T))
-    if (CurContext->isFunctionOrMethod())
+    if (CurContext->isFunctionOrMethod() &&
+        !(getCurScope() && getCurScope()->isFunctionPrototypeScope()))
       if (const Expr *Size = VAT->getSizeExpr())
         VLASizeExprOwners.try_emplace(Size, CurContext);
 
diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp 
b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
index d3d245eed22fdd..7360ad571c6275 100644
--- a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
+++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
@@ -17,6 +17,24 @@ int by_copy_t() {
   return [] { X x; return (int)sizeof(x); }();
 }
 
+// An explicit cast to such a type captures the bound as well.
+// CHECK-LABEL: define {{.*}} @_Z14cast_in_lambdav(
+// CHECK: store i64 {{%.*}}, ptr {{%.*}}
+// CHECK-LABEL: define internal {{.*}} @"_ZZ14cast_in_lambdavENK3$_0clEv"(
+// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}}
+// CHECK: mul nuw i64 4, [[BOUND]]
+unsigned long cast_in_lambda() {
+  using P = int (*)[foo()];
+  return [] { return sizeof(((P)0)[1]); }();
+}
+
+// The bound of the lambda's own parameter is not captured.
+// CHECK-LABEL: define {{.*}} @_Z15own_param_boundv(
+// CHECK-LABEL: define internal {{.*}} 
@"_ZZ15own_param_boundvENK3$_0clEiPA_i"(ptr noundef nonnull align 1 
dereferenceable(1) %this,
+int own_param_bound() {
+  return [](int m, int (*p)[m]) { decltype(p) q = p; return (int)sizeof(*q); 
}(1, nullptr);
+}
+
 // CHECK-LABEL: define {{.*}} @_Z8nested_tv(
 // CHECK-LABEL: define internal {{.*}} 
@"_ZZZ8nested_tvENK3$_0clEvENKUlvE_clEv"(
 // CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}}
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
index 7534089d6bf053..fbd7f4ebca7e90 100644
--- a/clang/test/Sema/vla-enclosing-function-blocks.c
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -17,6 +17,16 @@ void typedef_in_block(void) {
   ^{ typedef X Y; }(); // expected-error {{cannot be used in a block}}
 }
 
+void cast_in_block(void) {
+  typedef int (*P)[size_tab()]; // expected-note {{size expression is 
evaluated here}}
+  ^{ (void)(P)0; }(); // expected-error {{cannot be used in a block}}
+}
+
+// Valid: VLA parameter of the block itself.
+int own_vla_param_in_block(void) {
+  return (^ int(int m, int (*p)[m]) { __typeof(p) q = p; return sizeof(*q); 
})(1, 0);
+}
+
 // Valid: VLA declared inside the block itself.
 int own_vla_in_block(void) {
   return (^ int(void) { int a[size_tab()]; return sizeof(a); })();
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
index 516a3ee604cd8b..7ce2fcfc91a0f3 100644
--- a/clang/test/SemaCXX/vla-enclosing-function.cpp
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -30,11 +30,28 @@ void pointer_to_vla() {
   struct S { unsigned long f(P p) { return sizeof(*p); } }; // expected-error 
{{cannot be used in a local class}}
 }
 
+void casts() {
+  using P = int (*)[foo()]; // expected-note 3 {{size expression is evaluated 
here}}
+  struct S {
+    void f() {
+      (void)(P)0; // expected-error {{cannot be used in a local class}}
+      (void)static_cast<P>(nullptr); // expected-error {{cannot be used in a 
local class}}
+      (void)P(nullptr); // expected-error {{cannot be used in a local class}}
+    }
+  };
+}
+
 void typedef_in_member_function() {
   using X = int[foo()]; // expected-note {{size expression is evaluated here}}
   struct S { void f() { typedef X Y; } }; // expected-error {{cannot be used 
in a local class}}
 }
 
+// FIXME: Bounds in a function prototype are not recorded, so this is not
+// diagnosed.
+void param_bound(int n, int (*p)[n]) {
+  struct S { void f() { decltype(p) q; } };
+}
+
 template <typename T> void in_template() {
   using X = int[foo()]; // expected-note {{size expression is evaluated here}}
   struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}
@@ -70,6 +87,10 @@ void param_vla_in_local_class() {
   struct S { unsigned long f(int n, int (*p)[n]) { return sizeof(*p); } };
 }
 
+unsigned long lambda_own_vla_param() {
+  return [](int m, int (*p)[m]) { decltype(p) q = p; return sizeof(*q); }(1, 
nullptr);
+}
+
 int lambda_sizeof() {
   using X = int[foo()];
   return [&] { return (int)sizeof(X); }();

>From 2fbf3098481da28ddf3a920e8430280b8729fc2f Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Sun, 18 Oct 2026 14:00:13 +0300
Subject: [PATCH 6/8] Check all variably modified sizeof operands, not only
 typedefs

---
 clang/lib/Sema/SemaExpr.cpp                           |  3 +--
 .../test/CodeGenCXX/vla-lambda-enclosing-function.cpp | 11 +++++++++++
 clang/test/Sema/vla-enclosing-function-blocks.c       |  7 +++++++
 clang/test/SemaCXX/vla-enclosing-function.cpp         | 11 +++++++++++
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 6311f89baf459a..a87e257566e476 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4948,8 +4948,7 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType 
ExprType,
                                        ExprKind))
     return true;
 
-  if (ExprType->getAs<TypedefType>() &&
-      !CheckVariablyModifiedTypeUse(ExprType, OpLoc))
+  if (!CheckVariablyModifiedTypeUse(ExprType, OpLoc))
     return true;
 
   return false;
diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp 
b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
index 7360ad571c6275..3b8043eb3c311d 100644
--- a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
+++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp
@@ -28,6 +28,17 @@ unsigned long cast_in_lambda() {
   return [] { return sizeof(((P)0)[1]); }();
 }
 
+// So does sizeof of such a type.
+// CHECK-LABEL: define {{.*}} @_Z13sizeof_lambdav(
+// CHECK: store i64 {{%.*}}, ptr {{%.*}}
+// CHECK-LABEL: define internal {{.*}} @"_ZZ13sizeof_lambdavENK3$_0clEv"(
+// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}}
+// CHECK: mul nuw i64 4, [[BOUND]]
+unsigned long sizeof_lambda() {
+  using X = int[foo()];
+  return [] { return sizeof(X); }();
+}
+
 // The bound of the lambda's own parameter is not captured.
 // CHECK-LABEL: define {{.*}} @_Z15own_param_boundv(
 // CHECK-LABEL: define internal {{.*}} 
@"_ZZ15own_param_boundvENK3$_0clEiPA_i"(ptr noundef nonnull align 1 
dereferenceable(1) %this,
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
index fbd7f4ebca7e90..16d15fcfac2faa 100644
--- a/clang/test/Sema/vla-enclosing-function-blocks.c
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -22,6 +22,13 @@ void cast_in_block(void) {
   ^{ (void)(P)0; }(); // expected-error {{cannot be used in a block}}
 }
 
+// FIXME: The operand of sizeof is checked in an unevaluated context, so this
+// is not diagnosed.
+void sizeof_in_block(void) {
+  typedef int X[size_tab()];
+  ^{ (void)sizeof(X); }();
+}
+
 // Valid: VLA parameter of the block itself.
 int own_vla_param_in_block(void) {
   return (^ int(int m, int (*p)[m]) { __typeof(p) q = p; return sizeof(*q); 
})(1, 0);
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
index 7ce2fcfc91a0f3..1375f0ca9a349c 100644
--- a/clang/test/SemaCXX/vla-enclosing-function.cpp
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -52,6 +52,17 @@ void param_bound(int n, int (*p)[n]) {
   struct S { void f() { decltype(p) q; } };
 }
 
+// FIXME: The operand of sizeof is checked in an unevaluated context, so this
+// is not diagnosed.
+void sizeof_type() {
+  using X = int[foo()];
+  int a[foo()];
+  struct S {
+    unsigned long f() { return sizeof(X); }
+    unsigned long g() { return sizeof(decltype(a)); }
+  };
+}
+
 template <typename T> void in_template() {
   using X = int[foo()]; // expected-note {{size expression is evaluated here}}
   struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}

>From f3521f37489df84691aae32ad32373bc0fef2be9 Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Thu, 22 Oct 2026 16:58:51 +0300
Subject: [PATCH 7/8] Diagnose sizeof of such types in local classes and blocks

---
 clang/lib/Sema/SemaExpr.cpp                   |  6 ++++
 clang/lib/Sema/TreeTransform.h                |  5 ++++
 .../test/Sema/vla-enclosing-function-blocks.c |  7 ++---
 clang/test/SemaCXX/vla-enclosing-function.cpp | 28 +++++++++++++++----
 4 files changed, 36 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index a87e257566e476..7f71620735e58c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4948,6 +4948,12 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType 
ExprType,
                                        ExprKind))
     return true;
 
+  // The operand of sizeof is parsed in an unevaluated context, but a variably
+  // modified operand is evaluated if the sizeof itself is.
+  std::optional<EnterExpressionEvaluationContext> Evaluated;
+  if ((ExprKind == UETT_SizeOf || ExprKind == UETT_CountOf) &&
+      isUnevaluatedContext())
+    Evaluated.emplace(*this, parentEvaluationContext().Context);
   if (!CheckVariablyModifiedTypeUse(ExprType, OpLoc))
     return true;
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 73367d6bea063d..344659a65c6547 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -13980,6 +13980,11 @@ ExprResult
 TreeTransform<Derived>::TransformUnaryExprOrTypeTraitExpr(
                                                 UnaryExprOrTypeTraitExpr *E) {
   if (E->isArgumentType()) {
+    // Transform the type operand in an unevaluated context, as the parser 
does.
+    EnterExpressionEvaluationContext Unevaluated(
+        SemaRef, Sema::ExpressionEvaluationContext::Unevaluated,
+        Sema::ReuseLambdaContextDecl);
+
     TypeSourceInfo *OldT = E->getArgumentTypeInfo();
 
     TypeSourceInfo *NewT = getDerived().TransformType(OldT);
diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c 
b/clang/test/Sema/vla-enclosing-function-blocks.c
index 16d15fcfac2faa..2e420efabd77b8 100644
--- a/clang/test/Sema/vla-enclosing-function-blocks.c
+++ b/clang/test/Sema/vla-enclosing-function-blocks.c
@@ -22,11 +22,10 @@ void cast_in_block(void) {
   ^{ (void)(P)0; }(); // expected-error {{cannot be used in a block}}
 }
 
-// FIXME: The operand of sizeof is checked in an unevaluated context, so this
-// is not diagnosed.
 void sizeof_in_block(void) {
-  typedef int X[size_tab()];
-  ^{ (void)sizeof(X); }();
+  typedef int X[size_tab()]; // expected-note 2 {{size expression is evaluated 
here}}
+  ^{ (void)sizeof(X); }(); // expected-error {{cannot be used in a block}}
+  ^{ (void)_Countof(X); }(); // expected-error {{cannot be used in a block}}
 }
 
 // Valid: VLA parameter of the block itself.
diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp 
b/clang/test/SemaCXX/vla-enclosing-function.cpp
index 1375f0ca9a349c..cbb722aa2b6dfe 100644
--- a/clang/test/SemaCXX/vla-enclosing-function.cpp
+++ b/clang/test/SemaCXX/vla-enclosing-function.cpp
@@ -52,14 +52,12 @@ void param_bound(int n, int (*p)[n]) {
   struct S { void f() { decltype(p) q; } };
 }
 
-// FIXME: The operand of sizeof is checked in an unevaluated context, so this
-// is not diagnosed.
 void sizeof_type() {
-  using X = int[foo()];
-  int a[foo()];
+  using X = int[foo()]; // expected-note {{size expression is evaluated here}}
+  int a[foo()]; // expected-note {{size expression is evaluated here}}
   struct S {
-    unsigned long f() { return sizeof(X); }
-    unsigned long g() { return sizeof(decltype(a)); }
+    unsigned long f() { return sizeof(X); } // expected-error {{cannot be used 
in a local class}}
+    unsigned long g() { return sizeof(decltype(a)); } // expected-error 
{{cannot be used in a local class}}
   };
 }
 
@@ -68,6 +66,15 @@ template <typename T> void in_template() {
   struct S { S() { X x; } }; // expected-error {{cannot be used in a local 
class}}
 }
 
+template <typename T> void sizeof_in_template() {
+  using X = T[foo()]; // expected-note {{size expression is evaluated here}}
+  struct S { // expected-note {{in instantiation of member function}}
+    unsigned long f() { return sizeof(X); } // expected-error {{cannot be used 
in a local class}}
+    void g() { decltype(sizeof(X)) n = 0; (void)n; }
+  };
+}
+template void sizeof_in_template<int>(); // expected-note {{in instantiation 
of}}
+
 namespace GH55686 {
 void f(int n) {
   using T = int[n]; // expected-note 2 {{size expression is evaluated here}}
@@ -90,6 +97,15 @@ int same_function() {
   return sizeof(X) + sizeof(x);
 }
 
+// The bound is not evaluated here.
+void sizeof_not_evaluated() {
+  using X = int[foo()];
+  struct S {
+    void f() { decltype(sizeof(X)) n = 0; (void)n; }
+    unsigned long g() { return alignof(X); }
+  };
+}
+
 void own_vla_in_local_class() {
   struct S { void f() { int a[foo()]; (void)sizeof(a); } };
 }

>From 92148f8c9de2c8fc31a9e339e1577941d4f4eaa4 Mon Sep 17 00:00:00 2001
From: Ivan Ugryumov <[email protected]>
Date: Fri, 23 Oct 2026 09:57:21 +0300
Subject: [PATCH 8/8] Add release note

---
 clang/docs/ReleaseNotes.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 08442f74879afc..e369508306995b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -624,6 +624,7 @@ features cannot lower the translation-unit ABI level;
 - Fixed assertion failures caused by stale linkage information when an extern 
variable or function declaration is merged with a preceding static declaration. 
(#GH204759, #GH204754)
 - Fixed a crash due to typo correction mishandling custom keywords 
`_virtual_inheritance` and `_multiple_inheritance` in `-fms-compatibility` 
mode. (#GH228003)
 - Clang no longer treats a file-scope `thread_local` declaration without an 
initializer as a tentative definition in C23 mode. As specified by C23 6.9.3, 
such a declaration is a definition, so declaring the same variable more that 
once is now diagnosed as a redefinition. (#GH217636)
+- Fixed a crash when a variably modified type whose size is evaluated in an 
enclosing function was used in a local class or a block. Such uses are now 
diagnosed, and a lambda now captures the size of such a type. (#GH55686)
 
 #### Bug Fixes to Compiler Builtins
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to