diff options
Diffstat (limited to 'lib/AST/ExprConstant.cpp')
-rw-r--r-- | lib/AST/ExprConstant.cpp | 582 |
1 files changed, 401 insertions, 181 deletions
diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index 6023a678e2..331c757c12 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -2427,6 +2427,13 @@ protected: return Info.CCEDiag(E, D); } + RetTy ZeroInitialization(const Expr *E) { return Error(E); } + +public: + ExprEvaluatorBase(EvalInfo &Info) : Info(Info) {} + + EvalInfo &getEvalInfo() { return Info; } + /// Report an evaluation error. This should only be called when an error is /// first discovered. When propagating an error, just return false. bool Error(const Expr *E, diag::kind D) { @@ -2437,11 +2444,6 @@ protected: return Error(E, diag::note_invalid_subexpr_in_const_expr); } - RetTy ZeroInitialization(const Expr *E) { return Error(E); } - -public: - ExprEvaluatorBase(EvalInfo &Info) : Info(Info) {} - RetTy VisitStmt(const Stmt *) { llvm_unreachable("Expression evaluator should not be called on stmts"); } @@ -3958,7 +3960,7 @@ public: IntExprEvaluator(EvalInfo &info, APValue &result) : ExprEvaluatorBaseTy(info), Result(result) {} - bool Success(const llvm::APSInt &SI, const Expr *E) { + bool Success(const llvm::APSInt &SI, const Expr *E, APValue &Result) { assert(E->getType()->isIntegralOrEnumerationType() && "Invalid evaluation result."); assert(SI.isSigned() == E->getType()->isSignedIntegerOrEnumerationType() && @@ -3968,8 +3970,11 @@ public: Result = APValue(SI); return true; } + bool Success(const llvm::APSInt &SI, const Expr *E) { + return Success(SI, E, Result); + } - bool Success(const llvm::APInt &I, const Expr *E) { + bool Success(const llvm::APInt &I, const Expr *E, APValue &Result) { assert(E->getType()->isIntegralOrEnumerationType() && "Invalid evaluation result."); assert(I.getBitWidth() == Info.Ctx.getIntWidth(E->getType()) && @@ -3979,13 +3984,19 @@ public: E->getType()->isUnsignedIntegerOrEnumerationType()); return true; } + bool Success(const llvm::APInt &I, const Expr *E) { + return Success(I, E, Result); + } - bool Success(uint64_t Value, const Expr *E) { + bool Success(uint64_t Value, const Expr *E, APValue &Result) { assert(E->getType()->isIntegralOrEnumerationType() && "Invalid evaluation result."); Result = APValue(Info.Ctx.MakeIntValue(Value, E->getType())); return true; } + bool Success(uint64_t Value, const Expr *E) { + return Success(Value, E, Result); + } bool Success(CharUnits Size, const Expr *E) { return Success(Size.getQuantity(), E); @@ -4433,49 +4444,402 @@ static APSInt CheckedIntArithmetic(EvalInfo &Info, const Expr *E, return Result; } -bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { - if (E->isAssignmentOp()) - return Error(E); +namespace { - if (E->getOpcode() == BO_Comma) { - VisitIgnoredValue(E->getLHS()); - return Visit(E->getRHS()); +/// \brief Data recursive integer evaluator of certain binary operators. +/// +/// We use a data recursive algorithm for binary operators so that we are able +/// to handle extreme cases of chained binary operators without causing stack +/// overflow. +class DataRecursiveIntBinOpEvaluator { + struct EvalResult { + APValue Val; + bool Failed; + + EvalResult() : Failed(false) { } + + void swap(EvalResult &RHS) { + Val.swap(RHS.Val); + Failed = RHS.Failed; + RHS.Failed = false; + } + }; + + struct Job { + const Expr *E; + EvalResult LHSResult; // meaningful only for binary operator expression. + enum { AnyExprKind, BinOpKind, BinOpVisitedLHSKind } Kind; + + Job() : StoredInfo(0) { } + void startSpeculativeEval(EvalInfo &Info) { + OldEvalStatus = Info.EvalStatus; + Info.EvalStatus.Diag = 0; + StoredInfo = &Info; + } + ~Job() { + if (StoredInfo) { + StoredInfo->EvalStatus = OldEvalStatus; + } + } + private: + EvalInfo *StoredInfo; // non-null if status changed. + Expr::EvalStatus OldEvalStatus; + }; + + SmallVector<Job, 16> Queue; + + IntExprEvaluator &IntEval; + EvalInfo &Info; + APValue &FinalResult; + +public: + DataRecursiveIntBinOpEvaluator(IntExprEvaluator &IntEval, APValue &Result) + : IntEval(IntEval), Info(IntEval.getEvalInfo()), FinalResult(Result) { } + + /// \brief True if \param E is a binary operator that we are going to handle + /// data recursively. + /// We handle binary operators that are comma, logical, or that have operands + /// with integral or enumeration type. + static bool shouldEnqueue(const BinaryOperator *E) { + return E->getOpcode() == BO_Comma || + E->isLogicalOp() || + (E->getLHS()->getType()->isIntegralOrEnumerationType() && + E->getRHS()->getType()->isIntegralOrEnumerationType()); + } + + bool Traverse(const BinaryOperator *E) { + enqueue(E); + EvalResult PrevResult; + while (!Queue.empty()) { + if (!process(PrevResult)) { + Queue.clear(); + return false; + } + } + + FinalResult.swap(PrevResult.Val); + return true; } - if (E->isLogicalOp()) { - // These need to be handled specially because the operands aren't - // necessarily integral nor evaluated. - bool lhsResult, rhsResult; +private: + bool Success(uint64_t Value, const Expr *E, APValue &Result) { + return IntEval.Success(Value, E, Result); + } + bool Success(const APSInt &Value, const Expr *E, APValue &Result) { + return IntEval.Success(Value, E, Result); + } + bool Error(const Expr *E) { + return IntEval.Error(E); + } + bool Error(const Expr *E, diag::kind D) { + return IntEval.Error(E, D); + } + + OptionalDiagnostic CCEDiag(const Expr *E, diag::kind D) { + return Info.CCEDiag(E, D); + } + + bool VisitBinOpLHSOnly(const EvalResult &LHSResult, const BinaryOperator *E, + bool &IgnoreRHS, APValue &Result, + bool &SuppressRHSDiags); + + bool VisitBinOp(const EvalResult &LHSResult, const EvalResult &RHSResult, + const BinaryOperator *E, APValue &Result); + + void EvaluateExpr(const Expr *E, EvalResult &Result) { + Result.Failed = !Evaluate(Result.Val, Info, E); + if (Result.Failed) + Result.Val = APValue(); + } - if (EvaluateAsBooleanCondition(E->getLHS(), lhsResult, Info)) { + bool process(EvalResult &Result); + + void enqueue(const Expr *E) { + E = E->IgnoreParens(); + Queue.resize(Queue.size()+1); + Queue.back().E = E; + Queue.back().Kind = Job::AnyExprKind; + } +}; + +} + +bool DataRecursiveIntBinOpEvaluator:: + VisitBinOpLHSOnly(const EvalResult &LHSResult, const BinaryOperator *E, + bool &IgnoreRHS, APValue &Result, + bool &SuppressRHSDiags) { + if (E->getOpcode() == BO_Comma) { + // Ignore LHS but note if we could not evaluate it. + if (LHSResult.Failed) + Info.EvalStatus.HasSideEffects = true; + return true; + } + + if (E->isLogicalOp()) { + bool lhsResult; + if (HandleConversionToBool(LHSResult.Val, lhsResult)) { // We were able to evaluate the LHS, see if we can get away with not // evaluating the RHS: 0 && X -> 0, 1 || X -> 1 - if (lhsResult == (E->getOpcode() == BO_LOr)) - return Success(lhsResult, E); - - if (EvaluateAsBooleanCondition(E->getRHS(), rhsResult, Info)) { - if (E->getOpcode() == BO_LOr) - return Success(lhsResult || rhsResult, E); - else - return Success(lhsResult && rhsResult, E); + if (lhsResult == (E->getOpcode() == BO_LOr)) { + IgnoreRHS = true; + return Success(lhsResult, E, Result); } } else { // Since we weren't able to evaluate the left hand side, it // must have had side effects. Info.EvalStatus.HasSideEffects = true; + + // We can't evaluate the LHS; however, sometimes the result + // is determined by the RHS: X && 0 -> 0, X || 1 -> 1. + // Don't ignore RHS and suppress diagnostics from this arm. + SuppressRHSDiags = true; + } + + return true; + } + + assert(E->getLHS()->getType()->isIntegralOrEnumerationType() && + E->getRHS()->getType()->isIntegralOrEnumerationType()); + + if (LHSResult.Failed && !Info.keepEvaluatingAfterFailure()) + return false; + + return true; +} - // Suppress diagnostics from this arm. - SpeculativeEvaluationRAII Speculative(Info); - if (EvaluateAsBooleanCondition(E->getRHS(), rhsResult, Info)) { +bool DataRecursiveIntBinOpEvaluator:: + VisitBinOp(const EvalResult &LHSResult, const EvalResult &RHSResult, + const BinaryOperator *E, APValue &Result) { + if (E->getOpcode() == BO_Comma) { + if (RHSResult.Failed) + return false; + Result = RHSResult.Val; + return true; + } + + if (E->isLogicalOp()) { + bool lhsResult, rhsResult; + bool LHSIsOK = HandleConversionToBool(LHSResult.Val, lhsResult); + bool RHSIsOK = HandleConversionToBool(RHSResult.Val, rhsResult); + + if (LHSIsOK) { + if (RHSIsOK) { + if (E->getOpcode() == BO_LOr) + return Success(lhsResult || rhsResult, E, Result); + else + return Success(lhsResult && rhsResult, E, Result); + } + } else { + if (RHSIsOK) { // We can't evaluate the LHS; however, sometimes the result // is determined by the RHS: X && 0 -> 0, X || 1 -> 1. if (rhsResult == (E->getOpcode() == BO_LOr)) - return Success(rhsResult, E); + return Success(rhsResult, E, Result); } } - + return false; } + + assert(E->getLHS()->getType()->isIntegralOrEnumerationType() && + E->getRHS()->getType()->isIntegralOrEnumerationType()); + + if (LHSResult.Failed || RHSResult.Failed) + return false; + + const APValue &LHSVal = LHSResult.Val; + const APValue &RHSVal = RHSResult.Val; + + // Handle cases like (unsigned long)&a + 4. + if (E->isAdditiveOp() && LHSVal.isLValue() && RHSVal.isInt()) { + Result = LHSVal; + CharUnits AdditionalOffset = CharUnits::fromQuantity( + RHSVal.getInt().getZExtValue()); + if (E->getOpcode() == BO_Add) + Result.getLValueOffset() += AdditionalOffset; + else + Result.getLValueOffset() -= AdditionalOffset; + return true; + } + + // Handle cases like 4 + (unsigned long)&a + if (E->getOpcode() == BO_Add && + RHSVal.isLValue() && LHSVal.isInt()) { + Result = RHSVal; + Result.getLValueOffset() += CharUnits::fromQuantity( + LHSVal.getInt().getZExtValue()); + return true; + } + + if (E->getOpcode() == BO_Sub && LHSVal.isLValue() && RHSVal.isLValue()) { + // Handle (intptr_t)&&A - (intptr_t)&&B. + if (!LHSVal.getLValueOffset().isZero() || + !RHSVal.getLValueOffset().isZero()) + return false; + const Expr *LHSExpr = LHSVal.getLValueBase().dyn_cast<const Expr*>(); + const Expr *RHSExpr = RHSVal.getLValueBase().dyn_cast<const Expr*>(); + if (!LHSExpr || !RHSExpr) + return false; + const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr); + const AddrLabelExpr *RHSAddrExpr = dyn_cast<AddrLabelExpr>(RHSExpr); + if (!LHSAddrExpr || !RHSAddrExpr) + return false; + // Make sure both labels come from the same function. + if (LHSAddrExpr->getLabel()->getDeclContext() != + RHSAddrExpr->getLabel()->getDeclContext()) + return false; + Result = APValue(LHSAddrExpr, RHSAddrExpr); + return true; + } + + // All the following cases expect both operands to be an integer + if (!LHSVal.isInt() || !RHSVal.isInt()) + return Error(E); + + const APSInt &LHS = LHSVal.getInt(); + APSInt RHS = RHSVal.getInt(); + + switch (E->getOpcode()) { + default: + return Error(E); + case BO_Mul: + return Success(CheckedIntArithmetic(Info, E, LHS, RHS, + LHS.getBitWidth() * 2, + std::multiplies<APSInt>()), E, + Result); + case BO_Add: + return Success(CheckedIntArithmetic(Info, E, LHS, RHS, + LHS.getBitWidth() + 1, + std::plus<APSInt>()), E, Result); + case BO_Sub: + return Success(CheckedIntArithmetic(Info, E, LHS, RHS, + LHS.getBitWidth() + 1, + std::minus<APSInt>()), E, Result); + case BO_And: return Success(LHS & RHS, E, Result); + case BO_Xor: return Success(LHS ^ RHS, E, Result); + case BO_Or: return Success(LHS | RHS, E, Result); + case BO_Div: + case BO_Rem: + if (RHS == 0) + return Error(E, diag::note_expr_divide_by_zero); + // Check for overflow case: INT_MIN / -1 or INT_MIN % -1. The latter is + // not actually undefined behavior in C++11 due to a language defect. + if (RHS.isNegative() && RHS.isAllOnesValue() && + LHS.isSigned() && LHS.isMinSignedValue()) + HandleOverflow(Info, E, -LHS.extend(LHS.getBitWidth() + 1), E->getType()); + return Success(E->getOpcode() == BO_Rem ? LHS % RHS : LHS / RHS, E, + Result); + case BO_Shl: { + // During constant-folding, a negative shift is an opposite shift. Such + // a shift is not a constant expression. + if (RHS.isSigned() && RHS.isNegative()) { + CCEDiag(E, diag::note_constexpr_negative_shift) << RHS; + RHS = -RHS; + goto shift_right; + } + + shift_left: + // C++11 [expr.shift]p1: Shift width must be less than the bit width of + // the shifted type. + unsigned SA = (unsigned) RHS.getLimitedValue(LHS.getBitWidth()-1); + if (SA != RHS) { + CCEDiag(E, diag::note_constexpr_large_shift) + << RHS << E->getType() << LHS.getBitWidth(); + } else if (LHS.isSigned()) { + // C++11 [expr.shift]p2: A signed left shift must have a non-negative + // operand, and must not overflow the corresponding unsigned type. + if (LHS.isNegative()) + CCEDiag(E, diag::note_constexpr_lshift_of_negative) << LHS; + else if (LHS.countLeadingZeros() < SA) + CCEDiag(E, diag::note_constexpr_lshift_discards); + } + + return Success(LHS << SA, E, Result); + } + case BO_Shr: { + // During constant-folding, a negative shift is an opposite shift. Such a + // shift is not a constant expression. + if (RHS.isSigned() && RHS.isNegative()) { + CCEDiag(E, diag::note_constexpr_negative_shift) << RHS; + RHS = -RHS; + goto shift_left; + } + + shift_right: + // C++11 [expr.shift]p1: Shift width must be less than the bit width of the + // shifted type. + unsigned SA = (unsigned) RHS.getLimitedValue(LHS.getBitWidth()-1); + if (SA != RHS) + CCEDiag(E, diag::note_constexpr_large_shift) + << RHS << E->getType() << LHS.getBitWidth(); + + return Success(LHS >> SA, E, Result); + } + + case BO_LT: return Success(LHS < RHS, E, Result); + case BO_GT: return Success(LHS > RHS, E, Result); + case BO_LE: return Success(LHS <= RHS, E, Result); + case BO_GE: return Success(LHS >= RHS, E, Result); + case BO_EQ: return Success(LHS == RHS, E, Result); + case BO_NE: return Success(LHS != RHS, E, Result); + } +} + +bool DataRecursiveIntBinOpEvaluator::process(EvalResult &Result) { + Job &job = Queue.back(); + + switch (job.Kind) { + case Job::AnyExprKind: { + if (const BinaryOperator *Bop = dyn_cast<BinaryOperator>(job.E)) { + if (shouldEnqueue(Bop)) { + job.Kind = Job::BinOpKind; + enqueue(Bop->getLHS()); + return true; + } + } + + EvaluateExpr(job.E, Result); + Queue.pop_back(); + return true; + } + + case Job::BinOpKind: { + const BinaryOperator *Bop = cast<BinaryOperator>(job.E); + job.LHSResult.swap(Result); + bool IgnoreRHS = false; + bool SuppressRHSDiags = false; + bool ret = VisitBinOpLHSOnly(job.LHSResult, Bop, IgnoreRHS, Result.Val, + SuppressRHSDiags); + if (IgnoreRHS) { + Queue.pop_back(); + return ret; + } + if (SuppressRHSDiags) + job.startSpeculativeEval(Info); + job.Kind = Job::BinOpVisitedLHSKind; + enqueue(Bop->getRHS()); + return ret; + } + + case Job::BinOpVisitedLHSKind: { + const BinaryOperator *Bop = cast<BinaryOperator>(job.E); + EvalResult RHS; + RHS.swap(Result); + bool ret = VisitBinOp(job.LHSResult, RHS, Bop, Result.Val); + Queue.pop_back(); + return ret; + } + } + + llvm_unreachable("Invalid Job::Kind!"); +} + +bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { + if (E->isAssignmentOp()) + return Error(E); + + if (DataRecursiveIntBinOpEvaluator::shouldEnqueue(E)) + return DataRecursiveIntBinOpEvaluator(*this, Result).Traverse(E); QualType LHSTy = E->getLHS()->getType(); QualType RHSTy = E->getRHS()->getType(); @@ -4776,155 +5140,11 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { return Success(Opcode == BO_EQ || Opcode == BO_LE || Opcode == BO_GE, E); } - if (!LHSTy->isIntegralOrEnumerationType() || - !RHSTy->isIntegralOrEnumerationType()) { - // We can't continue from here for non-integral types. - return ExprEvaluatorBaseTy::VisitBinaryOperator(E); - } - - // The LHS of a constant expr is always evaluated and needed. - APValue LHSVal; - - bool LHSOK = EvaluateIntegerOrLValue(E->getLHS(), LHSVal, Info); - if (!LHSOK && !Info.keepEvaluatingAfterFailure()) - return false; - - if (!Visit(E->getRHS()) || !LHSOK) - return false; - - APValue &RHSVal = Result; - - // Handle cases like (unsigned long)&a + 4. - if (E->isAdditiveOp() && LHSVal.isLValue() && RHSVal.isInt()) { - CharUnits AdditionalOffset = CharUnits::fromQuantity( - RHSVal.getInt().getZExtValue()); - if (E->getOpcode() == BO_Add) - LHSVal.getLValueOffset() += AdditionalOffset; - else - LHSVal.getLValueOffset() -= AdditionalOffset; - Result = LHSVal; - return true; - } - - // Handle cases like 4 + (unsigned long)&a - if (E->getOpcode() == BO_Add && - RHSVal.isLValue() && LHSVal.isInt()) { - RHSVal.getLValueOffset() += CharUnits::fromQuantity( - LHSVal.getInt().getZExtValue()); - // Note that RHSVal is Result. - return true; - } - - if (E->getOpcode() == BO_Sub && LHSVal.isLValue() && RHSVal.isLValue()) { - // Handle (intptr_t)&&A - (intptr_t)&&B. - if (!LHSVal.getLValueOffset().isZero() || - !RHSVal.getLValueOffset().isZero()) - return false; - const Expr *LHSExpr = LHSVal.getLValueBase().dyn_cast<const Expr*>(); - const Expr *RHSExpr = RHSVal.getLValueBase().dyn_cast<const Expr*>(); - if (!LHSExpr || !RHSExpr) - return false; - const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr); - const AddrLabelExpr *RHSAddrExpr = dyn_cast<AddrLabelExpr>(RHSExpr); - if (!LHSAddrExpr || !RHSAddrExpr) - return false; - // Make sure both labels come from the same function. - if (LHSAddrExpr->getLabel()->getDeclContext() != - RHSAddrExpr->getLabel()->getDeclContext()) - return false; - Result = APValue(LHSAddrExpr, RHSAddrExpr); - return true; - } - - // All the following cases expect both operands to be an integer - if (!LHSVal.isInt() || !RHSVal.isInt()) - return Error(E); - - APSInt &LHS = LHSVal.getInt(); - APSInt &RHS = RHSVal.getInt(); - - switch (E->getOpcode()) { - default: - return Error(E); - case BO_Mul: - return Success(CheckedIntArithmetic(Info, E, LHS, RHS, - LHS.getBitWidth() * 2, - std::multiplies<APSInt>()), E); - case BO_Add: - return Success(CheckedIntArithmetic(Info, E, LHS, RHS, - LHS.getBitWidth() + 1, - std::plus<APSInt>()), E); - case BO_Sub: - return Success(CheckedIntArithmetic(Info, E, LHS, RHS, - LHS.getBitWidth() + 1, - std::minus<APSInt>()), E); - case BO_And: return Success(LHS & RHS, E); - case BO_Xor: return Success(LHS ^ RHS, E); - case BO_Or: return Success(LHS | RHS, E); - case BO_Div: - case BO_Rem: - if (RHS == 0) - return Error(E, diag::note_expr_divide_by_zero); - // Check for overflow case: INT_MIN / -1 or INT_MIN % -1. The latter is not - // actually undefined behavior in C++11 due to a language defect. - if (RHS.isNegative() && RHS.isAllOnesValue() && - LHS.isSigned() && LHS.isMinSignedValue()) - HandleOverflow(Info, E, -LHS.extend(LHS.getBitWidth() + 1), E->getType()); - return Success(E->getOpcode() == BO_Rem ? LHS % RHS : LHS / RHS, E); - case BO_Shl: { - // During constant-folding, a negative shift is an opposite shift. Such a - // shift is not a constant expression. - if (RHS.isSigned() && RHS.isNegative()) { - CCEDiag(E, diag::note_constexpr_negative_shift) << RHS; - RHS = -RHS; - goto shift_right; - } - - shift_left: - // C++11 [expr.shift]p1: Shift width must be less than the bit width of the - // shifted type. - unsigned SA = (unsigned) RHS.getLimitedValue(LHS.getBitWidth()-1); - if (SA != RHS) { - CCEDiag(E, diag::note_constexpr_large_shift) - << RHS << E->getType() << LHS.getBitWidth(); - } else if (LHS.isSigned()) { - // C++11 [expr.shift]p2: A signed left shift must have a non-negative - // operand, and must not overflow the corresponding unsigned type. - if (LHS.isNegative()) - CCEDiag(E, diag::note_constexpr_lshift_of_negative) << LHS; - else if (LHS.countLeadingZeros() < SA) - CCEDiag(E, diag::note_constexpr_lshift_discards); - } - - return Success(LHS << SA, E); - } - case BO_Shr: { - // During constant-folding, a negative shift is an opposite shift. Such a - // shift is not a constant expression. - if (RHS.isSigned() && RHS.isNegative()) { - CCEDiag(E, diag::note_constexpr_negative_shift) << RHS; - RHS = -RHS; - goto shift_left; - } - - shift_right: - // C++11 [expr.shift]p1: Shift width must be less than the bit width of the - // shifted type. - unsigned SA = (unsigned) RHS.getLimitedValue(LHS.getBitWidth()-1); - if (SA != RHS) - CCEDiag(E, diag::note_constexpr_large_shift) - << RHS << E->getType() << LHS.getBitWidth(); - - return Success(LHS >> SA, E); - } - - case BO_LT: return Success(LHS < RHS, E); - case BO_GT: return Success(LHS > RHS, E); - case BO_LE: return Success(LHS <= RHS, E); - case BO_GE: return Success(LHS >= RHS, E); - case BO_EQ: return Success(LHS == RHS, E); - case BO_NE: return Success(LHS != RHS, E); - } + assert((!LHSTy->isIntegralOrEnumerationType() || + !RHSTy->isIntegralOrEnumerationType()) && + "DataRecursiveIntBinOpEvaluator should have handled integral types"); + // We can't continue from here for non-integral types. + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); } CharUnits IntExprEvaluator::GetAlignOfType(QualType T) { |