diff --git a/Changelog.md b/Changelog.md index b32b6e00b..c50b395ae 100644 --- a/Changelog.md +++ b/Changelog.md @@ -24,8 +24,10 @@ Breaking Changes: Language Features: * Super constructors can now be called using the member notation e.g. ``M.C(123)``. +Bugfixes: + * Type Checker: Perform proper integer arithmetic when using constants in array length expressions. - AST Changes: +AST Changes: * New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName`` diff --git a/docs/080-breaking-changes.rst b/docs/080-breaking-changes.rst index 0139047fc..e9c206701 100644 --- a/docs/080-breaking-changes.rst +++ b/docs/080-breaking-changes.rst @@ -35,6 +35,10 @@ the compiler notifying you about it. * If a byte array in storage is accessed whose length is encoded incorrectly, a panic is caused. A contract cannot get into this situation unless inline assembly is used to modify the raw representation of storage byte arrays. +* If constants are used in array length expressions, previous versions of Solidity would use arbitrary precision + in all branches of the evaluation tree. Now, if constant variables are used as intermediate expressions, + their values will be properly rounded in the same way as when they are used in run-time expressions. + New Restrictions ================ diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index d00affb91..fdb414c5e 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -32,85 +32,357 @@ using namespace solidity; using namespace solidity::frontend; using namespace solidity::langutil; +using TypedRational = ConstantEvaluator::TypedRational; + +namespace +{ + +/// Check whether (_base ** _exp) fits into 4096 bits. +bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) +{ + if (_base == 0) + return true; + + solAssert(_base > 0, ""); + + size_t const bitsMax = 4096; + + unsigned mostSignificantBaseBit = boost::multiprecision::msb(_base); + if (mostSignificantBaseBit == 0) // _base == 1 + return true; + if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096 + return false; + + bigint bitsNeeded = _exp * (mostSignificantBaseBit + 1); + + return bitsNeeded <= bitsMax; +} + +/// Checks whether _mantissa * (2 ** _expBase10) fits into 4096 bits. +bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) +{ + return fitsPrecisionBaseX(_mantissa, 1.0, _expBase2); +} + +} + +optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, rational const& _left, rational const& _right) +{ + bool fractional = _left.denominator() != 1 || _right.denominator() != 1; + switch (_operator) + { + //bit operations will only be enabled for integers and fixed types that resemble integers + case Token::BitOr: + if (fractional) + return nullopt; + else + return _left.numerator() | _right.numerator(); + case Token::BitXor: + if (fractional) + return nullopt; + else + return _left.numerator() ^ _right.numerator(); + case Token::BitAnd: + if (fractional) + return nullopt; + else + return _left.numerator() & _right.numerator(); + case Token::Add: return _left + _right; + case Token::Sub: return _left - _right; + case Token::Mul: return _left * _right; + case Token::Div: + if (_right == rational(0)) + return nullopt; + else + return _left / _right; + case Token::Mod: + if (_right == rational(0)) + return nullopt; + else if (fractional) + { + rational tempValue = _left / _right; + return _left - (tempValue.numerator() / tempValue.denominator()) * _right; + } + else + return _left.numerator() % _right.numerator(); + break; + case Token::Exp: + { + if (_right.denominator() != 1) + return nullopt; + bigint const& exp = _right.numerator(); + + // x ** 0 = 1 + // for 0, 1 and -1 the size of the exponent doesn't have to be restricted + if (exp == 0) + return 1; + else if (_left == 0 || _left == 1) + return _left; + else if (_left == -1) + { + bigint isOdd = abs(exp) & bigint(1); + return 1 - 2 * isOdd.convert_to(); + } + else + { + if (abs(exp) > numeric_limits::max()) + return nullopt; // This will need too much memory to represent. + + uint32_t absExp = bigint(abs(exp)).convert_to(); + + if (!fitsPrecisionExp(abs(_left.numerator()), absExp) || !fitsPrecisionExp(abs(_left.denominator()), absExp)) + return nullopt; + + static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint { + if (_base == 1) + return 1; + else if (_base == -1) + return 1 - 2 * static_cast(_exponent & 1); + else + return boost::multiprecision::pow(_base, _exponent); + }; + + bigint numerator = optimizedPow(_left.numerator(), absExp); + bigint denominator = optimizedPow(_left.denominator(), absExp); + + if (exp >= 0) + return makeRational(numerator, denominator); + else + // invert + return makeRational(denominator, numerator); + } + break; + } + case Token::SHL: + { + if (fractional) + return nullopt; + else if (_right < 0) + return nullopt; + else if (_right > numeric_limits::max()) + return nullopt; + if (_left.numerator() == 0) + return 0; + else + { + uint32_t exponent = _right.numerator().convert_to(); + if (!fitsPrecisionBase2(abs(_left.numerator()), exponent)) + return nullopt; + return _left.numerator() * boost::multiprecision::pow(bigint(2), exponent); + } + break; + } + // NOTE: we're using >> (SAR) to denote right shifting. The type of the LValue + // determines the resulting type and the type of shift (SAR or SHR). + case Token::SAR: + { + if (fractional) + return nullopt; + else if (_right < 0) + return nullopt; + else if (_right > numeric_limits::max()) + return nullopt; + if (_left.numerator() == 0) + return 0; + else + { + uint32_t exponent = _right.numerator().convert_to(); + if (exponent > boost::multiprecision::msb(boost::multiprecision::abs(_left.numerator()))) + return _left.numerator() < 0 ? -1 : 0; + else + { + if (_left.numerator() < 0) + // Add 1 to the negative value before dividing to get a result that is strictly too large, + // then subtract 1 afterwards to round towards negative infinity. + // This is the same algorithm as used in ExpressionCompiler::appendShiftOperatorCode(...). + // To see this note that for negative x, xor(x,all_ones) = (-x-1) and + // therefore xor(div(xor(x,all_ones), exp(2, shift_amount)), all_ones) is + // -(-x - 1) / 2^shift_amount - 1, which is the same as + // (x + 1) / 2^shift_amount - 1. + return rational((_left.numerator() + 1) / boost::multiprecision::pow(bigint(2), exponent) - bigint(1), 1); + else + return rational(_left.numerator() / boost::multiprecision::pow(bigint(2), exponent), 1); + } + } + break; + } + default: + return nullopt; + } +} + +optional ConstantEvaluator::evaluateUnaryOperator(Token _operator, rational const& _input) +{ + switch (_operator) + { + case Token::BitNot: + if (_input.denominator() != 1) + return nullopt; + else + return ~_input.numerator(); + case Token::Sub: + return -_input; + default: + return nullopt; + } +} + +optional convertType(rational const& _value, Type const& _type) +{ + if (_type.category() == Type::Category::RationalNumber) + return TypedRational{TypeProvider::rationalNumber(_value), _value}; + else if (auto const* integerType = dynamic_cast(&_type)) + { + if (_value > integerType->maxValue() || _value < integerType->minValue()) + return nullopt; + else + return TypedRational{&_type, _value.numerator() / _value.denominator()}; + } + else + return nullopt; +} + +optional convertType(optional const& _value, Type const& _type) +{ + return _value ? convertType(_value->value, _type) : nullopt; +} + +optional constantToTypedValue(Type const& _type) +{ + if (_type.category() == Type::Category::RationalNumber) + return TypedRational{&_type, dynamic_cast(_type).value()}; + else + return nullopt; +} + +optional ConstantEvaluator::evaluate( + langutil::ErrorReporter& _errorReporter, + Expression const& _expr +) +{ + return ConstantEvaluator{_errorReporter}.evaluate(_expr); +} + + +optional ConstantEvaluator::evaluate(ASTNode const& _node) +{ + if (!m_values.count(&_node)) + { + if (auto const* varDecl = dynamic_cast(&_node)) + { + solAssert(varDecl->isConstant(), ""); + if (!varDecl->value()) + m_values[&_node] = nullopt; + else + { + m_depth++; + if (m_depth > 32) + m_errorReporter.fatalTypeError( + 5210_error, + varDecl->location(), + "Cyclic constant definition (or maximum recursion depth exhausted)." + ); + m_values[&_node] = convertType(evaluate(*varDecl->value()), *varDecl->type()); + m_depth--; + } + } + else if (auto const* expression = dynamic_cast(&_node)) + { + expression->accept(*this); + if (!m_values.count(&_node)) + m_values[&_node] = nullopt; + } + } + return m_values.at(&_node); +} + void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { - auto sub = type(_operation.subExpression()); - if (sub) - setType(_operation, sub->unaryOperatorResult(_operation.getOperator())); + optional value = evaluate(_operation.subExpression()); + if (!value) + return; + + TypePointer resultType = value->type->unaryOperatorResult(_operation.getOperator()); + if (!resultType) + return; + value = convertType(value, *resultType); + if (!value) + return; + + if (optional result = evaluateUnaryOperator(_operation.getOperator(), value->value)) + { + optional convertedValue = convertType(*result, *resultType); + if (!convertedValue) + m_errorReporter.fatalTypeError( + 3667_error, + _operation.location(), + "Arithmetic error when computing constant value." + ); + m_values[&_operation] = convertedValue; + } } void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { - auto left = type(_operation.leftExpression()); - auto right = type(_operation.rightExpression()); - if (left && right) + optional left = evaluate(_operation.leftExpression()); + optional right = evaluate(_operation.rightExpression()); + if (!left || !right) + return; + + // If this is implemented in the future: Comparison operators have a "binaryOperatorResult" + // that is non-bool, but the result has to be bool. + if (TokenTraits::isCompareOp(_operation.getOperator())) + return; + + TypePointer resultType = left->type->binaryOperatorResult(_operation.getOperator(), right->type); + if (!resultType) { - TypePointer commonType = left->binaryOperatorResult(_operation.getOperator(), right); - if (!commonType) - m_errorReporter.fatalTypeError( - 6020_error, - _operation.location(), - "Operator " + - string(TokenTraits::toString(_operation.getOperator())) + - " not compatible with types " + - left->toString() + - " and " + - right->toString() + m_errorReporter.fatalTypeError( + 6020_error, + _operation.location(), + "Operator " + + string(TokenTraits::toString(_operation.getOperator())) + + " not compatible with types " + + left->type->toString() + + " and " + + right->type->toString() ); - setType( - _operation, - TokenTraits::isCompareOp(_operation.getOperator()) ? - TypeProvider::boolean() : - commonType - ); + return; + } + + left = convertType(left, *resultType); + right = convertType(right, *resultType); + if (!left || !right) + return; + + if (optional value = evaluateBinaryOperator(_operation.getOperator(), left->value, right->value)) + { + optional convertedValue = convertType(*value, *resultType); + if (!convertedValue) + m_errorReporter.fatalTypeError( + 2643_error, + _operation.location(), + "Arithmetic error when computing constant value." + ); + m_values[&_operation] = convertedValue; } } void ConstantEvaluator::endVisit(Literal const& _literal) { - setType(_literal, TypeProvider::forLiteral(_literal)); + if (Type const* literalType = TypeProvider::forLiteral(_literal)) + m_values[&_literal] = constantToTypedValue(*literalType); } void ConstantEvaluator::endVisit(Identifier const& _identifier) { VariableDeclaration const* variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration); - if (!variableDeclaration) - return; - if (!variableDeclaration->isConstant()) - return; - - ASTPointer const& value = variableDeclaration->value(); - if (!value) - return; - else if (!m_types->count(value.get())) - { - if (m_depth > 32) - m_errorReporter.fatalTypeError(5210_error, _identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted)."); - ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value); - } - - setType(_identifier, type(*value)); + if (variableDeclaration && variableDeclaration->isConstant()) + m_values[&_identifier] = evaluate(*variableDeclaration); } void ConstantEvaluator::endVisit(TupleExpression const& _tuple) { if (!_tuple.isInlineArray() && _tuple.components().size() == 1) - setType(_tuple, type(*_tuple.components().front())); -} - -void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type) -{ - if (_type && _type->category() == Type::Category::RationalNumber) - (*m_types)[&_node] = _type; -} - -TypePointer ConstantEvaluator::type(ASTNode const& _node) -{ - return (*m_types)[&_node]; -} - -TypePointer ConstantEvaluator::evaluate(Expression const& _expr) -{ - _expr.accept(*this); - return type(_expr); + m_values[&_tuple] = evaluate(*_tuple.components().front()); } diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index 521f46338..7172258c3 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -39,37 +39,48 @@ class TypeChecker; /** * Small drop-in replacement for TypeChecker to evaluate simple expressions of integer constants. + * + * Note: This always use "checked arithmetic" in the sense that any over- or underflow + * results in "unknown" value. */ class ConstantEvaluator: private ASTConstVisitor { public: - ConstantEvaluator( - langutil::ErrorReporter& _errorReporter, - size_t _newDepth = 0, - std::shared_ptr> _types = std::make_shared>() - ): - m_errorReporter(_errorReporter), - m_depth(_newDepth), - m_types(std::move(_types)) + struct TypedRational { - } + TypePointer type; + rational value; + }; - TypePointer evaluate(Expression const& _expr); + static std::optional evaluate( + langutil::ErrorReporter& _errorReporter, + Expression const& _expr + ); + + /// Performs arbitrary-precision evaluation of a binary operator. Returns nullopt on cases like + /// division by zero or e.g. bit operators applied to fractional values. + static std::optional evaluateBinaryOperator(Token _operator, rational const& _left, rational const& _right); + + /// Performs arbitrary-precision evaluation of a unary operator. Returns nullopt on cases like + /// bit operators applied to fractional values. + static std::optional evaluateUnaryOperator(Token _operator, rational const& _input); private: + explicit ConstantEvaluator(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + + std::optional evaluate(ASTNode const& _node); + void endVisit(BinaryOperation const& _operation) override; void endVisit(UnaryOperation const& _operation) override; void endVisit(Literal const& _literal) override; void endVisit(Identifier const& _identifier) override; void endVisit(TupleExpression const& _tuple) override; - void setType(ASTNode const& _node, TypePointer const& _type); - TypePointer type(ASTNode const& _node); - langutil::ErrorReporter& m_errorReporter; /// Current recursion depth. size_t m_depth = 0; - std::shared_ptr> m_types; + /// Values of sub-expressions and variable declarations. + std::map> m_values; }; } diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index 14ed89a41..5c267b285 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -265,26 +265,30 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName) solAssert(baseType->storageBytes() != 0, "Illegal base type of storage size zero for array."); if (Expression const* length = _typeName.length()) { - TypePointer& lengthTypeGeneric = length->annotation().type; - if (!lengthTypeGeneric) - lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length); - RationalNumberType const* lengthType = dynamic_cast(lengthTypeGeneric); - u256 lengthValue = 0; - if (!lengthType || !lengthType->mobileType()) + optional lengthValue; + if (length->annotation().type && length->annotation().type->category() == Type::Category::RationalNumber) + lengthValue = dynamic_cast(*length->annotation().type).value(); + else if (optional value = ConstantEvaluator::evaluate(m_errorReporter, *length)) + lengthValue = value->value; + + if (!lengthValue || lengthValue > TypeProvider::uint256()->max()) m_errorReporter.typeError( 5462_error, length->location(), "Invalid array length, expected integer literal or constant expression." ); - else if (lengthType->isZero()) + else if (*lengthValue == 0) m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified."); - else if (lengthType->isFractional()) + else if (lengthValue->denominator() != 1) m_errorReporter.typeError(3208_error, length->location(), "Array with fractional length specified."); - else if (lengthType->isNegative()) + else if (*lengthValue < 0) m_errorReporter.typeError(3658_error, length->location(), "Array with negative length specified."); - else - lengthValue = lengthType->literalValue(nullptr); - _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue); + + _typeName.annotation().type = TypeProvider::array( + DataLocation::Storage, + baseType, + lengthValue ? u256(lengthValue->numerator()) : u256(0) + ); } else _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType); diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index abfeae23d..7b828ebce 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -290,10 +290,8 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation) *_operation.rightExpression().annotation().isPure && (_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod) ) - if (auto rhs = dynamic_cast( - ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression()) - )) - if (rhs->isZero()) + if (auto rhs = ConstantEvaluator::evaluate(m_errorReporter, _operation.rightExpression())) + if (rhs->value == 0) m_errorReporter.typeError( 1211_error, _operation.location(), @@ -313,10 +311,8 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) { solAssert(_functionCall.arguments().size() == 3, ""); if (*_functionCall.arguments()[2]->annotation().isPure) - if (auto lastArg = dynamic_cast( - ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2]) - )) - if (lastArg->isZero()) + if (auto lastArg = ConstantEvaluator::evaluate(m_errorReporter, *(_functionCall.arguments())[2])) + if (lastArg->value == 0) m_errorReporter.typeError( 4195_error, _functionCall.location(), diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 82a80d974..35910ceac 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include #include #include @@ -56,50 +58,6 @@ using namespace solidity::frontend; namespace { -/// Check whether (_base ** _exp) fits into 4096 bits. -bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) -{ - if (_base == 0) - return true; - - solAssert(_base > 0, ""); - - size_t const bitsMax = 4096; - - unsigned mostSignificantBaseBit = boost::multiprecision::msb(_base); - if (mostSignificantBaseBit == 0) // _base == 1 - return true; - if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096 - return false; - - bigint bitsNeeded = _exp * (mostSignificantBaseBit + 1); - - return bitsNeeded <= bitsMax; -} - -/// Checks whether _mantissa * (X ** _exp) fits into 4096 bits, -/// where X is given indirectly via _log2OfBase = log2(X). -bool fitsPrecisionBaseX( - bigint const& _mantissa, - double _log2OfBase, - uint32_t _exp -) -{ - if (_mantissa == 0) - return true; - - solAssert(_mantissa > 0, ""); - - size_t const bitsMax = 4096; - - unsigned mostSignificantMantissaBit = boost::multiprecision::msb(_mantissa); - if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096 - return false; - - bigint bitsNeeded = mostSignificantMantissaBit + bigint(floor(double(_exp) * _log2OfBase)) + 1; - return bitsNeeded <= bitsMax; -} - /// Checks whether _mantissa * (10 ** _expBase10) fits into 4096 bits. bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10) { @@ -107,12 +65,6 @@ bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10) return fitsPrecisionBaseX(_mantissa, log2Of10AwayFromZero, _expBase10); } -/// Checks whether _mantissa * (2 ** _expBase10) fits into 4096 bits. -bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) -{ - return fitsPrecisionBaseX(_mantissa, 1.0, _expBase2); -} - /// Checks whether _value fits into IntegerType _type. BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type) { @@ -1000,26 +952,10 @@ BoolResult RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const { - rational value; - switch (_operator) - { - case Token::BitNot: - if (isFractional()) - return nullptr; - value = ~m_value.numerator(); - break; - case Token::Add: - value = +(m_value); - break; - case Token::Sub: - value = -(m_value); - break; - case Token::After: - return this; - default: + if (optional value = ConstantEvaluator::evaluateUnaryOperator(_operator, m_value)) + return TypeResult{TypeProvider::rationalNumber(*value)}; + else return nullptr; - } - return TypeResult{TypeProvider::rationalNumber(value)}; } TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* _other) const @@ -1074,165 +1010,16 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* return nullptr; return thisMobile->binaryOperatorResult(_operator, otherMobile); } - else + else if (optional value = ConstantEvaluator::evaluateBinaryOperator(_operator, m_value, other.m_value)) { - rational value; - bool fractional = isFractional() || other.isFractional(); - switch (_operator) - { - //bit operations will only be enabled for integers and fixed types that resemble integers - case Token::BitOr: - if (fractional) - return nullptr; - value = m_value.numerator() | other.m_value.numerator(); - break; - case Token::BitXor: - if (fractional) - return nullptr; - value = m_value.numerator() ^ other.m_value.numerator(); - break; - case Token::BitAnd: - if (fractional) - return nullptr; - value = m_value.numerator() & other.m_value.numerator(); - break; - case Token::Add: - value = m_value + other.m_value; - break; - case Token::Sub: - value = m_value - other.m_value; - break; - case Token::Mul: - value = m_value * other.m_value; - break; - case Token::Div: - if (other.m_value == rational(0)) - return nullptr; - else - value = m_value / other.m_value; - break; - case Token::Mod: - if (other.m_value == rational(0)) - return nullptr; - else if (fractional) - { - rational tempValue = m_value / other.m_value; - value = m_value - (tempValue.numerator() / tempValue.denominator()) * other.m_value; - } - else - value = m_value.numerator() % other.m_value.numerator(); - break; - case Token::Exp: - { - if (other.isFractional()) - return nullptr; - solAssert(other.m_value.denominator() == 1, ""); - bigint const& exp = other.m_value.numerator(); - - // x ** 0 = 1 - // for 0, 1 and -1 the size of the exponent doesn't have to be restricted - if (exp == 0) - value = 1; - else if (m_value.numerator() == 0 || m_value == 1) - value = m_value; - else if (m_value == -1) - { - bigint isOdd = abs(exp) & bigint(1); - value = 1 - 2 * isOdd.convert_to(); - } - else - { - if (abs(exp) > numeric_limits::max()) - return nullptr; // This will need too much memory to represent. - - uint32_t absExp = bigint(abs(exp)).convert_to(); - - if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp)) - return TypeResult::err("Precision of rational constants is limited to 4096 bits."); - - static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint { - if (_base == 1) - return 1; - else if (_base == -1) - return 1 - 2 * static_cast(_exponent & 1); - else - return boost::multiprecision::pow(_base, _exponent); - }; - - bigint numerator = optimizedPow(m_value.numerator(), absExp); - bigint denominator = optimizedPow(m_value.denominator(), absExp); - - if (exp >= 0) - value = makeRational(numerator, denominator); - else - // invert - value = makeRational(denominator, numerator); - } - break; - } - case Token::SHL: - { - if (fractional) - return nullptr; - else if (other.m_value < 0) - return nullptr; - else if (other.m_value > numeric_limits::max()) - return nullptr; - if (m_value.numerator() == 0) - value = 0; - else - { - uint32_t exponent = other.m_value.numerator().convert_to(); - if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent)) - return nullptr; - value = m_value.numerator() * boost::multiprecision::pow(bigint(2), exponent); - } - break; - } - // NOTE: we're using >> (SAR) to denote right shifting. The type of the LValue - // determines the resulting type and the type of shift (SAR or SHR). - case Token::SAR: - { - if (fractional) - return nullptr; - else if (other.m_value < 0) - return nullptr; - else if (other.m_value > numeric_limits::max()) - return nullptr; - if (m_value.numerator() == 0) - value = 0; - else - { - uint32_t exponent = other.m_value.numerator().convert_to(); - if (exponent > boost::multiprecision::msb(boost::multiprecision::abs(m_value.numerator()))) - value = m_value.numerator() < 0 ? -1 : 0; - else - { - if (m_value.numerator() < 0) - // Add 1 to the negative value before dividing to get a result that is strictly too large, - // then subtract 1 afterwards to round towards negative infinity. - // This is the same algorithm as used in ExpressionCompiler::appendShiftOperatorCode(...). - // To see this note that for negative x, xor(x,all_ones) = (-x-1) and - // therefore xor(div(xor(x,all_ones), exp(2, shift_amount)), all_ones) is - // -(-x - 1) / 2^shift_amount - 1, which is the same as - // (x + 1) / 2^shift_amount - 1. - value = rational((m_value.numerator() + 1) / boost::multiprecision::pow(bigint(2), exponent) - bigint(1), 1); - else - value = rational(m_value.numerator() / boost::multiprecision::pow(bigint(2), exponent), 1); - } - } - break; - } - default: - return nullptr; - } - // verify that numerator and denominator fit into 4096 bit after every operation - if (value.numerator() != 0 && max(boost::multiprecision::msb(abs(value.numerator())), boost::multiprecision::msb(abs(value.denominator()))) > 4096) + if (value->numerator() != 0 && max(boost::multiprecision::msb(abs(value->numerator())), boost::multiprecision::msb(abs(value->denominator()))) > 4096) return TypeResult::err("Precision of rational constants is limited to 4096 bits."); - return TypeResult{TypeProvider::rationalNumber(value)}; + return TypeResult{TypeProvider::rationalNumber(*value)}; } + else + return nullptr; } string RationalNumberType::richIdentifier() const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 60ce4686f..b366df78e 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -568,6 +568,11 @@ public: u256 literalValue(Literal const* _literal) const override; TypePointer mobileType() const override; + /// @returns the underlying raw literal value. + /// + /// @see literalValue(Literal const*)) + rational const& value() const noexcept { return m_value; } + /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. IntegerType const* integerType() const; /// @returns the smallest fixed type that can hold the value or incurs the least precision loss, diff --git a/libsolutil/CMakeLists.txt b/libsolutil/CMakeLists.txt index ea8bcdf86..a38df7443 100644 --- a/libsolutil/CMakeLists.txt +++ b/libsolutil/CMakeLists.txt @@ -2,6 +2,7 @@ set(sources Algorithms.h AnsiColorized.h Assertions.h + Common.cpp Common.h CommonData.cpp CommonData.h diff --git a/libsolutil/Common.cpp b/libsolutil/Common.cpp new file mode 100644 index 000000000..3f77e5d25 --- /dev/null +++ b/libsolutil/Common.cpp @@ -0,0 +1,40 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +using namespace solidity; + +bool solidity::fitsPrecisionBaseX(bigint const& _mantissa, double _log2OfBase, uint32_t _exp) +{ + if (_mantissa == 0) + return true; + + solAssert(_mantissa > 0, ""); + + size_t const bitsMax = 4096; + + unsigned mostSignificantMantissaBit = boost::multiprecision::msb(_mantissa); + if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096 + return false; + + bigint bitsNeeded = mostSignificantMantissaBit + bigint(floor(double(_exp) * _log2OfBase)) + 1; + return bitsNeeded <= bitsMax; +} diff --git a/libsolutil/Common.h b/libsolutil/Common.h index aae2fa859..659ea2e3e 100644 --- a/libsolutil/Common.h +++ b/libsolutil/Common.h @@ -107,6 +107,10 @@ inline u256 exp256(u256 _base, u256 _exponent) return result; } +/// Checks whether _mantissa * (X ** _exp) fits into 4096 bits, +/// where X is given indirectly via _log2OfBase = log2(X). +bool fitsPrecisionBaseX(bigint const& _mantissa, double _log2OfBase, uint32_t _exp); + inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) { std::ostringstream ss; diff --git a/test/libsolidity/semanticTests/constantEvaluator/negative_fractional_mod.sol b/test/libsolidity/semanticTests/constantEvaluator/negative_fractional_mod.sol new file mode 100644 index 000000000..21675e51a --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/negative_fractional_mod.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (int, int) { + int x = int((-(-5.2 % 3)) * 5); + int t = 5; + return (x, (-(-t % 3)) * 5); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 11, 10 diff --git a/test/libsolidity/semanticTests/constantEvaluator/rounding.sol b/test/libsolidity/semanticTests/constantEvaluator/rounding.sol new file mode 100644 index 000000000..a640d8676 --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/rounding.sol @@ -0,0 +1,15 @@ +contract C { + int constant a = 7; + int constant b = 3; + int constant c = a / b; + int constant d = (-a) / b; + function f() public pure returns (uint, int, uint, int) { + uint[c] memory x; + uint[-d] memory y; + return (x.length, c, y.length, -d); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 2, 2, 2, 2 diff --git a/test/libsolidity/semanticTests/constants/consteval_array_length.sol b/test/libsolidity/semanticTests/constants/consteval_array_length.sol new file mode 100644 index 000000000..91e929834 --- /dev/null +++ b/test/libsolidity/semanticTests/constants/consteval_array_length.sol @@ -0,0 +1,14 @@ +contract C { + uint constant a = 12; + uint constant b = 10; + + function f() public pure returns (uint, uint) { + uint[(a / b) * b] memory x; + return (x.length, (a / b) * b); + } +} +// ==== +// compileViaYul: true +// ---- +// constructor() -> +// f() -> 0x0a, 0x0a diff --git a/test/libsolidity/syntaxTests/array/length/complex_cyclic_constant.sol b/test/libsolidity/syntaxTests/array/length/complex_cyclic_constant.sol index 38018165c..0710124a1 100644 --- a/test/libsolidity/syntaxTests/array/length/complex_cyclic_constant.sol +++ b/test/libsolidity/syntaxTests/array/length/complex_cyclic_constant.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// TypeError 5210: (36-39): Cyclic constant definition (or maximum recursion depth exhausted). +// TypeError 5210: (17-44): Cyclic constant definition (or maximum recursion depth exhausted). diff --git a/test/libsolidity/syntaxTests/array/length/const_cannot_be_fractional.sol b/test/libsolidity/syntaxTests/array/length/const_cannot_be_fractional.sol index ca200cdd0..2145d7025 100644 --- a/test/libsolidity/syntaxTests/array/length/const_cannot_be_fractional.sol +++ b/test/libsolidity/syntaxTests/array/length/const_cannot_be_fractional.sol @@ -3,4 +3,4 @@ contract C { uint[L] ids; } // ---- -// TypeError 3208: (51-52): Array with fractional length specified. +// TypeError 5462: (51-52): Invalid array length, expected integer literal or constant expression. diff --git a/test/libsolidity/syntaxTests/array/length/cyclic_constant.sol b/test/libsolidity/syntaxTests/array/length/cyclic_constant.sol index 08a5f1cf9..dd089e4d0 100644 --- a/test/libsolidity/syntaxTests/array/length/cyclic_constant.sol +++ b/test/libsolidity/syntaxTests/array/length/cyclic_constant.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// TypeError 5210: (37-40): Cyclic constant definition (or maximum recursion depth exhausted). +// TypeError 5210: (17-40): Cyclic constant definition (or maximum recursion depth exhausted). diff --git a/test/libsolidity/syntaxTests/constantEvaluator/overflow.sol b/test/libsolidity/syntaxTests/constantEvaluator/overflow.sol new file mode 100644 index 000000000..55bd27901 --- /dev/null +++ b/test/libsolidity/syntaxTests/constantEvaluator/overflow.sol @@ -0,0 +1,9 @@ +contract C { + uint8 constant a = 255; + uint16 constant b = a + 2; + function f() public pure { + uint[b] memory x; + } +} +// ---- +// TypeError 2643: (65-70): Arithmetic error when computing constant value. diff --git a/test/libsolidity/syntaxTests/constantEvaluator/unary_fine.sol b/test/libsolidity/syntaxTests/constantEvaluator/unary_fine.sol new file mode 100644 index 000000000..f36c45c0d --- /dev/null +++ b/test/libsolidity/syntaxTests/constantEvaluator/unary_fine.sol @@ -0,0 +1,8 @@ +contract C { + int8 constant a = -7; + function f() public pure { + uint[-a] memory x; + x[0] = 2; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/constantEvaluator/underflow.sol b/test/libsolidity/syntaxTests/constantEvaluator/underflow.sol new file mode 100644 index 000000000..6ca25911a --- /dev/null +++ b/test/libsolidity/syntaxTests/constantEvaluator/underflow.sol @@ -0,0 +1,8 @@ +contract C { + uint8 constant a = 0; + function f() public pure { + uint[a - 1] memory x; + } +} +// ---- +// TypeError 2643: (83-88): Arithmetic error when computing constant value. diff --git a/test/libsolidity/syntaxTests/constantEvaluator/underflow_unary.sol b/test/libsolidity/syntaxTests/constantEvaluator/underflow_unary.sol new file mode 100644 index 000000000..5887ab19d --- /dev/null +++ b/test/libsolidity/syntaxTests/constantEvaluator/underflow_unary.sol @@ -0,0 +1,8 @@ +contract C { + int8 constant a = -128; + function f() public pure { + uint[-a] memory x; + } +} +// ---- +// TypeError 3667: (85-87): Arithmetic error when computing constant value. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/308_rational_unary_plus_operation.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/308_rational_unary_plus_operation.sol index 8b401c1ae..276c6533d 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/308_rational_unary_plus_operation.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/308_rational_unary_plus_operation.sol @@ -7,3 +7,4 @@ contract test { } // ---- // SyntaxError 9636: (70-75): Use of unary + is disallowed. +// TypeError 4907: (70-75): Unary operator + cannot be applied to type rational_const 13 / 4 diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/array_length_fractional_computed.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/array_length_fractional_computed.sol new file mode 100644 index 000000000..dd4c8cb83 --- /dev/null +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/array_length_fractional_computed.sol @@ -0,0 +1,10 @@ +contract test { + uint constant a = 7; + uint constant b = 3; + function f() public { + uint[a / b] memory x; x[0]; + uint[7 / 3] memory y; y[0]; + } +} +// ---- +// TypeError 3208: (141-146): Array with fractional length specified. diff --git a/test/libsolidity/syntaxTests/types/rational_number_exp_limit_fail.sol b/test/libsolidity/syntaxTests/types/rational_number_exp_limit_fail.sol index 4a9fc7349..2d7fbdfad 100644 --- a/test/libsolidity/syntaxTests/types/rational_number_exp_limit_fail.sol +++ b/test/libsolidity/syntaxTests/types/rational_number_exp_limit_fail.sol @@ -19,7 +19,7 @@ contract c { } } // ---- -// TypeError 2271: (71-112): Operator ** not compatible with types int_const 1797...(301 digits omitted)...7216 and int_const 4. Precision of rational constants is limited to 4096 bits. +// TypeError 2271: (71-112): Operator ** not compatible with types int_const 1797...(301 digits omitted)...7216 and int_const 4 // TypeError 7407: (71-112): Type int_const 1797...(301 digits omitted)...7216 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. // TypeError 2271: (135-151): Operator ** not compatible with types int_const 4 and int_const 1157...(70 digits omitted)...9936 // TypeError 7407: (126-169): Type int_const 1340...(147 digits omitted)...4096 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. @@ -29,13 +29,13 @@ contract c { // TypeError 2271: (258-270): Operator ** not compatible with types int_const -2 and int_const 1000...(1226 digits omitted)...0000 // TypeError 2271: (284-296): Operator ** not compatible with types int_const 2 and int_const -100...(1227 digits omitted)...0000 // TypeError 2271: (310-323): Operator ** not compatible with types int_const -2 and int_const -100...(1227 digits omitted)...0000 -// TypeError 2271: (337-348): Operator ** not compatible with types int_const 1000...(1226 digits omitted)...0000 and int_const 2. Precision of rational constants is limited to 4096 bits. +// TypeError 2271: (337-348): Operator ** not compatible with types int_const 1000...(1226 digits omitted)...0000 and int_const 2 // TypeError 7407: (337-348): Type int_const 1000...(1226 digits omitted)...0000 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. -// TypeError 2271: (362-374): Operator ** not compatible with types int_const -100...(1227 digits omitted)...0000 and int_const 2. Precision of rational constants is limited to 4096 bits. +// TypeError 2271: (362-374): Operator ** not compatible with types int_const -100...(1227 digits omitted)...0000 and int_const 2 // TypeError 7407: (362-374): Type int_const -100...(1227 digits omitted)...0000 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. -// TypeError 2271: (388-400): Operator ** not compatible with types int_const 1000...(1226 digits omitted)...0000 and int_const -2. Precision of rational constants is limited to 4096 bits. +// TypeError 2271: (388-400): Operator ** not compatible with types int_const 1000...(1226 digits omitted)...0000 and int_const -2 // TypeError 7407: (388-400): Type int_const 1000...(1226 digits omitted)...0000 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. -// TypeError 2271: (414-427): Operator ** not compatible with types int_const -100...(1227 digits omitted)...0000 and int_const -2. Precision of rational constants is limited to 4096 bits. +// TypeError 2271: (414-427): Operator ** not compatible with types int_const -100...(1227 digits omitted)...0000 and int_const -2 // TypeError 7407: (414-427): Type int_const -100...(1227 digits omitted)...0000 is not implicitly convertible to expected type int256. Literal is too large to fit in int256. // TypeError 2271: (441-457): Operator ** not compatible with types int_const 1000...(1226 digits omitted)...0000 and int_const 1000...(1226 digits omitted)...0000 // TypeError 7407: (441-457): Type int_const 1000...(1226 digits omitted)...0000 is not implicitly convertible to expected type int256. Literal is too large to fit in int256.