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/6] [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/6] Also reject member typedefs of such types in local classes Diagnose a typedef or alias member of a class that names a variably modified type whose bound is evaluated in an enclosing function, as GCC does, and don't diagnose uses of such a typedef again. Add the example from GH55686 and reference that issue in the tests. --- clang/lib/Sema/SemaDecl.cpp | 9 +++++++++ clang/lib/Sema/SemaExpr.cpp | 5 +++++ clang/test/Sema/vla-enclosing-function-blocks.c | 2 +- clang/test/SemaCXX/vla-enclosing-function.cpp | 17 ++++++++++++++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 2db8fa88676daa..e43ac27c4110f4 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -7017,6 +7017,15 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, TypedefNameDecl *NewTD) { // that redeclarations will match. TypeSourceInfo *TInfo = NewTD->getTypeSourceInfo(); QualType T = TInfo->getType(); + + // A member typedef cannot name a variably modified type whose bound is + // evaluated in an enclosing function. + if (NewTD->getDeclContext()->isRecord() && + !CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) { + NewTD->setInvalidDecl(); + return; + } + if (T->isVariablyModifiedType()) { setFunctionHasBranchProtectedScope(); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index d7650ae54452a5..40a967ec8eb63c 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4847,6 +4847,11 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) { if (!BadSize) return true; + // A member typedef naming such a type has already been diagnosed. + if (const auto *TT = T->getAs<TypedefType>(); + TT && TT->getDecl()->isInvalidDecl()) + return false; + Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << *Barrier; Diag(BadSize->getBeginLoc(), diag::note_vm_type_size_evaluated_here); return false; diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c b/clang/test/Sema/vla-enclosing-function-blocks.c index 42961dcf3bd1c8..2306087f68768e 100644 --- a/clang/test/Sema/vla-enclosing-function-blocks.c +++ b/clang/test/Sema/vla-enclosing-function-blocks.c @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s -// GH229694 +// GH55686 int size_tab(void); int main(void) { __auto_type tab = (int(*)[size_tab()])0; // expected-note {{size expression is evaluated here}} diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp b/clang/test/SemaCXX/vla-enclosing-function.cpp index 3e9ec40a766a01..5ee408c7544dee 100644 --- a/clang/test/SemaCXX/vla-enclosing-function.cpp +++ b/clang/test/SemaCXX/vla-enclosing-function.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -Wno-vla-cxx-extension -verify %s -// GH229694 +// GH55686 int foo(); void ctor() { @@ -35,6 +35,21 @@ template <typename T> void in_template() { struct S { S() { X x; } }; // expected-error {{cannot be used in a local class}} } +namespace GH55686 { +void f(int n) { + using T = int[n]; // expected-note 2 {{size expression is evaluated here}} + struct A { + using U = T; // expected-error {{cannot be used in a local class}} + void f() { U u; } + void g() { T t; } // expected-error {{cannot be used in a local class}} + }; + A::U u; + A a; + a.f(); + a.g(); +} +} // namespace GH55686 + // Valid: no diagnostics. int same_function() { using X = int[foo()]; >From 4225f28176f3f882f267c5a6e02ae1941cecc656 Mon Sep 17 00:00:00 2001 From: Ivan Ugryumov <[email protected]> Date: Wed, 14 Oct 2026 10:20:25 +0300 Subject: [PATCH 3/6] Handle VLA bound captures and diagnostics in one function --- clang/include/clang/Sema/Sema.h | 7 +- clang/lib/Sema/SemaDecl.cpp | 5 +- clang/lib/Sema/SemaExpr.cpp | 125 +++++++++--------- .../vla-lambda-enclosing-function.cpp | 27 ++++ .../test/Sema/vla-enclosing-function-blocks.c | 5 + clang/test/SemaCXX/vla-enclosing-function.cpp | 5 + 6 files changed, 101 insertions(+), 73 deletions(-) create mode 100644 clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fc8d7ce6bb425c..858f763ceaf306 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -7222,9 +7222,10 @@ class Sema final : public SemaBase { /// variable will have in the given scope. QualType getCapturedDeclRefType(ValueDecl *Var, SourceLocation Loc); - /// Diagnose a use of the variably modified type \p T in a local class or - /// block when one of its array bounds is evaluated in an enclosing function. - /// Returns false if a diagnostic was emitted. + /// Handle a use of the variably modified type \p T in the current context. + /// Array bounds evaluated in an enclosing context are captured by the + /// lambdas in between; if a local class or a block is in between instead, + /// the use is diagnosed. Returns false if the use is invalid. bool CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc); /// Mark all of the declarations referenced within a particular AST node as diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index e43ac27c4110f4..3c0dd3740e9ddf 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -7018,10 +7018,7 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, TypedefNameDecl *NewTD) { TypeSourceInfo *TInfo = NewTD->getTypeSourceInfo(); QualType T = TInfo->getType(); - // A member typedef cannot name a variably modified type whose bound is - // evaluated in an enclosing function. - if (NewTD->getDeclContext()->isRecord() && - !CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) { + if (!CheckVariablyModifiedTypeUse(T, TInfo->getTypeLoc().getBeginLoc())) { NewTD->setInvalidDecl(); return; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 40a967ec8eb63c..bfad1c3cf32468 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4782,44 +4782,20 @@ static void captureVariablyModifiedType(ASTContext &Context, QualType T, }); } -/// Find what separates \p DC from \p Owner, the context in which a VLA bound -/// is evaluated: 0 for a local class, 1 for a block, or std::nullopt if the -/// bound is available in \p DC or \p Owner does not enclose it. -static std::optional<unsigned> findVLABoundBarrier(const DeclContext *DC, - const DeclContext *Owner) { - std::optional<unsigned> Barrier; - for (; DC; DC = DC->getParent()) { - if (DC == Owner) - return Barrier; - // Lambdas and captured regions can capture a VLA bound (see - // captureVariablyModifiedType), so they are not barriers here. - if (isLambdaCallOperator(DC) || isa<CapturedDecl>(DC)) - continue; - if (const auto *RD = dyn_cast<CXXRecordDecl>(DC); RD && RD->isLambda()) - continue; - if (isa<BlockDecl>(DC)) { - if (!Barrier) - Barrier = 1; - continue; - } - if (isa<RecordDecl>(DC) && !Barrier) - Barrier = 0; - } - return std::nullopt; -} - -static bool isInLocalClassOrBlock(const DeclContext *DC) { - for (; DC; DC = DC->getParent()) { - if (isa<BlockDecl>(DC)) - return true; - if (const auto *RD = dyn_cast<RecordDecl>(DC)) { - if (const auto *CRD = dyn_cast<CXXRecordDecl>(RD); CRD && CRD->isLambda()) - continue; - if (RD->getParentFunctionOrMethod()) - return true; - } +/// Find the capturing scope that is being built for the lambda call operator +/// or captured declaration \p DC, if any. +static CapturingScopeInfo * +findCapturingScope(ArrayRef<FunctionScopeInfo *> Scopes, + const DeclContext *DC) { + for (FunctionScopeInfo *FSI : llvm::reverse(Scopes)) { + if (auto *LSI = dyn_cast<LambdaScopeInfo>(FSI); + LSI && LSI->CallOperator == DC) + return LSI; + if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(FSI); + CRSI && CRSI->TheCapturedDecl == DC) + return CRSI; } - return false; + return nullptr; } bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) { @@ -4828,11 +4804,11 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) { // Deduced types (auto, __auto_type) do not propagate the variably modified // bit, so look at the canonical type. QualType CanonT = T.getCanonicalType(); - if (!CanonT->isVariablyModifiedType() || !isInLocalClassOrBlock(CurContext)) + if (!CanonT->isVariablyModifiedType()) return true; const Expr *BadSize = nullptr; - std::optional<unsigned> Barrier; + unsigned BadKind = 0; forEachVariableArrayType(Context, CanonT, [&](const VariableArrayType *VAT) { const Expr *Size = VAT->getSizeExpr(); if (BadSize || !Size) @@ -4840,19 +4816,56 @@ bool Sema::CheckVariablyModifiedTypeUse(QualType T, SourceLocation Loc) { auto It = VLASizeExprOwners.find(Size); if (It == VLASizeExprOwners.end()) return; - Barrier = findVLABoundBarrier(CurContext, It->second); - if (Barrier) + + // Walk from the use to the context in which the bound is evaluated. + // Lambdas and captured regions on the way capture the bound; a local + // class or a block cannot see it. + SmallVector<CapturingScopeInfo *, 2> Capturers; + std::optional<unsigned> Barrier; + const DeclContext *DC = CurContext; + for (; DC && DC != It->second; DC = DC->getParent()) { + if (isLambdaCallOperator(DC) || isa<CapturedDecl>(DC)) { + if (CapturingScopeInfo *CSI = findCapturingScope(FunctionScopes, DC)) + Capturers.push_back(CSI); + } else if (isa<BlockDecl>(DC)) { + Barrier = Barrier.value_or(1); + } else if (const auto *RD = dyn_cast<RecordDecl>(DC)) { + if (const auto *CRD = dyn_cast<CXXRecordDecl>(RD); + !CRD || !CRD->isLambda()) + Barrier = Barrier.value_or(0); + } + } + // Nothing to do if the bound is not evaluated in an enclosing context. + if (!DC) + return; + if (Barrier) { BadSize = Size; + BadKind = *Barrier; + return; + } + // VAT is a canonical type; the bound may already have been captured + // through a different type node with the same size expression. + for (CapturingScopeInfo *CSI : Capturers) + if (llvm::none_of(CSI->Captures, [&](const Capture &C) { + return C.isVLATypeCapture() && + C.getCapturedVLAType()->getSizeExpr() == Size; + })) + CSI->addVLATypeCapture(Size->getExprLoc(), VAT, Context.getSizeType()); }); if (!BadSize) return true; - // A member typedef naming such a type has already been diagnosed. + // Default arguments and default member initializers are evaluated where + // they are used, and unevaluated operands are not evaluated at all. + if (isUnevaluatedContext() || isCheckingDefaultArgumentOrInitializer()) + return true; + + // A typedef naming such a type has already been diagnosed. if (const auto *TT = T->getAs<TypedefType>(); TT && TT->getDecl()->isInvalidDecl()) return false; - Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << *Barrier; + Diag(Loc, diag::err_vm_type_from_enclosing_function) << T << BadKind; Diag(BadSize->getBeginLoc(), diag::note_vm_type_size_evaluated_here); return false; } @@ -4935,29 +4948,9 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType, ExprKind)) return true; - if (ExprType->isVariablyModifiedType() && FunctionScopes.size() > 1) { - if (auto *TT = ExprType->getAs<TypedefType>()) { - for (auto I = FunctionScopes.rbegin(), - E = std::prev(FunctionScopes.rend()); - I != E; ++I) { - auto *CSI = dyn_cast<CapturingScopeInfo>(*I); - if (CSI == nullptr) - break; - DeclContext *DC = nullptr; - if (auto *LSI = dyn_cast<LambdaScopeInfo>(CSI)) - DC = LSI->CallOperator; - else if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(CSI)) - DC = CRSI->TheCapturedDecl; - else if (auto *BSI = dyn_cast<BlockScopeInfo>(CSI)) - DC = BSI->TheDecl; - if (DC) { - if (DC->containsDecl(TT->getDecl())) - break; - captureVariablyModifiedType(Context, ExprType, CSI); - } - } - } - } + if (ExprType->getAs<TypedefType>() && + !CheckVariablyModifiedTypeUse(ExprType, OpLoc)) + return true; return false; } diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp new file mode 100644 index 00000000000000..d3d245eed22fdd --- /dev/null +++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -Wno-vla-cxx-extension -emit-llvm -o - %s | FileCheck %s + +// A lambda that declares a variable of a VLA type from the enclosing function +// captures the array bound. + +int foo(); + +// CHECK-LABEL: define {{.*}} @_Z9by_copy_tv( +// CHECK: [[SIZE:%.*]] = call noundef i32 @_Z3foov() +// CHECK: [[EXT:%.*]] = zext i32 [[SIZE]] to i64 +// CHECK: store i64 [[EXT]], ptr {{%.*}} +// CHECK-LABEL: define internal {{.*}} @"_ZZ9by_copy_tvENK3$_0clEv"( +// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}} +// CHECK: alloca i32, i64 [[BOUND]] +int by_copy_t() { + using X = int[foo()]; + return [] { X x; return (int)sizeof(x); }(); +} + +// CHECK-LABEL: define {{.*}} @_Z8nested_tv( +// CHECK-LABEL: define internal {{.*}} @"_ZZZ8nested_tvENK3$_0clEvENKUlvE_clEv"( +// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}} +// CHECK: alloca i32, i64 [[BOUND]] +int nested_t() { + using X = int[foo()]; + return [&] { return [&] { X x; return (int)sizeof(x); }(); }(); +} diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c b/clang/test/Sema/vla-enclosing-function-blocks.c index 2306087f68768e..7534089d6bf053 100644 --- a/clang/test/Sema/vla-enclosing-function-blocks.c +++ b/clang/test/Sema/vla-enclosing-function-blocks.c @@ -12,6 +12,11 @@ void var_in_block(void) { ^{ X x; }(); // expected-error {{cannot be used in a block}} } +void typedef_in_block(void) { + typedef int X[size_tab()]; // expected-note {{size expression is evaluated here}} + ^{ typedef X Y; }(); // expected-error {{cannot be used in a block}} +} + // Valid: VLA declared inside the block itself. int own_vla_in_block(void) { return (^ int(void) { int a[size_tab()]; return sizeof(a); })(); diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp b/clang/test/SemaCXX/vla-enclosing-function.cpp index 5ee408c7544dee..516a3ee604cd8b 100644 --- a/clang/test/SemaCXX/vla-enclosing-function.cpp +++ b/clang/test/SemaCXX/vla-enclosing-function.cpp @@ -30,6 +30,11 @@ void pointer_to_vla() { struct S { unsigned long f(P p) { return sizeof(*p); } }; // expected-error {{cannot be used in a local class}} } +void typedef_in_member_function() { + using X = int[foo()]; // expected-note {{size expression is evaluated here}} + struct S { void f() { typedef X Y; } }; // expected-error {{cannot be used in a local class}} +} + template <typename T> void in_template() { using X = int[foo()]; // expected-note {{size expression is evaluated here}} struct S { S() { X x; } }; // expected-error {{cannot be used in a local class}} >From 87ecda45fb525993a3d96c8465d7e91ccf161a44 Mon Sep 17 00:00:00 2001 From: Ivan Ugryumov <[email protected]> Date: Wed, 14 Oct 2026 11:21:53 +0300 Subject: [PATCH 4/6] Use CheckVariablyModifiedTypeUse for array subscripts as well --- clang/lib/Sema/SemaExpr.cpp | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index bfad1c3cf32468..04b5dda1c9f763 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5718,32 +5718,10 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, SourceLocation LLoc, assert(VK == VK_PRValue || LangOpts.CPlusPlus || !ResultType.isCForbiddenLValueType()); - if (LHSExp->IgnoreParenImpCasts()->getType()->isVariablyModifiedType() && - FunctionScopes.size() > 1) { - if (auto *TT = - LHSExp->IgnoreParenImpCasts()->getType()->getAs<TypedefType>()) { - for (auto I = FunctionScopes.rbegin(), - E = std::prev(FunctionScopes.rend()); - I != E; ++I) { - auto *CSI = dyn_cast<CapturingScopeInfo>(*I); - if (CSI == nullptr) - break; - DeclContext *DC = nullptr; - if (auto *LSI = dyn_cast<LambdaScopeInfo>(CSI)) - DC = LSI->CallOperator; - else if (auto *CRSI = dyn_cast<CapturedRegionScopeInfo>(CSI)) - DC = CRSI->TheCapturedDecl; - else if (auto *BSI = dyn_cast<BlockScopeInfo>(CSI)) - DC = BSI->TheDecl; - if (DC) { - if (DC->containsDecl(TT->getDecl())) - break; - captureVariablyModifiedType( - Context, LHSExp->IgnoreParenImpCasts()->getType(), CSI); - } - } - } - } + QualType BaseType = LHSExp->IgnoreParenImpCasts()->getType(); + if (BaseType->getAs<TypedefType>() && + !CheckVariablyModifiedTypeUse(BaseType, LLoc)) + return ExprError(); return new (Context) ArraySubscriptExpr(LHSExp, RHSExp, ResultType, VK, OK, RLoc); >From 311ae64ec9d4260ae4443f6aa4fe82c5077cedda Mon Sep 17 00:00:00 2001 From: Ivan Ugryumov <[email protected]> Date: Sun, 18 Oct 2026 09:15:53 +0300 Subject: [PATCH 5/6] Don't record VLA bounds in prototypes; capture on casts instead of subscripts --- clang/lib/Sema/SemaCast.cpp | 11 ++++++++++ clang/lib/Sema/SemaExpr.cpp | 5 ----- clang/lib/Sema/SemaType.cpp | 7 ++++--- .../vla-lambda-enclosing-function.cpp | 18 ++++++++++++++++ .../test/Sema/vla-enclosing-function-blocks.c | 10 +++++++++ clang/test/SemaCXX/vla-enclosing-function.cpp | 21 +++++++++++++++++++ 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp index c797bdb11e0cbd..03df93370dd394 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -340,6 +340,9 @@ Sema::BuildCXXNamedCast(SourceLocation OpLoc, tok::TokenKind Kind, SourceRange AngleBrackets, SourceRange Parens) { ExprResult Ex = E; QualType DestType = DestTInfo->getType(); + if (!CheckVariablyModifiedTypeUse(DestType, + DestTInfo->getTypeLoc().getBeginLoc())) + return ExprError(); // If the type is dependent, we won't do the semantic analysis now. bool TypeDependent = @@ -3477,6 +3480,10 @@ ExprResult Sema::BuildCStyleCastExpr(SourceLocation LPLoc, TypeSourceInfo *CastTypeInfo, SourceLocation RPLoc, Expr *CastExpr) { + if (!CheckVariablyModifiedTypeUse(CastTypeInfo->getType(), + CastTypeInfo->getTypeLoc().getBeginLoc())) + return ExprError(); + CastOperation Op(*this, CastTypeInfo->getType(), CastExpr); Op.DestRange = CastTypeInfo->getTypeLoc().getSourceRange(); Op.OpRange = CastOperation::OpRangeType(LPLoc, LPLoc, CastExpr->getEndLoc()); @@ -3507,6 +3514,10 @@ ExprResult Sema::BuildCXXFunctionalCastExpr(TypeSourceInfo *CastTypeInfo, Expr *CastExpr, SourceLocation RPLoc) { assert(LPLoc.isValid() && "List-initialization shouldn't get here."); + if (!CheckVariablyModifiedTypeUse(Type, + CastTypeInfo->getTypeLoc().getBeginLoc())) + return ExprError(); + CastOperation Op(*this, Type, CastExpr); Op.DestRange = CastTypeInfo->getTypeLoc().getSourceRange(); Op.OpRange = diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 04b5dda1c9f763..6311f89baf459a 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5718,11 +5718,6 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, SourceLocation LLoc, assert(VK == VK_PRValue || LangOpts.CPlusPlus || !ResultType.isCForbiddenLValueType()); - QualType BaseType = LHSExp->IgnoreParenImpCasts()->getType(); - if (BaseType->getAs<TypedefType>() && - !CheckVariablyModifiedTypeUse(BaseType, LLoc)) - return ExprError(); - return new (Context) ArraySubscriptExpr(LHSExp, RHSExp, ResultType, VK, OK, RLoc); } diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 3bcc09a571c765..e3ed5b0eff097b 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -2371,10 +2371,11 @@ QualType Sema::BuildArrayType(QualType T, ArraySizeModifier ASM, } // Remember where the bound of a VLA is evaluated. Bounds in a function - // prototype are evaluated in that function, which doesn't exist yet; - // those are not recorded and never diagnosed. + // prototype are evaluated in that function, which doesn't exist yet, so they + // are not recorded and their uses are not diagnosed. if (const VariableArrayType *VAT = Context.getAsVariableArrayType(T)) - if (CurContext->isFunctionOrMethod()) + if (CurContext->isFunctionOrMethod() && + !(getCurScope() && getCurScope()->isFunctionPrototypeScope())) if (const Expr *Size = VAT->getSizeExpr()) VLASizeExprOwners.try_emplace(Size, CurContext); diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp index d3d245eed22fdd..7360ad571c6275 100644 --- a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp +++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp @@ -17,6 +17,24 @@ int by_copy_t() { return [] { X x; return (int)sizeof(x); }(); } +// An explicit cast to such a type captures the bound as well. +// CHECK-LABEL: define {{.*}} @_Z14cast_in_lambdav( +// CHECK: store i64 {{%.*}}, ptr {{%.*}} +// CHECK-LABEL: define internal {{.*}} @"_ZZ14cast_in_lambdavENK3$_0clEv"( +// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}} +// CHECK: mul nuw i64 4, [[BOUND]] +unsigned long cast_in_lambda() { + using P = int (*)[foo()]; + return [] { return sizeof(((P)0)[1]); }(); +} + +// The bound of the lambda's own parameter is not captured. +// CHECK-LABEL: define {{.*}} @_Z15own_param_boundv( +// CHECK-LABEL: define internal {{.*}} @"_ZZ15own_param_boundvENK3$_0clEiPA_i"(ptr noundef nonnull align 1 dereferenceable(1) %this, +int own_param_bound() { + return [](int m, int (*p)[m]) { decltype(p) q = p; return (int)sizeof(*q); }(1, nullptr); +} + // CHECK-LABEL: define {{.*}} @_Z8nested_tv( // CHECK-LABEL: define internal {{.*}} @"_ZZZ8nested_tvENK3$_0clEvENKUlvE_clEv"( // CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}} diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c b/clang/test/Sema/vla-enclosing-function-blocks.c index 7534089d6bf053..fbd7f4ebca7e90 100644 --- a/clang/test/Sema/vla-enclosing-function-blocks.c +++ b/clang/test/Sema/vla-enclosing-function-blocks.c @@ -17,6 +17,16 @@ void typedef_in_block(void) { ^{ typedef X Y; }(); // expected-error {{cannot be used in a block}} } +void cast_in_block(void) { + typedef int (*P)[size_tab()]; // expected-note {{size expression is evaluated here}} + ^{ (void)(P)0; }(); // expected-error {{cannot be used in a block}} +} + +// Valid: VLA parameter of the block itself. +int own_vla_param_in_block(void) { + return (^ int(int m, int (*p)[m]) { __typeof(p) q = p; return sizeof(*q); })(1, 0); +} + // Valid: VLA declared inside the block itself. int own_vla_in_block(void) { return (^ int(void) { int a[size_tab()]; return sizeof(a); })(); diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp b/clang/test/SemaCXX/vla-enclosing-function.cpp index 516a3ee604cd8b..7ce2fcfc91a0f3 100644 --- a/clang/test/SemaCXX/vla-enclosing-function.cpp +++ b/clang/test/SemaCXX/vla-enclosing-function.cpp @@ -30,11 +30,28 @@ void pointer_to_vla() { struct S { unsigned long f(P p) { return sizeof(*p); } }; // expected-error {{cannot be used in a local class}} } +void casts() { + using P = int (*)[foo()]; // expected-note 3 {{size expression is evaluated here}} + struct S { + void f() { + (void)(P)0; // expected-error {{cannot be used in a local class}} + (void)static_cast<P>(nullptr); // expected-error {{cannot be used in a local class}} + (void)P(nullptr); // expected-error {{cannot be used in a local class}} + } + }; +} + void typedef_in_member_function() { using X = int[foo()]; // expected-note {{size expression is evaluated here}} struct S { void f() { typedef X Y; } }; // expected-error {{cannot be used in a local class}} } +// FIXME: Bounds in a function prototype are not recorded, so this is not +// diagnosed. +void param_bound(int n, int (*p)[n]) { + struct S { void f() { decltype(p) q; } }; +} + template <typename T> void in_template() { using X = int[foo()]; // expected-note {{size expression is evaluated here}} struct S { S() { X x; } }; // expected-error {{cannot be used in a local class}} @@ -70,6 +87,10 @@ void param_vla_in_local_class() { struct S { unsigned long f(int n, int (*p)[n]) { return sizeof(*p); } }; } +unsigned long lambda_own_vla_param() { + return [](int m, int (*p)[m]) { decltype(p) q = p; return sizeof(*q); }(1, nullptr); +} + int lambda_sizeof() { using X = int[foo()]; return [&] { return (int)sizeof(X); }(); >From 2fbf3098481da28ddf3a920e8430280b8729fc2f Mon Sep 17 00:00:00 2001 From: Ivan Ugryumov <[email protected]> Date: Sun, 18 Oct 2026 14:00:13 +0300 Subject: [PATCH 6/6] Check all variably modified sizeof operands, not only typedefs --- clang/lib/Sema/SemaExpr.cpp | 3 +-- .../test/CodeGenCXX/vla-lambda-enclosing-function.cpp | 11 +++++++++++ clang/test/Sema/vla-enclosing-function-blocks.c | 7 +++++++ clang/test/SemaCXX/vla-enclosing-function.cpp | 11 +++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 6311f89baf459a..a87e257566e476 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4948,8 +4948,7 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType, ExprKind)) return true; - if (ExprType->getAs<TypedefType>() && - !CheckVariablyModifiedTypeUse(ExprType, OpLoc)) + if (!CheckVariablyModifiedTypeUse(ExprType, OpLoc)) return true; return false; diff --git a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp index 7360ad571c6275..3b8043eb3c311d 100644 --- a/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp +++ b/clang/test/CodeGenCXX/vla-lambda-enclosing-function.cpp @@ -28,6 +28,17 @@ unsigned long cast_in_lambda() { return [] { return sizeof(((P)0)[1]); }(); } +// So does sizeof of such a type. +// CHECK-LABEL: define {{.*}} @_Z13sizeof_lambdav( +// CHECK: store i64 {{%.*}}, ptr {{%.*}} +// CHECK-LABEL: define internal {{.*}} @"_ZZ13sizeof_lambdavENK3$_0clEv"( +// CHECK: [[BOUND:%[0-9]+]] = load i64, ptr {{%[0-9]+}} +// CHECK: mul nuw i64 4, [[BOUND]] +unsigned long sizeof_lambda() { + using X = int[foo()]; + return [] { return sizeof(X); }(); +} + // The bound of the lambda's own parameter is not captured. // CHECK-LABEL: define {{.*}} @_Z15own_param_boundv( // CHECK-LABEL: define internal {{.*}} @"_ZZ15own_param_boundvENK3$_0clEiPA_i"(ptr noundef nonnull align 1 dereferenceable(1) %this, diff --git a/clang/test/Sema/vla-enclosing-function-blocks.c b/clang/test/Sema/vla-enclosing-function-blocks.c index fbd7f4ebca7e90..16d15fcfac2faa 100644 --- a/clang/test/Sema/vla-enclosing-function-blocks.c +++ b/clang/test/Sema/vla-enclosing-function-blocks.c @@ -22,6 +22,13 @@ void cast_in_block(void) { ^{ (void)(P)0; }(); // expected-error {{cannot be used in a block}} } +// FIXME: The operand of sizeof is checked in an unevaluated context, so this +// is not diagnosed. +void sizeof_in_block(void) { + typedef int X[size_tab()]; + ^{ (void)sizeof(X); }(); +} + // Valid: VLA parameter of the block itself. int own_vla_param_in_block(void) { return (^ int(int m, int (*p)[m]) { __typeof(p) q = p; return sizeof(*q); })(1, 0); diff --git a/clang/test/SemaCXX/vla-enclosing-function.cpp b/clang/test/SemaCXX/vla-enclosing-function.cpp index 7ce2fcfc91a0f3..1375f0ca9a349c 100644 --- a/clang/test/SemaCXX/vla-enclosing-function.cpp +++ b/clang/test/SemaCXX/vla-enclosing-function.cpp @@ -52,6 +52,17 @@ void param_bound(int n, int (*p)[n]) { struct S { void f() { decltype(p) q; } }; } +// FIXME: The operand of sizeof is checked in an unevaluated context, so this +// is not diagnosed. +void sizeof_type() { + using X = int[foo()]; + int a[foo()]; + struct S { + unsigned long f() { return sizeof(X); } + unsigned long g() { return sizeof(decltype(a)); } + }; +} + template <typename T> void in_template() { using X = int[foo()]; // expected-note {{size expression is evaluated here}} struct S { S() { X x; } }; // expected-error {{cannot be used in a local class}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
