From 5528ac9f40ec6cb54e7096908bf2beeed511bce4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Sun, 5 May 2013 23:31:59 +0000 Subject: [PATCH] C++1y: support for increment and decrement in constant expression evaluation. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@181173 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticASTKinds.td | 26 +- lib/AST/ExprConstant.cpp | 246 ++++++++++++++++-- .../CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp | 2 +- test/SemaCXX/constant-expression-cxx1y.cpp | 80 +++++- 4 files changed, 305 insertions(+), 49 deletions(-) diff --git a/include/clang/Basic/DiagnosticASTKinds.td b/include/clang/Basic/DiagnosticASTKinds.td index aea2980e6f..c69f85f18d 100644 --- a/include/clang/Basic/DiagnosticASTKinds.td +++ b/include/clang/Basic/DiagnosticASTKinds.td @@ -84,19 +84,19 @@ def note_constexpr_depth_limit_exceeded : Note< def note_constexpr_call_limit_exceeded : Note< "constexpr evaluation hit maximum call limit">; def note_constexpr_lifetime_ended : Note< - "%select{read of|assignment to}0 %select{temporary|variable}1 " - "whose lifetime has ended">; + "%select{read of|assignment to|increment of|decrement of}0 " + "%select{temporary|variable}1 whose lifetime has ended">; def note_constexpr_access_uninit : Note< - "%select{read of|assignment to}0 object outside its lifetime " - "is not allowed in a constant expression">; + "%select{read of|assignment to|increment of|decrement of}0 " + "object outside its lifetime is not allowed in a constant expression">; def note_constexpr_modify_const_type : Note< "modification of object of const-qualified type %0 is not allowed " "in a constant expression">; def note_constexpr_access_volatile_type : Note< - "%select{read of|assignment to}0 volatile-qualified type %1 " - "is not allowed in a constant expression">; + "%select{read of|assignment to|increment of|decrement of}0 " + "volatile-qualified type %1 is not allowed in a constant expression">; def note_constexpr_access_volatile_obj : Note< - "%select{read of|assignment to}0 volatile " + "%select{read of|assignment to|increment of|decrement of}0 volatile " "%select{temporary|object %2|member %2}1 is not allowed in " "a constant expression">; def note_constexpr_ltor_mutable : Note< @@ -106,14 +106,14 @@ def note_constexpr_ltor_non_const_int : Note< def note_constexpr_ltor_non_constexpr : Note< "read of non-constexpr variable %0 is not allowed in a constant expression">; def note_constexpr_access_null : Note< - "%select{read of|assignment to}0 dereferenced null pointer " - "is not allowed in a constant expression">; + "%select{read of|assignment to|increment of|decrement of}0 " + "dereferenced null pointer is not allowed in a constant expression">; def note_constexpr_access_past_end : Note< - "%select{read of|assignment to}0 dereferenced one-past-the-end pointer " - "is not allowed in a constant expression">; + "%select{read of|assignment to|increment of|decrement of}0 " + "dereferenced one-past-the-end pointer is not allowed in a constant expression">; def note_constexpr_access_inactive_union_member : Note< - "%select{read of|assignment to}0 member %1 of union with " - "%select{active member %3|no active member}2 " + "%select{read of|assignment to|increment of|decrement of}0 " + "member %1 of union with %select{active member %3|no active member}2 " "is not allowed in a constant expression">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index 13fd9df266..5d153c422f 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -1614,7 +1614,9 @@ static void expandArray(APValue &Array, unsigned Index) { /// Kinds of access we can perform on an object. enum AccessKinds { AK_Read, - AK_Assign + AK_Assign, + AK_Increment, + AK_Decrement }; /// A handle to a complete object (an object that is not a subobject of @@ -2083,9 +2085,9 @@ CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, AccessKinds AK, return CompleteObject(BaseVal, BaseType); } -/// HandleLValueToRValueConversion - Perform an lvalue-to-rvalue conversion on -/// the given glvalue. This can also be used for 'lvalue-to-lvalue' conversions -/// for looking up the glvalue referred to by an entity of reference type. +/// \brief Perform an lvalue-to-rvalue conversion on the given glvalue. This +/// can also be used for 'lvalue-to-lvalue' conversions for looking up the +/// glvalue referred to by an entity of reference type. /// /// \param Info - Information about the ongoing evaluation. /// \param Conv - The expression for which we are performing the conversion. @@ -2094,7 +2096,7 @@ CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, AccessKinds AK, /// case of a non-class type). /// \param LVal - The glvalue on which we are attempting to perform this action. /// \param RVal - The produced value will be placed here. -static bool HandleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, +static bool handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, QualType Type, const LValue &LVal, APValue &RVal) { if (LVal.Designator.Invalid) @@ -2133,7 +2135,7 @@ static bool HandleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, } /// Perform an assignment of Val to LVal. Takes ownership of Val. -static bool HandleAssignment(EvalInfo &Info, const Expr *E, const LValue &LVal, +static bool handleAssignment(EvalInfo &Info, const Expr *E, const LValue &LVal, QualType LValType, APValue &Val) { if (LVal.Designator.Invalid) return false; @@ -2147,6 +2149,160 @@ static bool HandleAssignment(EvalInfo &Info, const Expr *E, const LValue &LVal, return Obj && modifySubobject(Info, E, Obj, LVal.Designator, Val); } +static bool isOverflowingIntegerType(ASTContext &Ctx, QualType T) { + return T->isSignedIntegerType() && + Ctx.getIntWidth(T) >= Ctx.getIntWidth(Ctx.IntTy); +} + +namespace { +struct IncDecSubobjectHandler { + EvalInfo &Info; + const Expr *E; + AccessKinds AccessKind; + APValue *Old; + + typedef bool result_type; + + bool checkConst(QualType QT) { + // Assigning to a const object has undefined behavior. + if (QT.isConstQualified()) { + Info.Diag(E, diag::note_constexpr_modify_const_type) << QT; + return false; + } + return true; + } + + bool failed() { return false; } + bool found(APValue &Subobj, QualType SubobjType) { + // Stash the old value. Also clear Old, so we don't clobber it later + // if we're post-incrementing a complex. + if (Old) { + *Old = Subobj; + Old = 0; + } + + switch (Subobj.getKind()) { + case APValue::Int: + return found(Subobj.getInt(), SubobjType); + case APValue::Float: + return found(Subobj.getFloat(), SubobjType); + case APValue::ComplexInt: + return found(Subobj.getComplexIntReal(), + SubobjType->castAs()->getElementType() + .withCVRQualifiers(SubobjType.getCVRQualifiers())); + case APValue::ComplexFloat: + return found(Subobj.getComplexFloatReal(), + SubobjType->castAs()->getElementType() + .withCVRQualifiers(SubobjType.getCVRQualifiers())); + case APValue::LValue: + return foundPointer(Subobj, SubobjType); + default: + // FIXME: can this happen? + Info.Diag(E); + return false; + } + } + bool found(APSInt &Value, QualType SubobjType) { + if (!checkConst(SubobjType)) + return false; + + if (!SubobjType->isIntegerType()) { + // We don't support increment / decrement on integer-cast-to-pointer + // values. + Info.Diag(E); + return false; + } + + if (Old) *Old = APValue(Value); + + // bool arithmetic promotes to int, and the conversion back to bool + // doesn't reduce mod 2^n, so special-case it. + if (SubobjType->isBooleanType()) { + if (AccessKind == AK_Increment) + Value = 1; + else + Value = !Value; + return true; + } + + bool WasNegative = Value.isNegative(); + if (AccessKind == AK_Increment) { + ++Value; + + if (!WasNegative && Value.isNegative() && + isOverflowingIntegerType(Info.Ctx, SubobjType)) { + APSInt ActualValue(Value, /*IsUnsigned*/true); + HandleOverflow(Info, E, ActualValue, SubobjType); + } + } else { + --Value; + + if (WasNegative && !Value.isNegative() && + isOverflowingIntegerType(Info.Ctx, SubobjType)) { + unsigned BitWidth = Value.getBitWidth(); + APSInt ActualValue(Value.sext(BitWidth + 1), /*IsUnsigned*/false); + ActualValue.setBit(BitWidth); + HandleOverflow(Info, E, ActualValue, SubobjType); + } + } + return true; + } + bool found(APFloat &Value, QualType SubobjType) { + if (!checkConst(SubobjType)) + return false; + + if (Old) *Old = APValue(Value); + + APFloat One(Value.getSemantics(), 1); + if (AccessKind == AK_Increment) + Value.add(One, APFloat::rmNearestTiesToEven); + else + Value.subtract(One, APFloat::rmNearestTiesToEven); + return true; + } + bool foundPointer(APValue &Subobj, QualType SubobjType) { + if (!checkConst(SubobjType)) + return false; + + QualType PointeeType; + if (const PointerType *PT = SubobjType->getAs()) + PointeeType = PT->getPointeeType(); + else { + Info.Diag(E); + return false; + } + + LValue LVal; + LVal.setFrom(Info.Ctx, Subobj); + if (!HandleLValueArrayAdjustment(Info, E, LVal, PointeeType, + AccessKind == AK_Increment ? 1 : -1)) + return false; + LVal.moveInto(Subobj); + return true; + } + bool foundString(APValue &Subobj, QualType SubobjType, uint64_t Character) { + llvm_unreachable("shouldn't encounter string elements here"); + } +}; +} // end anonymous namespace + +/// Perform an increment or decrement on LVal. +static bool handleIncDec(EvalInfo &Info, const Expr *E, const LValue &LVal, + QualType LValType, bool IsIncrement, APValue *Old) { + if (LVal.Designator.Invalid) + return false; + + if (!Info.getLangOpts().CPlusPlus1y) { + Info.Diag(E); + return false; + } + + AccessKinds AK = IsIncrement ? AK_Increment : AK_Decrement; + CompleteObject Obj = findCompleteObject(Info, E, AK, LVal, LValType); + IncDecSubobjectHandler Handler = { Info, E, AK, Old }; + return Obj && findSubobject(Info, E, Obj, LVal.Designator, Handler); +} + /// Build an lvalue for the object argument of a member function call. static bool EvaluateObjectArgument(EvalInfo &Info, const Expr *Object, LValue &This) { @@ -2534,7 +2690,7 @@ static bool HandleConstructorCall(SourceLocation CallLoc, const LValue &This, (Definition->isMoveConstructor() && Definition->isTrivial()))) { LValue RHS; RHS.setFrom(Info.Ctx, ArgValues[0]); - return HandleLValueToRValueConversion(Info, Args[0], Args[0]->getType(), + return handleLValueToRValueConversion(Info, Args[0], Args[0]->getType(), RHS, Result); } @@ -2761,7 +2917,7 @@ public: if (!HandleMemberPointerAccess(Info, E, Obj)) return false; APValue Result; - if (!HandleLValueToRValueConversion(Info, E, E->getType(), Obj, Result)) + if (!handleLValueToRValueConversion(Info, E, E->getType(), Obj, Result)) return false; return DerivedSuccess(Result, E); } @@ -2967,7 +3123,7 @@ public: return false; APValue RVal; // Note, we use the subexpression's type in order to retain cv-qualifiers. - if (!HandleLValueToRValueConversion(Info, E, E->getSubExpr()->getType(), + if (!handleLValueToRValueConversion(Info, E, E->getSubExpr()->getType(), LVal, RVal)) return false; return DerivedSuccess(RVal, E); @@ -2977,6 +3133,26 @@ public: return Error(E); } + RetTy VisitUnaryPostInc(const UnaryOperator *UO) { + return VisitUnaryPostIncDec(UO); + } + RetTy VisitUnaryPostDec(const UnaryOperator *UO) { + return VisitUnaryPostIncDec(UO); + } + RetTy VisitUnaryPostIncDec(const UnaryOperator *UO) { + if (!Info.getLangOpts().CPlusPlus1y && !Info.keepEvaluatingAfterFailure()) + return Error(UO); + + LValue LVal; + if (!EvaluateLValue(UO->getSubExpr(), LVal, Info)) + return false; + APValue RVal; + if (!handleIncDec(this->Info, UO, LVal, UO->getSubExpr()->getType(), + UO->isIncrementOp(), &RVal)) + return false; + return DerivedSuccess(RVal, UO); + } + /// Visit a value which is evaluated, but whose value is ignored. void VisitIgnoredValue(const Expr *E) { EvaluateIgnoredValue(Info, E); @@ -3044,7 +3220,7 @@ public: if (MD->getType()->isReferenceType()) { APValue RefValue; - if (!HandleLValueToRValueConversion(this->Info, E, MD->getType(), Result, + if (!handleLValueToRValueConversion(this->Info, E, MD->getType(), Result, RefValue)) return false; return Success(RefValue, E); @@ -3127,7 +3303,7 @@ public: LValueExprEvaluatorBaseTy(Info, Result) {} bool VisitVarDecl(const Expr *E, const VarDecl *VD); - bool VisitIncDec(const UnaryOperator *UO); + bool VisitUnaryPreIncDec(const UnaryOperator *UO); bool VisitDeclRefExpr(const DeclRefExpr *E); bool VisitPredefinedExpr(const PredefinedExpr *E) { return Success(E); } @@ -3142,8 +3318,12 @@ public: bool VisitUnaryDeref(const UnaryOperator *E); bool VisitUnaryReal(const UnaryOperator *E); bool VisitUnaryImag(const UnaryOperator *E); - bool VisitUnaryPreInc(const UnaryOperator *UO) { return VisitIncDec(UO); } - bool VisitUnaryPreDec(const UnaryOperator *UO) { return VisitIncDec(UO); } + bool VisitUnaryPreInc(const UnaryOperator *UO) { + return VisitUnaryPreIncDec(UO); + } + bool VisitUnaryPreDec(const UnaryOperator *UO) { + return VisitUnaryPreIncDec(UO); + } bool VisitBinAssign(const BinaryOperator *BO); bool VisitCompoundAssignOperator(const CompoundAssignOperator *CAO); @@ -3296,31 +3476,32 @@ bool LValueExprEvaluator::VisitUnaryImag(const UnaryOperator *E) { return true; } -bool LValueExprEvaluator::VisitIncDec(const UnaryOperator *UO) { - if (!Info.getLangOpts().CPlusPlus1y) +bool LValueExprEvaluator::VisitUnaryPreIncDec(const UnaryOperator *UO) { + if (!Info.getLangOpts().CPlusPlus1y && !Info.keepEvaluatingAfterFailure()) return Error(UO); if (!this->Visit(UO->getSubExpr())) return false; - // FIXME: - //return handleIncDec( - // this->Info, CAO, Result, UO->getSubExpr()->getType(), - // UO->isIncrementOp()); - // (Watch out for promotions: ++short can't overflow, ++bool is always true). - return Error(UO); + return handleIncDec( + this->Info, UO, Result, UO->getSubExpr()->getType(), + UO->isIncrementOp(), 0); } bool LValueExprEvaluator::VisitCompoundAssignOperator( const CompoundAssignOperator *CAO) { - if (!Info.getLangOpts().CPlusPlus1y) + if (!Info.getLangOpts().CPlusPlus1y && !Info.keepEvaluatingAfterFailure()) return Error(CAO); + APValue RHS; + // The overall lvalue result is the result of evaluating the LHS. - if (!this->Visit(CAO->getLHS())) + if (!this->Visit(CAO->getLHS())) { + if (Info.keepEvaluatingAfterFailure()) + Evaluate(RHS, this->Info, CAO->getRHS()); return false; + } - APValue RHS; if (!Evaluate(RHS, this->Info, CAO->getRHS())) return false; @@ -3335,12 +3516,21 @@ bool LValueExprEvaluator::VisitCompoundAssignOperator( } bool LValueExprEvaluator::VisitBinAssign(const BinaryOperator *E) { - if (!this->Visit(E->getLHS())) - return false; + if (!Info.getLangOpts().CPlusPlus1y && !Info.keepEvaluatingAfterFailure()) + return Error(E); + APValue NewVal; + + if (!this->Visit(E->getLHS())) { + if (Info.keepEvaluatingAfterFailure()) + Evaluate(NewVal, this->Info, E->getRHS()); + return false; + } + if (!Evaluate(NewVal, this->Info, E->getRHS())) return false; - return HandleAssignment(this->Info, E, Result, E->getLHS()->getType(), + + return handleAssignment(this->Info, E, Result, E->getLHS()->getType(), NewVal); } @@ -6682,7 +6872,7 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) { if (E->isGLValue()) { LValue LV; LV.setFrom(Info.Ctx, Result); - if (!HandleLValueToRValueConversion(Info, E, E->getType(), LV, Result)) + if (!handleLValueToRValueConversion(Info, E, E->getType(), LV, Result)) return false; } diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp index e12011d19c..3bc639dd57 100644 --- a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp @@ -277,7 +277,7 @@ namespace std_example { constexpr int prev(int x) { return --x; } -#if 1 // FIXME: !defined CXX1Y +#ifndef CXX1Y // expected-error@-4 {{never produces a constant expression}} // expected-note@-4 {{subexpression}} #endif diff --git a/test/SemaCXX/constant-expression-cxx1y.cpp b/test/SemaCXX/constant-expression-cxx1y.cpp index 62739ee8c1..2dfded4cc3 100644 --- a/test/SemaCXX/constant-expression-cxx1y.cpp +++ b/test/SemaCXX/constant-expression-cxx1y.cpp @@ -161,11 +161,9 @@ namespace string_assign { swap(*begin++, *end); #else if (begin != end) { - end = end - 1; - if (begin == end) + if (begin == --end) return; - swap(*begin, *end); - begin = begin + 1; + swap(*begin++, *end); reverse(begin, end); } #endif @@ -180,10 +178,8 @@ namespace string_assign { } #else if (a != ae && b != be) { - if (*a != *b) + if (*a++ != *b++) return false; - a = a + 1; - b = b + 1; return equal(a, ae, b, be); } #endif @@ -286,3 +282,73 @@ namespace null { } static_assert(test(0), ""); // expected-error {{constant expression}} expected-note {{in call}} } + +namespace incdec { + template constexpr T &ref(T &&r) { return r; } + template constexpr T postinc(T &&r) { return (r++, r); } + template constexpr T postdec(T &&r) { return (r--, r); } + + static_assert(++ref(0) == 1, ""); + static_assert(ref(0)++ == 0, ""); + static_assert(postinc(0) == 1, ""); + static_assert(--ref(0) == -1, ""); + static_assert(ref(0)-- == 0, ""); + static_assert(postdec(0) == -1, ""); + + constexpr int overflow_int_inc_1 = ref(0x7fffffff)++; // expected-error {{constant}} expected-note {{2147483648}} + constexpr int overflow_int_inc_1_ok = ref(0x7ffffffe)++; + constexpr int overflow_int_inc_2 = ++ref(0x7fffffff); // expected-error {{constant}} expected-note {{2147483648}} + constexpr int overflow_int_inc_2_ok = ++ref(0x7ffffffe); + + // inc/dec on short can't overflow because we promote to int first + static_assert(++ref(0x7fff) == (int)0xffff8000u, ""); + static_assert(--ref(0x8000) == 0x7fff, ""); + + // inc on bool sets to true + static_assert(++ref(false), ""); // expected-warning {{deprecated}} + static_assert(++ref(true), ""); // expected-warning {{deprecated}} + + int arr[10]; + static_assert(++ref(&arr[0]) == &arr[1], ""); + static_assert(++ref(&arr[9]) == &arr[10], ""); + static_assert(++ref(&arr[10]) == &arr[11], ""); // expected-error {{constant}} expected-note {{cannot refer to element 11}} + static_assert(ref(&arr[0])++ == &arr[0], ""); + static_assert(ref(&arr[10])++ == &arr[10], ""); // expected-error {{constant}} expected-note {{cannot refer to element 11}} + static_assert(postinc(&arr[0]) == &arr[1], ""); + static_assert(--ref(&arr[10]) == &arr[9], ""); + static_assert(--ref(&arr[1]) == &arr[0], ""); + static_assert(--ref(&arr[0]) != &arr[0], ""); // expected-error {{constant}} expected-note {{cannot refer to element -1}} + static_assert(ref(&arr[1])-- == &arr[1], ""); + static_assert(ref(&arr[0])-- == &arr[0], ""); // expected-error {{constant}} expected-note {{cannot refer to element -1}} + static_assert(postdec(&arr[1]) == &arr[0], ""); + + int x; + static_assert(++ref(&x) == &x + 1, ""); + + static_assert(++ref(0.0) == 1.0, ""); + static_assert(ref(0.0)++ == 0.0, ""); + static_assert(postinc(0.0) == 1.0, ""); + static_assert(--ref(0.0) == -1.0, ""); + static_assert(ref(0.0)-- == 0.0, ""); + static_assert(postdec(0.0) == -1.0, ""); + + static_assert(++ref(1e100) == 1e100, ""); + static_assert(--ref(1e100) == 1e100, ""); + + union U { + int a, b; + }; + constexpr int f(U u) { + return ++u.b; // expected-note {{increment of member 'b' of union with active member 'a'}} + } + constexpr int wrong_member = f({0}); // expected-error {{constant}} expected-note {{in call to 'f({.a = 0})'}} + constexpr int vol = --ref(0); // expected-error {{constant}} expected-note {{decrement of volatile-qualified}} + + constexpr int incr(int k) { + int x = k; + if (x++ == 100) + return x; + return incr(x); + } + static_assert(incr(0) == 101, ""); +} -- 2.40.0