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