From: Jordan Rose Date: Thu, 11 Apr 2013 00:58:58 +0000 (+0000) Subject: Force a load when creating a reference to a temporary copied from a bitfield. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1fd1e288d0f45b86d191d8f53f569e5143f3a18a;p=clang Force a load when creating a reference to a temporary copied from a bitfield. For this source: const int &ref = someStruct.bitfield; We used to generate this AST: DeclStmt [...] `-VarDecl [...] ref 'const int &' `-MaterializeTemporaryExpr [...] 'const int' lvalue `-ImplicitCastExpr [...] 'const int' lvalue `-MemberExpr [...] 'int' lvalue bitfield .bitfield [...] `-DeclRefExpr [...] 'struct X' lvalue ParmVar [...] 'someStruct' 'struct X' Notice the lvalue inside the MaterializeTemporaryExpr, which is very confusing (and caused an assertion to fire in the analyzer - PR15694). We now generate this: DeclStmt [...] `-VarDecl [...] ref 'const int &' `-MaterializeTemporaryExpr [...] 'const int' lvalue `-ImplicitCastExpr [...] 'int' `-MemberExpr [...] 'int' lvalue bitfield .bitfield [...] `-DeclRefExpr [...] 'struct X' lvalue ParmVar [...] 'someStruct' 'struct X' Which makes a lot more sense. This allows us to remove code in both CodeGen and AST that hacked around this special case. The commit also makes Clang accept this (legal) C++11 code: int &&ref = std::move(someStruct).bitfield PR15694 / git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@179250 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Sema/Initialization.h b/include/clang/Sema/Initialization.h index 8459be16f4..9740318e02 100644 --- a/include/clang/Sema/Initialization.h +++ b/include/clang/Sema/Initialization.h @@ -594,6 +594,8 @@ public: SK_QualificationConversionXValue, /// \brief Perform a qualification conversion, producing an lvalue. SK_QualificationConversionLValue, + /// \brief Perform a load from a glvalue, producing an rvalue. + SK_LValueToRValue, /// \brief Perform an implicit conversion sequence. SK_ConversionSequence, /// \brief Perform list-initialization without a constructor @@ -911,6 +913,12 @@ public: void AddQualificationConversionStep(QualType Ty, ExprValueKind Category); + /// \brief Add a new step that performs a load of the given type. + /// + /// Although the term "LValueToRValue" is conventional, this applies to both + /// lvalues and xvalues. + void AddLValueToRValueStep(QualType Ty); + /// \brief Add a new step that applies an implicit conversion sequence. void AddConversionSequenceStep(const ImplicitConversionSequence &ICS, QualType T); diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index ae86150ee2..d7abe3082e 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -2865,25 +2865,12 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) { bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *E) { - if (E->GetTemporaryExpr()->isRValue()) { - if (E->getType()->isRecordType()) - return EvaluateTemporary(E->GetTemporaryExpr(), Result, Info); + if (E->getType()->isRecordType()) + return EvaluateTemporary(E->GetTemporaryExpr(), Result, Info); - Result.set(E, Info.CurrentCall->Index); - return EvaluateInPlace(Info.CurrentCall->Temporaries[E], Info, - Result, E->GetTemporaryExpr()); - } - - // Materialization of an lvalue temporary occurs when we need to force a copy - // (for instance, if it's a bitfield). - // FIXME: The AST should contain an lvalue-to-rvalue node for such cases. - if (!Visit(E->GetTemporaryExpr())) - return false; - if (!HandleLValueToRValueConversion(Info, E, E->getType(), Result, - Info.CurrentCall->Temporaries[E])) - return false; Result.set(E, Info.CurrentCall->Index); - return true; + return EvaluateInPlace(Info.CurrentCall->Temporaries[E], Info, + Result, E->GetTemporaryExpr()); } bool diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index a0dd158533..fe0d070105 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -228,157 +228,152 @@ EmitExprForReferenceBinding(CodeGenFunction &CGF, const Expr *E, InitializedDecl); } - RValue RV; if (E->isGLValue()) { // Emit the expression as an lvalue. LValue LV = CGF.EmitLValue(E); + assert(LV.isSimple()); + return LV.getAddress(); + } + + if (!ObjCARCReferenceLifetimeType.isNull()) { + ReferenceTemporary = CreateReferenceTemporary(CGF, + ObjCARCReferenceLifetimeType, + InitializedDecl); - if (LV.isSimple()) - return LV.getAddress(); - // We have to load the lvalue. - RV = CGF.EmitLoadOfLValue(LV); - } else { - if (!ObjCARCReferenceLifetimeType.isNull()) { - ReferenceTemporary = CreateReferenceTemporary(CGF, - ObjCARCReferenceLifetimeType, - InitializedDecl); - - - LValue RefTempDst = CGF.MakeAddrLValue(ReferenceTemporary, - ObjCARCReferenceLifetimeType); + LValue RefTempDst = CGF.MakeAddrLValue(ReferenceTemporary, + ObjCARCReferenceLifetimeType); - CGF.EmitScalarInit(E, dyn_cast_or_null(InitializedDecl), - RefTempDst, false); - - bool ExtendsLifeOfTemporary = false; - if (const VarDecl *Var = dyn_cast_or_null(InitializedDecl)) { - if (Var->extendsLifetimeOfTemporary()) - ExtendsLifeOfTemporary = true; - } else if (InitializedDecl && isa(InitializedDecl)) { + CGF.EmitScalarInit(E, dyn_cast_or_null(InitializedDecl), + RefTempDst, false); + + bool ExtendsLifeOfTemporary = false; + if (const VarDecl *Var = dyn_cast_or_null(InitializedDecl)) { + if (Var->extendsLifetimeOfTemporary()) ExtendsLifeOfTemporary = true; - } - - if (!ExtendsLifeOfTemporary) { - // Since the lifetime of this temporary isn't going to be extended, - // we need to clean it up ourselves at the end of the full expression. - switch (ObjCARCReferenceLifetimeType.getObjCLifetime()) { - case Qualifiers::OCL_None: - case Qualifiers::OCL_ExplicitNone: - case Qualifiers::OCL_Autoreleasing: - break; - - case Qualifiers::OCL_Strong: { - assert(!ObjCARCReferenceLifetimeType->isArrayType()); - CleanupKind cleanupKind = CGF.getARCCleanupKind(); - CGF.pushDestroy(cleanupKind, - ReferenceTemporary, - ObjCARCReferenceLifetimeType, - CodeGenFunction::destroyARCStrongImprecise, - cleanupKind & EHCleanup); - break; - } + } else if (InitializedDecl && isa(InitializedDecl)) { + ExtendsLifeOfTemporary = true; + } + + if (!ExtendsLifeOfTemporary) { + // Since the lifetime of this temporary isn't going to be extended, + // we need to clean it up ourselves at the end of the full expression. + switch (ObjCARCReferenceLifetimeType.getObjCLifetime()) { + case Qualifiers::OCL_None: + case Qualifiers::OCL_ExplicitNone: + case Qualifiers::OCL_Autoreleasing: + break; - case Qualifiers::OCL_Weak: - assert(!ObjCARCReferenceLifetimeType->isArrayType()); - CGF.pushDestroy(NormalAndEHCleanup, - ReferenceTemporary, - ObjCARCReferenceLifetimeType, - CodeGenFunction::destroyARCWeak, - /*useEHCleanupForArray*/ true); - break; - } + case Qualifiers::OCL_Strong: { + assert(!ObjCARCReferenceLifetimeType->isArrayType()); + CleanupKind cleanupKind = CGF.getARCCleanupKind(); + CGF.pushDestroy(cleanupKind, + ReferenceTemporary, + ObjCARCReferenceLifetimeType, + CodeGenFunction::destroyARCStrongImprecise, + cleanupKind & EHCleanup); + break; + } - ObjCARCReferenceLifetimeType = QualType(); + case Qualifiers::OCL_Weak: + assert(!ObjCARCReferenceLifetimeType->isArrayType()); + CGF.pushDestroy(NormalAndEHCleanup, + ReferenceTemporary, + ObjCARCReferenceLifetimeType, + CodeGenFunction::destroyARCWeak, + /*useEHCleanupForArray*/ true); + break; } - return ReferenceTemporary; - } - - SmallVector Adjustments; - E = E->skipRValueSubobjectAdjustments(Adjustments); - if (const OpaqueValueExpr *opaque = dyn_cast(E)) - if (opaque->getType()->isRecordType()) - return CGF.EmitOpaqueValueLValue(opaque).getAddress(); - - // Create a reference temporary if necessary. - AggValueSlot AggSlot = AggValueSlot::ignored(); - if (CGF.hasAggregateEvaluationKind(E->getType())) { - ReferenceTemporary = CreateReferenceTemporary(CGF, E->getType(), - InitializedDecl); - CharUnits Alignment = CGF.getContext().getTypeAlignInChars(E->getType()); - AggValueSlot::IsDestructed_t isDestructed - = AggValueSlot::IsDestructed_t(InitializedDecl != 0); - AggSlot = AggValueSlot::forAddr(ReferenceTemporary, Alignment, - Qualifiers(), isDestructed, - AggValueSlot::DoesNotNeedGCBarriers, - AggValueSlot::IsNotAliased); + ObjCARCReferenceLifetimeType = QualType(); } - if (InitializedDecl) { - if (const InitListExpr *ILE = dyn_cast(E)) { - if (ILE->initializesStdInitializerList()) { - ReferenceInitializerList = ILE; - } - } - else if (const RecordType *RT = - E->getType()->getBaseElementTypeUnsafe()->getAs()){ - // Get the destructor for the reference temporary. - CXXRecordDecl *ClassDecl = cast(RT->getDecl()); - if (!ClassDecl->hasTrivialDestructor()) - ReferenceTemporaryDtor = ClassDecl->getDestructor(); + return ReferenceTemporary; + } + + SmallVector Adjustments; + E = E->skipRValueSubobjectAdjustments(Adjustments); + if (const OpaqueValueExpr *opaque = dyn_cast(E)) + if (opaque->getType()->isRecordType()) + return CGF.EmitOpaqueValueLValue(opaque).getAddress(); + + // Create a reference temporary if necessary. + AggValueSlot AggSlot = AggValueSlot::ignored(); + if (CGF.hasAggregateEvaluationKind(E->getType())) { + ReferenceTemporary = CreateReferenceTemporary(CGF, E->getType(), + InitializedDecl); + CharUnits Alignment = CGF.getContext().getTypeAlignInChars(E->getType()); + AggValueSlot::IsDestructed_t isDestructed + = AggValueSlot::IsDestructed_t(InitializedDecl != 0); + AggSlot = AggValueSlot::forAddr(ReferenceTemporary, Alignment, + Qualifiers(), isDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased); + } + + if (InitializedDecl) { + if (const InitListExpr *ILE = dyn_cast(E)) { + if (ILE->initializesStdInitializerList()) { + ReferenceInitializerList = ILE; } } + else if (const RecordType *RT = + E->getType()->getBaseElementTypeUnsafe()->getAs()){ + // Get the destructor for the reference temporary. + CXXRecordDecl *ClassDecl = cast(RT->getDecl()); + if (!ClassDecl->hasTrivialDestructor()) + ReferenceTemporaryDtor = ClassDecl->getDestructor(); + } + } - RV = CGF.EmitAnyExpr(E, AggSlot); - - // Check if need to perform derived-to-base casts and/or field accesses, to - // get from the temporary object we created (and, potentially, for which we - // extended the lifetime) to the subobject we're binding the reference to. - if (!Adjustments.empty()) { - llvm::Value *Object = RV.getAggregateAddr(); - for (unsigned I = Adjustments.size(); I != 0; --I) { - SubobjectAdjustment &Adjustment = Adjustments[I-1]; - switch (Adjustment.Kind) { - case SubobjectAdjustment::DerivedToBaseAdjustment: - Object = - CGF.GetAddressOfBaseClass(Object, - Adjustment.DerivedToBase.DerivedClass, - Adjustment.DerivedToBase.BasePath->path_begin(), - Adjustment.DerivedToBase.BasePath->path_end(), - /*NullCheckValue=*/false); - break; - - case SubobjectAdjustment::FieldAdjustment: { - LValue LV = CGF.MakeAddrLValue(Object, E->getType()); - LV = CGF.EmitLValueForField(LV, Adjustment.Field); - if (LV.isSimple()) { - Object = LV.getAddress(); - break; - } + RValue RV = CGF.EmitAnyExpr(E, AggSlot); + + // Check if need to perform derived-to-base casts and/or field accesses, to + // get from the temporary object we created (and, potentially, for which we + // extended the lifetime) to the subobject we're binding the reference to. + if (!Adjustments.empty()) { + llvm::Value *Object = RV.getAggregateAddr(); + for (unsigned I = Adjustments.size(); I != 0; --I) { + SubobjectAdjustment &Adjustment = Adjustments[I-1]; + switch (Adjustment.Kind) { + case SubobjectAdjustment::DerivedToBaseAdjustment: + Object = + CGF.GetAddressOfBaseClass(Object, + Adjustment.DerivedToBase.DerivedClass, + Adjustment.DerivedToBase.BasePath->path_begin(), + Adjustment.DerivedToBase.BasePath->path_end(), + /*NullCheckValue=*/false); + break; - // For non-simple lvalues, we actually have to create a copy of - // the object we're binding to. - QualType T = Adjustment.Field->getType().getNonReferenceType() - .getUnqualifiedType(); - Object = CreateReferenceTemporary(CGF, T, InitializedDecl); - LValue TempLV = CGF.MakeAddrLValue(Object, - Adjustment.Field->getType()); - CGF.EmitStoreThroughLValue(CGF.EmitLoadOfLValue(LV), TempLV); - break; - } - - case SubobjectAdjustment::MemberPointerAdjustment: { - llvm::Value *Ptr = CGF.EmitScalarExpr(Adjustment.Ptr.RHS); - Object = CGF.CGM.getCXXABI().EmitMemberDataPointerAddress( - CGF, Object, Ptr, Adjustment.Ptr.MPT); + case SubobjectAdjustment::FieldAdjustment: { + LValue LV = CGF.MakeAddrLValue(Object, E->getType()); + LV = CGF.EmitLValueForField(LV, Adjustment.Field); + if (LV.isSimple()) { + Object = LV.getAddress(); break; } - } + + // For non-simple lvalues, we actually have to create a copy of + // the object we're binding to. + QualType T = Adjustment.Field->getType().getNonReferenceType() + .getUnqualifiedType(); + Object = CreateReferenceTemporary(CGF, T, InitializedDecl); + LValue TempLV = CGF.MakeAddrLValue(Object, + Adjustment.Field->getType()); + CGF.EmitStoreThroughLValue(CGF.EmitLoadOfLValue(LV), TempLV); + break; } - return Object; + case SubobjectAdjustment::MemberPointerAdjustment: { + llvm::Value *Ptr = CGF.EmitScalarExpr(Adjustment.Ptr.RHS); + Object = CGF.CGM.getCXXABI().EmitMemberDataPointerAddress( + CGF, Object, Ptr, Adjustment.Ptr.MPT); + break; + } + } } + + return Object; } if (RV.isAggregate()) diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp index 63309e376e..cd59a328f0 100644 --- a/lib/Sema/SemaInit.cpp +++ b/lib/Sema/SemaInit.cpp @@ -2409,6 +2409,7 @@ void InitializationSequence::Step::Destroy() { case SK_QualificationConversionRValue: case SK_QualificationConversionXValue: case SK_QualificationConversionLValue: + case SK_LValueToRValue: case SK_ListInitialization: case SK_ListConstructorCall: case SK_UnwrapInitList: @@ -2555,6 +2556,15 @@ void InitializationSequence::AddQualificationConversionStep(QualType Ty, Steps.push_back(S); } +void InitializationSequence::AddLValueToRValueStep(QualType Ty) { + assert(!Ty.hasQualifiers() && "rvalues may not have qualifiers"); + + Step S; + S.Kind = SK_LValueToRValue; + S.Type = Ty; + Steps.push_back(S); +} + void InitializationSequence::AddConversionSequenceStep( const ImplicitConversionSequence &ICS, QualType T) { @@ -3351,6 +3361,57 @@ static void TryReferenceInitialization(Sema &S, T1Quals, cv2T2, T2, T2Quals, Sequence); } +/// Converts the target of reference initialization so that it has the +/// appropriate qualifiers and value kind. +/// +/// In this case, 'x' is an 'int' lvalue, but it needs to be 'const int'. +/// \code +/// int x; +/// const int &r = x; +/// \endcode +/// +/// In this case the reference is binding to a bitfield lvalue, which isn't +/// valid. Perform a load to create a lifetime-extended temporary instead. +/// \code +/// const int &r = someStruct.bitfield; +/// \endcode +static ExprValueKind +convertQualifiersAndValueKindIfNecessary(Sema &S, + InitializationSequence &Sequence, + Expr *Initializer, + QualType cv1T1, + Qualifiers T1Quals, + Qualifiers T2Quals, + bool IsLValueRef) { + bool IsNonAddressableType = Initializer->getBitField() || + Initializer->refersToVectorElement(); + + if (IsNonAddressableType) { + // C++11 [dcl.init.ref]p5: [...] Otherwise, the reference shall be an + // lvalue reference to a non-volatile const type, or the reference shall be + // an rvalue reference. + // + // If not, we can't make a temporary and bind to that. Give up and allow the + // error to be diagnosed later. + if (IsLValueRef && (!T1Quals.hasConst() || T1Quals.hasVolatile())) { + assert(Initializer->isGLValue()); + return Initializer->getValueKind(); + } + + // Force a load so we can materialize a temporary. + Sequence.AddLValueToRValueStep(cv1T1.getUnqualifiedType()); + return VK_RValue; + } + + if (T1Quals != T2Quals) { + Sequence.AddQualificationConversionStep(cv1T1, + Initializer->getValueKind()); + } + + return Initializer->getValueKind(); +} + + /// \brief Reference initialization without resolving overloaded functions. static void TryReferenceInitializationCore(Sema &S, const InitializedEntity &Entity, @@ -3406,11 +3467,11 @@ static void TryReferenceInitializationCore(Sema &S, Sequence.AddObjCObjectConversionStep( S.Context.getQualifiedType(T1, T2Quals)); - if (T1Quals != T2Quals) - Sequence.AddQualificationConversionStep(cv1T1, VK_LValue); - bool BindingTemporary = T1Quals.hasConst() && !T1Quals.hasVolatile() && - (Initializer->getBitField() || Initializer->refersToVectorElement()); - Sequence.AddReferenceBindingStep(cv1T1, BindingTemporary); + ExprValueKind ValueKind = + convertQualifiersAndValueKindIfNecessary(S, Sequence, Initializer, + cv1T1, T1Quals, T2Quals, + isLValueRef); + Sequence.AddReferenceBindingStep(cv1T1, ValueKind == VK_RValue); return; } @@ -3493,10 +3554,12 @@ static void TryReferenceInitializationCore(Sema &S, Sequence.AddObjCObjectConversionStep( S.Context.getQualifiedType(T1, T2Quals)); - if (T1Quals != T2Quals) - Sequence.AddQualificationConversionStep(cv1T1, ValueKind); - Sequence.AddReferenceBindingStep(cv1T1, - /*bindingTemporary=*/InitCategory.isPRValue()); + ValueKind = convertQualifiersAndValueKindIfNecessary(S, Sequence, + Initializer, cv1T1, + T1Quals, T2Quals, + isLValueRef); + + Sequence.AddReferenceBindingStep(cv1T1, ValueKind == VK_RValue); return; } @@ -4988,6 +5051,7 @@ InitializationSequence::Perform(Sema &S, case SK_QualificationConversionLValue: case SK_QualificationConversionXValue: case SK_QualificationConversionRValue: + case SK_LValueToRValue: case SK_ConversionSequence: case SK_ListInitialization: case SK_UnwrapInitList: @@ -5104,6 +5168,9 @@ InitializationSequence::Perform(Sema &S, break; case SK_BindReferenceToTemporary: + // Make sure the "temporary" is actually an rvalue. + assert(CurInit.get()->isRValue() && "not a temporary"); + // Check exception specifications if (S.CheckExceptionSpecCompatibility(CurInit.get(), DestType)) return ExprError(); @@ -5241,6 +5308,16 @@ InitializationSequence::Perform(Sema &S, break; } + case SK_LValueToRValue: { + assert(CurInit.get()->isGLValue() && "cannot load from a prvalue"); + CurInit = S.Owned(ImplicitCastExpr::Create(S.Context, Step->Type, + CK_LValueToRValue, + CurInit.take(), + /*BasePath=*/0, + VK_RValue)); + break; + } + case SK_ConversionSequence: { Sema::CheckedConversionKind CCK = Kind.isCStyleCast()? Sema::CCK_CStyleCast @@ -6185,6 +6262,10 @@ void InitializationSequence::dump(raw_ostream &OS) const { OS << "qualification conversion (lvalue)"; break; + case SK_LValueToRValue: + OS << "load (lvalue to rvalue)"; + break; + case SK_ConversionSequence: OS << "implicit conversion sequence ("; S->ICS->DebugPrint(); // FIXME: use OS diff --git a/test/Analysis/reference.cpp b/test/Analysis/reference.cpp index 8dd0baf8c3..bcab80b04b 100644 --- a/test/Analysis/reference.cpp +++ b/test/Analysis/reference.cpp @@ -224,3 +224,13 @@ namespace rdar11212286 { return *x; // no-warning } } + +namespace PR15694 { + class C { + bool bit : 1; + template void bar(const T &obj) {} + void foo() { + bar(bit); // don't crash + } + }; +} diff --git a/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp b/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp index 812d0de56b..fdfa6781fe 100644 --- a/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp +++ b/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp @@ -200,3 +200,37 @@ namespace rdar13278115 { X &&f1(Y &y) { return y; } // expected-error{{rvalue reference to type 'rdar13278115::X' cannot bind to lvalue of type 'rdar13278115::Y'}} const X &&f2(Y &y) { return y; } // expected-error{{rvalue reference to type 'const rdar13278115::X' cannot bind to lvalue of type 'rdar13278115::Y'}} } + +namespace bitfields { + struct IntBitfield { + int i : 17; // expected-note 3 {{bit-field is declared here}} + }; + + // A simplified version of std::move. + template + T &&move(T &obj) { + return static_cast(obj); + } + + void test() { + int & ir1 = (lvalue().i); // expected-error{{non-const reference cannot bind to bit-field 'i'}} + int & ir2 = (xvalue().i); // expected-error{{non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'}} + int && ir3 = (xvalue().i); // no-warning + int && ir4 = move(lvalue()).i; // no-warning + + volatile int & vir1 = (lvalue().i); // expected-error{{non-const reference cannot bind to bit-field 'i'}} + volatile int & vir2 = (xvalue().i); // expected-error{{volatile lvalue reference to type 'volatile int' cannot bind to a temporary of type 'int'}} + volatile int && vir3 = (xvalue().i); // no-warning + volatile int && vir4 = move(lvalue()).i; // no-warning + + const int & cir1 = (lvalue().i); // no-warning + const int & cir2 = (xvalue().i); // no-warning + const int && cir3 = (xvalue().i); // no-warning + const int && cir4 = move(lvalue()).i; // no-warning + + const volatile int & cvir1 = (lvalue().i); // expected-error{{non-const reference cannot bind to bit-field 'i'}} + const volatile int & cvir2 = (xvalue().i); // expected-error{{volatile lvalue reference to type 'const volatile int' cannot bind to a temporary of type 'int'}} + const volatile int && cvir3 = (xvalue().i); // no-warning + const volatile int && cvir4 = move(lvalue()).i; // no-warning + } +}