diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1353,6 +1353,10 @@ bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false; + bool ExtendCXXForRangeInitVariableLifetime = false; + + bool MaterializePRValueTemporaryInDiscardStatement = false; + // When evaluating immediate functions in the initializer of a default // argument or default member initializer, this is the declaration whose // default initializer is being evaluated and the location of the call @@ -9835,6 +9839,18 @@ return ExprEvalContexts.back().isImmediateFunctionContext(); } + bool isExtendXXForRangeInitVariableLifetimeContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().ExtendCXXForRangeInitVariableLifetime; + } + + bool isMaterializePRValueTemporaryInDiscardStatement() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().MaterializePRValueTemporaryInDiscardStatement; + } + bool isCheckingDefaultArgumentOrInitializer() const { assert(!ExprEvalContexts.empty() && "Must be in an expression evaluation context"); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2239,6 +2239,16 @@ bool IsForRangeLoop = false; if (TryConsumeToken(tok::colon, FRI->ColonLoc)) { IsForRangeLoop = true; + EnterExpressionEvaluationContext ForRangeInitContext( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, + /*LambdaContextDecl=*/nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, + getLangOpts().CPlusPlus23); + if (getLangOpts().CPlusPlus23) { + auto &LastRecord = Actions.ExprEvalContexts.back(); + LastRecord.MaterializePRValueTemporaryInDiscardStatement = true; + } + if (getLangOpts().OpenMP) Actions.startOpenMPCXXRangeFor(); if (Tok.is(tok::l_brace)) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -8185,21 +8185,6 @@ E = result.get(); } - // C99 6.3.2.1: - // [Except in specific positions,] an lvalue that does not have - // array type is converted to the value stored in the - // designated object (and is no longer an lvalue). - if (E->isPRValue()) { - // In C, function designators (i.e. expressions of function type) - // are r-values, but we still want to do function-to-pointer decay - // on them. This is both technically correct and convenient for - // some clients. - if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType()) - return DefaultFunctionArrayConversion(E); - - return E; - } - if (getLangOpts().CPlusPlus) { // The C++11 standard defines the notion of a discarded-value expression; // normally, we don't need to do anything to handle it, but if it is a @@ -8225,6 +8210,29 @@ // just clutter. // FIXME: We don't emit lifetime markers for the temporaries due to this. // FIXME: Do any other AST consumers care about this? + if (isMaterializePRValueTemporaryInDiscardStatement() && + getLangOpts().CPlusPlus17 && E->isPRValue() && + !E->getType()->isVoidType()) { + ExprResult Res = TemporaryMaterializationConversion(E); + if (Res.isInvalid()) + return E; + E = Res.get(); + } + return E; + } + + // C99 6.3.2.1: + // [Except in specific positions,] an lvalue that does not have + // array type is converted to the value stored in the + // designated object (and is no longer an lvalue). + if (E->isPRValue()) { + // In C, function designators (i.e. expressions of function type) + // are r-values, but we still want to do function-to-pointer decay + // on them. This is both technically correct and convenient for + // some clients. + if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType()) + return DefaultFunctionArrayConversion(E); + return E; } diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7357,12 +7357,13 @@ }); } -static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, +static void visitLocalsRetainedByInitializer(Sema &S, IndirectLocalPath &Path, Expr *Init, LocalVisitor Visit, bool RevisitSubinits, bool EnableLifetimeWarnings); -static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, +static void visitLocalsRetainedByReferenceBinding(Sema &S, + IndirectLocalPath &Path, Expr *Init, ReferenceKind RK, LocalVisitor Visit, bool EnableLifetimeWarnings); @@ -7448,7 +7449,7 @@ return false; } -static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call, +static void handleGslAnnotatedTypes(Sema &S, IndirectLocalPath &Path, Expr *Call, LocalVisitor Visit) { auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) { // We are not interested in the temporary base objects of gsl Pointers: @@ -7469,11 +7470,11 @@ : IndirectLocalPathEntry::GslReferenceInit, Arg, D}); if (Arg->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, Arg, RK_ReferenceBinding, Visit, /*EnableLifetimeWarnings=*/true); else - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings=*/true); Path.pop_back(); }; @@ -7544,7 +7545,7 @@ return false; } -static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call, +static void visitLifetimeBoundArguments(Sema &S, IndirectLocalPath &Path, Expr *Call, LocalVisitor Visit) { const FunctionDecl *Callee; ArrayRef Args; @@ -7571,11 +7572,11 @@ auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) { Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D}); if (Arg->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, Arg, RK_ReferenceBinding, Visit, /*EnableLifetimeWarnings=*/false); else - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings=*/false); Path.pop_back(); }; @@ -7586,14 +7587,15 @@ for (unsigned I = 0, N = std::min(Callee->getNumParams(), Args.size()); I != N; ++I) { - if (Callee->getParamDecl(I)->hasAttr()) + if (Callee->getParamDecl(I)->hasAttr() || + S.isExtendXXForRangeInitVariableLifetimeContext()) VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]); } } /// Visit the locals that would be reachable through a reference bound to the /// glvalue expression \c Init. -static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, +static void visitLocalsRetainedByReferenceBinding(Sema &S, IndirectLocalPath &Path, Expr *Init, ReferenceKind RK, LocalVisitor Visit, bool EnableLifetimeWarnings) { @@ -7615,8 +7617,16 @@ // Step over any subobject adjustments; we may have a materialized // temporary inside them. - Init = const_cast(Init->skipRValueSubobjectAdjustments()); + SmallVector CommaLHS; + SmallVector Adjustments; + Init = const_cast( + Init->skipRValueSubobjectAdjustments(CommaLHS, Adjustments)); + llvm::for_each(CommaLHS, [&](const Expr *LHS) { + visitLocalsRetainedByReferenceBinding(S, Path, const_cast(LHS), + RK_ReferenceBinding, Visit, + EnableLifetimeWarnings); + }); // Per current approach for DR1376, look through casts to reference type // when performing lifetime extension. if (CastExpr *CE = dyn_cast(Init)) @@ -7633,7 +7643,7 @@ else // We can't lifetime extend through this but we might still find some // retained temporaries. - return visitLocalsRetainedByInitializer(Path, Init, Visit, true, + return visitLocalsRetainedByInitializer(S, Path, Init, Visit, true, EnableLifetimeWarnings); } @@ -7648,14 +7658,14 @@ if (auto *MTE = dyn_cast(Init)) { if (Visit(Path, Local(MTE), RK)) - visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, MTE->getSubExpr(), Visit, true, EnableLifetimeWarnings); } if (isa(Init)) { if (EnableLifetimeWarnings) - handleGslAnnotatedTypes(Path, Init, Visit); - return visitLifetimeBoundArguments(Path, Init, Visit); + handleGslAnnotatedTypes(S, Path, Init, Visit); + return visitLifetimeBoundArguments(S, Path, Init, Visit); } switch (Init->getStmtClass()) { @@ -7674,7 +7684,7 @@ break; } else if (VD->getInit() && !isVarOnPath(Path, VD)) { Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD}); - visitLocalsRetainedByReferenceBinding(Path, VD->getInit(), + visitLocalsRetainedByReferenceBinding(S, Path, VD->getInit(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); } @@ -7688,13 +7698,13 @@ // handling all sorts of rvalues passed to a unary operator. const UnaryOperator *U = cast(Init); if (U->getOpcode() == UO_Deref) - visitLocalsRetainedByInitializer(Path, U->getSubExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, U->getSubExpr(), Visit, true, EnableLifetimeWarnings); break; } case Stmt::OMPArraySectionExprClass: { - visitLocalsRetainedByInitializer(Path, + visitLocalsRetainedByInitializer(S, Path, cast(Init)->getBase(), Visit, true, EnableLifetimeWarnings); break; @@ -7704,10 +7714,10 @@ case Stmt::BinaryConditionalOperatorClass: { auto *C = cast(Init); if (!C->getTrueExpr()->getType()->isVoidType()) - visitLocalsRetainedByReferenceBinding(Path, C->getTrueExpr(), RK, Visit, + visitLocalsRetainedByReferenceBinding(S, Path, C->getTrueExpr(), RK, Visit, EnableLifetimeWarnings); if (!C->getFalseExpr()->getType()->isVoidType()) - visitLocalsRetainedByReferenceBinding(Path, C->getFalseExpr(), RK, Visit, + visitLocalsRetainedByReferenceBinding(S, Path, C->getFalseExpr(), RK, Visit, EnableLifetimeWarnings); break; } @@ -7721,7 +7731,7 @@ /// Visit the locals that would be reachable through an object initialized by /// the prvalue expression \c Init. -static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, +static void visitLocalsRetainedByInitializer(Sema &S, IndirectLocalPath &Path, Expr *Init, LocalVisitor Visit, bool RevisitSubinits, bool EnableLifetimeWarnings) { @@ -7757,19 +7767,19 @@ // initializer. Path.push_back({IndirectLocalPathEntry::LValToRVal, CE}); return visitLocalsRetainedByReferenceBinding( - Path, Init, RK_ReferenceBinding, + S, Path, Init, RK_ReferenceBinding, [&](IndirectLocalPath &Path, Local L, ReferenceKind RK) -> bool { if (auto *DRE = dyn_cast(L)) { auto *VD = dyn_cast(DRE->getDecl()); if (VD && VD->getType().isConstQualified() && VD->getInit() && !isVarOnPath(Path, VD)) { Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD}); - visitLocalsRetainedByInitializer(Path, VD->getInit(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, VD->getInit(), Visit, true, EnableLifetimeWarnings); } } else if (auto *MTE = dyn_cast(L)) { if (MTE->getType().isConstQualified()) - visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, + visitLocalsRetainedByInitializer(S, Path, MTE->getSubExpr(), Visit, true, EnableLifetimeWarnings); } return false; @@ -7802,7 +7812,7 @@ // Model array-to-pointer decay as taking the address of the array // lvalue. Path.push_back({IndirectLocalPathEntry::AddressOf, CE}); - return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(), + return visitLocalsRetainedByReferenceBinding(S, Path, CE->getSubExpr(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); @@ -7818,7 +7828,7 @@ // initializing an initializer_list object from the array extends the // lifetime of the array exactly like binding a reference to a temporary. if (auto *ILE = dyn_cast(Init)) - return visitLocalsRetainedByReferenceBinding(Path, ILE->getSubExpr(), + return visitLocalsRetainedByReferenceBinding(S, Path, ILE->getSubExpr(), RK_StdInitializerList, Visit, EnableLifetimeWarnings); @@ -7830,13 +7840,13 @@ return; if (ILE->isTransparent()) - return visitLocalsRetainedByInitializer(Path, ILE->getInit(0), Visit, + return visitLocalsRetainedByInitializer(S, Path, ILE->getInit(0), Visit, RevisitSubinits, EnableLifetimeWarnings); if (ILE->getType()->isArrayType()) { for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I) - visitLocalsRetainedByInitializer(Path, ILE->getInit(I), Visit, + visitLocalsRetainedByInitializer(S, Path, ILE->getInit(I), Visit, RevisitSubinits, EnableLifetimeWarnings); return; @@ -7850,13 +7860,13 @@ // bound to temporaries, those temporaries are also lifetime-extended. if (RD->isUnion() && ILE->getInitializedFieldInUnion() && ILE->getInitializedFieldInUnion()->getType()->isReferenceType()) - visitLocalsRetainedByReferenceBinding(Path, ILE->getInit(0), + visitLocalsRetainedByReferenceBinding(S, Path, ILE->getInit(0), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else { unsigned Index = 0; for (; Index < RD->getNumBases() && Index < ILE->getNumInits(); ++Index) - visitLocalsRetainedByInitializer(Path, ILE->getInit(Index), Visit, + visitLocalsRetainedByInitializer(S, Path, ILE->getInit(Index), Visit, RevisitSubinits, EnableLifetimeWarnings); for (const auto *I : RD->fields()) { @@ -7866,14 +7876,14 @@ continue; Expr *SubInit = ILE->getInit(Index); if (I->getType()->isReferenceType()) - visitLocalsRetainedByReferenceBinding(Path, SubInit, + visitLocalsRetainedByReferenceBinding(S, Path, SubInit, RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else // This might be either aggregate-initialization of a member or // initialization of a std::initializer_list object. Regardless, // we should recursively lifetime-extend that initializer. - visitLocalsRetainedByInitializer(Path, SubInit, Visit, + visitLocalsRetainedByInitializer(S, Path, SubInit, Visit, RevisitSubinits, EnableLifetimeWarnings); ++Index; @@ -7895,10 +7905,10 @@ if (Cap.capturesVariable()) Path.push_back({IndirectLocalPathEntry::LambdaCaptureInit, E, &Cap}); if (E->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, E, RK_ReferenceBinding, + visitLocalsRetainedByReferenceBinding(S, Path, E, RK_ReferenceBinding, Visit, EnableLifetimeWarnings); else - visitLocalsRetainedByInitializer(Path, E, Visit, true, + visitLocalsRetainedByInitializer(S, Path, E, Visit, true, EnableLifetimeWarnings); if (Cap.capturesVariable()) Path.pop_back(); @@ -7913,7 +7923,7 @@ Expr *Arg = MTE->getSubExpr(); Path.push_back({IndirectLocalPathEntry::TemporaryCopy, Arg, CCE->getConstructor()}); - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + visitLocalsRetainedByInitializer(S, Path, Arg, Visit, true, /*EnableLifetimeWarnings*/false); Path.pop_back(); } @@ -7922,8 +7932,8 @@ if (isa(Init) || isa(Init)) { if (EnableLifetimeWarnings) - handleGslAnnotatedTypes(Path, Init, Visit); - return visitLifetimeBoundArguments(Path, Init, Visit); + handleGslAnnotatedTypes(S, Path, Init, Visit); + return visitLifetimeBoundArguments(S, Path, Init, Visit); } switch (Init->getStmtClass()) { @@ -7939,7 +7949,7 @@ return; Path.push_back({IndirectLocalPathEntry::AddressOf, UO}); - visitLocalsRetainedByReferenceBinding(Path, UO->getSubExpr(), + visitLocalsRetainedByReferenceBinding(S, Path, UO->getSubExpr(), RK_ReferenceBinding, Visit, EnableLifetimeWarnings); } @@ -7954,10 +7964,10 @@ break; if (BO->getLHS()->getType()->isPointerType()) - visitLocalsRetainedByInitializer(Path, BO->getLHS(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, BO->getLHS(), Visit, true, EnableLifetimeWarnings); else if (BO->getRHS()->getType()->isPointerType()) - visitLocalsRetainedByInitializer(Path, BO->getRHS(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, BO->getRHS(), Visit, true, EnableLifetimeWarnings); break; } @@ -7968,10 +7978,10 @@ // In C++, we can have a throw-expression operand, which has 'void' type // and isn't interesting from a lifetime perspective. if (!C->getTrueExpr()->getType()->isVoidType()) - visitLocalsRetainedByInitializer(Path, C->getTrueExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, C->getTrueExpr(), Visit, true, EnableLifetimeWarnings); if (!C->getFalseExpr()->getType()->isVoidType()) - visitLocalsRetainedByInitializer(Path, C->getFalseExpr(), Visit, true, + visitLocalsRetainedByInitializer(S, Path, C->getFalseExpr(), Visit, true, EnableLifetimeWarnings); break; } @@ -8083,6 +8093,12 @@ auto *MTE = dyn_cast(L); + if (MTE && isExtendXXForRangeInitVariableLifetimeContext()) { + MTE->setExtendingDecl(ExtendingEntity->getDecl(), + ExtendingEntity->allocateManglingNumber()); + return true; + } + bool IsGslPtrInitWithGslTempOwner = false; bool IsLocalGslOwner = false; if (pathOnlyInitializesGslPointer(Path)) { @@ -8330,11 +8346,11 @@ diag::warn_dangling_lifetime_pointer, SourceLocation()); llvm::SmallVector Path; if (Init->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, - TemporaryVisitor, + visitLocalsRetainedByReferenceBinding(*this, Path, Init, + RK_ReferenceBinding, TemporaryVisitor, EnableLifetimeWarnings); else - visitLocalsRetainedByInitializer(Path, Init, TemporaryVisitor, false, + visitLocalsRetainedByInitializer(*this, Path, Init, TemporaryVisitor, false, EnableLifetimeWarnings); } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -27,6 +27,7 @@ #include "clang/AST/TypeOrdering.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Ownership.h" @@ -2529,10 +2530,20 @@ VarDecl *RangeVar = BuildForRangeVarDecl(*this, RangeLoc, Context.getAutoRRefDeductType(), std::string("__range") + DepthStr); - if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc, - diag::err_for_range_deduction_failure)) { - ActOnInitializerError(LoopVar); - return StmtError(); + { + EnterExpressionEvaluationContext RangeVarContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated, /*LambdaContextDecl=*/nullptr, + ExpressionEvaluationContextRecord::EK_Other, getLangOpts().CPlusPlus23); + if (getLangOpts().CPlusPlus23) { + auto &LastRecord = ExprEvalContexts.back(); + LastRecord.ExtendCXXForRangeInitVariableLifetime = true; + Cleanup = LastRecord.ParentCleanup; + } + if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc, + diag::err_for_range_deduction_failure)) { + ActOnInitializerError(LoopVar); + return StmtError(); + } } // Claim the type doesn't contain auto: we've already done the checking. diff --git a/clang/test/AST/ast-dump-for-range-lifetime.cpp b/clang/test/AST/ast-dump-for-range-lifetime.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/ast-dump-for-range-lifetime.cpp @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -std=c++23 -triple x86_64-linux-gnu -fcxx-exceptions -ast-dump %s \ +// RUN: | FileCheck -strict-whitespace %s + +namespace p2718r0 { +struct T { + int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + T() {} + ~T() {} + const int *begin() const { return a; } + const int *end() const { return a + 10; } +}; + +const T &f1(const T &t) { return t; } +T g() { return T(); } + +void foo() { + // CHECK: FunctionDecl {{.*}} foo 'void ()' + // CHECK: `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'const T &' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'const T':'const p2718r0::T' lvalue + // CHECK-NEXT: | `-CallExpr {{.*}} 'const T':'const p2718r0::T' lvalue + // CHECK-NEXT: | |-ImplicitCastExpr {{.*}} 'const T &(*)(const T &)' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'const T &(const T &)' lvalue Function {{.*}} 'f1' 'const T &(const T &)' + // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'const T':'const p2718r0::T' lvalue extended by Var {{.*}} '__range1' 'const T &' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'const T':'const p2718r0::T' + // CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'T':'p2718r0::T' (CXXTemporary {{.*}}) + // CHECK-NEXT: | `-CallExpr {{.*}} 'T':'p2718r0::T' + // CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'T (*)()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'T ()' lvalue Function {{.*}} 'g' 'T ()' + [[maybe_unused]] int sum = 0; + for (auto e : f1(g())) + sum += e; +} + +struct LockGuard { + LockGuard(int) {} + + ~LockGuard() {} +}; + +void f() { + int v[] = {42, 17, 13}; + int M = 0; + + // CHECK: FunctionDecl {{.*}} f 'void ()' + // CHECK: `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} col:16 implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CXXStaticCastExpr {{.*}}'void' static_cast + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'LockGuard':'p2718r0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXFunctionalCastExpr {{.*}} 'LockGuard':'p2718r0::LockGuard' functional cast to LockGuard + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'p2718r0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXConstructExpr {{.*}} 'LockGuard':'p2718r0::LockGuard' 'void (int)' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'int' + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'int' lvalue Var {{.*}} 'M' 'int' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for (int x : static_cast(LockGuard(M)), v) // lock released in C++ 2020 + { + LockGuard guard(M); // OK in C++ 2020, now deadlocks + } +} + +} // namespace p2718r0