From c5d172c058a1d8b52310f16309d8ec7068540067 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 16 Nov 2020 12:19:52 +0100 Subject: [PATCH] Reimplement constant evaluator. --- libsolidity/analysis/ConstantEvaluator.cpp | 195 ++++++++++++------ libsolidity/analysis/ConstantEvaluator.h | 31 +-- .../analysis/DeclarationTypeChecker.cpp | 28 +-- libsolidity/analysis/StaticAnalyzer.cpp | 12 +- libsolidity/ast/Types.h | 5 + libsolutil/CMakeLists.txt | 2 +- .../constantEvaluator/rounding.sol | 15 ++ .../constants/consteval_array_length.sol | 14 ++ .../array/length/complex_cyclic_constant.sol | 2 +- .../length/const_cannot_be_fractional.sol | 2 +- .../array/length/cyclic_constant.sol | 2 +- .../constantEvaluator/overflow.sol | 9 + .../constantEvaluator/unary_fine.sol | 8 + .../constantEvaluator/underflow.sol | 8 + .../constantEvaluator/underflow_unary.sol | 8 + 15 files changed, 245 insertions(+), 96 deletions(-) create mode 100644 test/libsolidity/semanticTests/constantEvaluator/rounding.sol create mode 100644 test/libsolidity/semanticTests/constants/consteval_array_length.sol create mode 100644 test/libsolidity/syntaxTests/constantEvaluator/overflow.sol create mode 100644 test/libsolidity/syntaxTests/constantEvaluator/unary_fine.sol create mode 100644 test/libsolidity/syntaxTests/constantEvaluator/underflow.sol create mode 100644 test/libsolidity/syntaxTests/constantEvaluator/underflow_unary.sol diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 8ec9ccdf9..8a5fb8a66 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -32,6 +32,8 @@ using namespace solidity; using namespace solidity::frontend; using namespace solidity::langutil; +using TypedRational = ConstantEvaluator::TypedRational; + namespace { @@ -227,85 +229,162 @@ optional ConstantEvaluator::evaluateUnaryOperator(Token _operator, rat } } +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 a656ef46b..7172258c3 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -39,22 +39,23 @@ 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. @@ -65,19 +66,21 @@ public: 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.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 76da41b08..a38df7443 100644 --- a/libsolutil/CMakeLists.txt +++ b/libsolutil/CMakeLists.txt @@ -2,8 +2,8 @@ set(sources Algorithms.h AnsiColorized.h Assertions.h - Common.h Common.cpp + Common.h CommonData.cpp CommonData.h CommonIO.cpp 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.