[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
Thu Oct 8 01:05:11 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/2] [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 <smille2003 at yandex.ru>
Date: Thu, 8 Oct 2026 09:57:42 +0300
Subject: [PATCH 2/2] 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()];
More information about the cfe-commits
mailing list