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/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 <[email protected]> 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()]; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
