[clang] [clang] Diagnose VLA types from an enclosing function in local classes and blocks (PR #229697)
Ivan Ugryumov via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 14 02:23:56 PDT 2026
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 <smille2003 at yandex.ru>
Date: Wed, 7 Oct 2026 11:17:49 +0300
Subject: [PATCH 1/4] [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 9208aba1445d7..c5d1ff7a21395 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 7fbcc4aae7d6c..fc8d7ce6bb425 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 fdbe152116512..2db8fa88676da 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 9b3384cdbdac8..d7650ae54452a 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 202bd486321f9..3bcc09a571c76 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 0000000000000..42961dcf3bd1c
--- /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 0000000000000..3e9ec40a766a0
--- /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 <smille2003 at yandex.ru>
Date: Thu, 8 Oct 2026 09:57:42 +0300
Subject: [PATCH 2/4] 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 2db8fa88676da..e43ac27c4110f 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 d7650ae54452a..40a967ec8eb63 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 42961dcf3bd1c..2306087f68768 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 3e9ec40a766a0..5ee408c7544de 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 <smille2003 at yandex.ru>
Date: Wed, 14 Oct 2026 10:20:25 +0300
Subject: [PATCH 3/4] 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 fc8d7ce6bb425..858f763ceaf30 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 e43ac27c4110f..3c0dd3740e9dd 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 40a967ec8eb63..bfad1c3cf3246 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 0000000000000..d3d245eed22fd
--- /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 2306087f68768..7534089d6bf05 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 5ee408c7544de..516a3ee604cd8 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 <smille2003 at yandex.ru>
Date: Wed, 14 Oct 2026 11:21:53 +0300
Subject: [PATCH 4/4] 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 bfad1c3cf3246..04b5dda1c9f76 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);
More information about the cfe-commits
mailing list