diff --git a/Changelog.md b/Changelog.md index 5c1420ca346c..5cf011ae41f6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -17,6 +17,7 @@ Compiler Features: * Yul Optimizer: Improve performance of control flow side effects collector and function references resolver. Bugfixes: +* Constant Evaluator: Fix incorrect calculation of bit operations which was not consistent with codegen. * Yul: Fix incorrect serialization of Yul object names containing double quotes and escape sequences, producing output that could not be parsed as valid Yul. * Yul EVM Code Transform: Improve stack shuffler performance by fixing a BFS deduplication issue. diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index f094988d9bd6..d2f7d86d6f2a 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -275,6 +275,30 @@ TypedValue convertType(TypedValue const& _value, Type const& _type) }, _value.value); } +rational truncateToType(rational const& _value, Type const& _type) +{ + if (_type.category() != Type::Category::Integer) + return _value; + + auto const* integerType = dynamic_cast(&_type); + solAssert(integerType); + + solAssert(_value.denominator() == 1); + bigint integerValue = _value.numerator(); + + unsigned int numBits = integerType->numBits(); + bigint mask = (bigint(1) << numBits) - 1; + bigint sign = bigint(1) << (numBits - 1); + // clean bits out of range + integerValue = integerValue & mask; + + // extend sign if needed + if (integerType->isSigned() && boost::multiprecision::bit_test(integerValue, numBits - 1)) + integerValue = integerValue | ~mask; + + return rational(integerValue); +} + TypedValue constantToTypedValue(Type const& _type) { if (_type.category() == Type::Category::RationalNumber) @@ -358,7 +382,11 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation) if (std::optional result = evaluateUnaryOperator(_operation.getOperator(), std::get(value.value))) { - TypedValue convertedValue = convertType(*result, *resultType); + rational resultValue = *result; + if (TokenTraits::isBitOp(_operation.getOperator())) + resultValue = truncateToType(*result, *resultType); + + TypedValue convertedValue = convertType(resultValue, *resultType); if (!convertedValue.type) m_errorReporter.fatalTypeError( 3667_error, @@ -411,8 +439,12 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) std::get(right.value) )) { - TypedValue convertedValue = convertType(*value, *resultType); - if (!convertedValue.type) + rational resultValue = *value; + if (TokenTraits::isShiftOp(_operation.getOperator())) + resultValue = truncateToType(*value, *resultType); + + TypedValue convertedValue = convertType(resultValue, *resultType); + if (!convertedValue.type) m_errorReporter.fatalTypeError( 2643_error, _operation.location(), diff --git a/test/libsolidity/semanticTests/constantEvaluator/bit_not_operation_runtime_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/bit_not_operation_runtime_equivalence.sol new file mode 100644 index 000000000000..984c919b332c --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/bit_not_operation_runtime_equivalence.sol @@ -0,0 +1,36 @@ +uint8 constant U253 = 253; // 1111 1101 +int8 constant I128 = -128; // 1000 0000 +int8 constant IONE = 1; // 0000 0001 +contract C { + uint8 constant UNSIGNED = ~U253; // = 2 (0000 0010) + uint[UNSIGNED] a; + int8 constant NEGATIVE_SIGNED = ~I128; // = 127 (0111 1111) + uint[NEGATIVE_SIGNED] b; + int8 constant POSITIVE_SIGNED = ~IONE; // = -2 (1111 1110) + uint[POSITIVE_SIGNED * -1] c; + function testUnsignedEquivalence() public view returns (bool) { + uint8 runTimeResult = ~U253; + + return + UNSIGNED == runTimeResult && + a.length == runTimeResult; + } + function testNegativeSignedEquivalence() public view returns (bool) { + int8 runTimeResult = ~I128; + + return + NEGATIVE_SIGNED == runTimeResult && + b.length == 127; + } + function testPositiveSignedEquivalence() public view returns (bool) { + int8 runTimeResult = ~IONE; + + return + POSITIVE_SIGNED == runTimeResult && + c.length == 2; + } +} +// ---- +// testUnsignedEquivalence() -> true +// testNegativeSignedEquivalence() -> true +// testPositiveSignedEquivalence() -> true diff --git a/test/libsolidity/semanticTests/constantEvaluator/shift_signed_left_operand_runtime_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/shift_signed_left_operand_runtime_equivalence.sol new file mode 100644 index 000000000000..47b698396704 --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/shift_signed_left_operand_runtime_equivalence.sol @@ -0,0 +1,53 @@ +uint256 constant ONE = 1; +int8 constant I8_NEGATIVE_63 = -63; +int8 constant I8_POSITIVE_127 = 127; +int16 constant I16_POSITIVE_127 = 127; + +contract C { + // right side cannot be signed + int256 constant LITERAL_WRAP = -2**255 << ONE; // = 0 + uint[LITERAL_WRAP + 1] a; + int8 constant CONST_NO_WRAP = I8_NEGATIVE_63 << 1; + uint[CONST_NO_WRAP * -1] b; + int8 constant CONST_WRAP = I8_POSITIVE_127 << 1; // = -2 (1111 1110) + uint[CONST_WRAP * -1] c; + int16 constant CONST_SIGN_CHANGED = I16_POSITIVE_127 << 9; // = -512 (1111 1110 0000 0000) + uint[CONST_SIGN_CHANGED * -1] d; + + function testLiteralWrapEquivalence() public view returns (bool) { + int256 runTimeResult = -2**255 << ONE; + + return + LITERAL_WRAP == runTimeResult && + a.length == 1; + } + + function testConstNoWrapEquivalence() public view returns (bool) { + int8 runTimeResult = I8_NEGATIVE_63 << 1; + + return + CONST_NO_WRAP == runTimeResult && + b.length == 126; + } + + function testConstWrapEquivalence() public view returns (bool) { + int8 runTimeResult = I8_POSITIVE_127 << 1; + + return + CONST_WRAP == runTimeResult && + c.length == 2; + } + + function testConstSignChanged() public view returns (bool) { + int16 runTimeResult = I16_POSITIVE_127 << 9; + + return + CONST_SIGN_CHANGED == runTimeResult && + d.length == 512; + } +} +// ---- +// testLiteralWrapEquivalence() -> true +// testConstNoWrapEquivalence() -> true +// testConstWrapEquivalence() -> true +// testConstSignChanged() -> true diff --git a/test/libsolidity/semanticTests/constantEvaluator/shift_unsigned_left_operand_runtime_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/shift_unsigned_left_operand_runtime_equivalence.sol new file mode 100644 index 000000000000..ca054425d331 --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/shift_unsigned_left_operand_runtime_equivalence.sol @@ -0,0 +1,43 @@ +uint256 constant ONE = 1; +uint8 constant U8_64 = 64; +uint8 constant U8_255 = 255; + +contract C { + // Expression with only literals have rational type with unlimited precision, + // so we use a integer constant to force the literal have uint256 (mobileType). + // The whole expression then has type uint256. + uint256 constant LITERAL_WRAP = 2**255 << ONE; // = 0 + uint[LITERAL_WRAP + 1] a; + uint8 constant CONST_NO_WRAP = U8_64 << 1; + uint[CONST_NO_WRAP] b; + uint8 constant CONST_WRAP = U8_255 << 4; // = 240 (1111 0000) + uint[CONST_WRAP] c; + + function testLiteralWrapEquivalence() public view returns (bool) { + uint256 runTimeResult = 2**255 << ONE; + + return + LITERAL_WRAP == runTimeResult && + a.length == runTimeResult + 1; + } + + function testConstNoWrapEquivalence() public view returns (bool) { + uint8 runTimeResult = U8_64 << 1; + + return + CONST_NO_WRAP == runTimeResult && + b.length == runTimeResult; + } + + function testConstWrapEquivalence() public view returns (bool) { + uint8 runTimeResult = U8_255 << 4; + + return + CONST_WRAP == runTimeResult && + c.length == runTimeResult; + } +} +// ---- +// testLiteralWrapEquivalence() -> true +// testConstNoWrapEquivalence() -> true +// testConstWrapEquivalence() -> true diff --git a/test/libsolidity/smtCheckerTests/operators/constant_evaluation_bitwise_not.sol b/test/libsolidity/smtCheckerTests/operators/constant_evaluation_bitwise_not.sol index 54159b18074d..b9418c1f043c 100644 --- a/test/libsolidity/smtCheckerTests/operators/constant_evaluation_bitwise_not.sol +++ b/test/libsolidity/smtCheckerTests/operators/constant_evaluation_bitwise_not.sol @@ -7,3 +7,4 @@ contract C { // ==== // SMTEngine: chc // ---- +// Warning 6031: (186-199): Internal error: Expression undefined for SMT solver. diff --git a/test/libsolidity/syntaxTests/storageLayoutSpecifier/constant_divided_by_its_negation.sol b/test/libsolidity/syntaxTests/storageLayoutSpecifier/constant_divided_by_its_negation.sol index 9f8acbeb6a36..9db4eb809866 100644 --- a/test/libsolidity/syntaxTests/storageLayoutSpecifier/constant_divided_by_its_negation.sol +++ b/test/libsolidity/syntaxTests/storageLayoutSpecifier/constant_divided_by_its_negation.sol @@ -1,4 +1,3 @@ uint constant N = 100; contract C layout at N / ~N {} // ---- -// TypeError 3667: (48-50): Arithmetic error when computing constant value.