From a38d5495c469e39c2e7a706840c044bdd3350bd6 Mon Sep 17 00:00:00 2001 From: Matheus Aguiar Date: Wed, 15 Apr 2026 03:42:57 -0300 Subject: [PATCH 1/4] Add tests --- ...hift_left_literal_overflow_equivalence.sol | 13 +++++++++++ .../shift_left_runtime_equivalence.sol | 22 ++++++++++++++++++ .../shift_left_signed_cleanup_equivalence.sol | 23 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 test/libsolidity/semanticTests/constantEvaluator/shift_left_literal_overflow_equivalence.sol create mode 100644 test/libsolidity/semanticTests/constantEvaluator/shift_left_runtime_equivalence.sol create mode 100644 test/libsolidity/semanticTests/constantEvaluator/shift_left_signed_cleanup_equivalence.sol diff --git a/test/libsolidity/semanticTests/constantEvaluator/shift_left_literal_overflow_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/shift_left_literal_overflow_equivalence.sol new file mode 100644 index 000000000000..788d27868a82 --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/shift_left_literal_overflow_equivalence.sol @@ -0,0 +1,13 @@ +contract C { + uint constant ONE = 1; + uint constant OVERFLOW = 2**255 << ONE; + + uint[OVERFLOW + 1] a; + + function testEquivalence() public view returns (bool) { + uint runTimeResult = 2**255 << ONE; + return OVERFLOW == runTimeResult; + } +} +// ---- +// testEquivalence() -> true diff --git a/test/libsolidity/semanticTests/constantEvaluator/shift_left_runtime_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/shift_left_runtime_equivalence.sol new file mode 100644 index 000000000000..7626949e3229 --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/shift_left_runtime_equivalence.sol @@ -0,0 +1,22 @@ +uint8 constant UNSIGNED = 16; +int8 constant SIGNED = 16; +contract C { + // should be 0 + uint8 constant public URESULT = UNSIGNED << 5; + int8 constant public SRESULT = SIGNED << 2; + // use as array length so constant values are evaluated in comptime + uint[URESULT + 1] a; + uint[SRESULT] b; + + function testEquivalence() public view returns (bool) { + uint8 runTimeUResult = UNSIGNED << 5; + int8 runTimeSResult = SIGNED << 2; + + return + URESULT == runTimeUResult && + SRESULT == runTimeSResult + ; + } +} +// ---- +// testEquivalence() -> true diff --git a/test/libsolidity/semanticTests/constantEvaluator/shift_left_signed_cleanup_equivalence.sol b/test/libsolidity/semanticTests/constantEvaluator/shift_left_signed_cleanup_equivalence.sol new file mode 100644 index 000000000000..d7db60de6b9c --- /dev/null +++ b/test/libsolidity/semanticTests/constantEvaluator/shift_left_signed_cleanup_equivalence.sol @@ -0,0 +1,23 @@ +contract C { + int8 constant NEG = -63; + int8 constant POS = 63; + // should be 4 -> 1100 0001 << 2 = 0000 0100 + int8 constant public NLEFT_OPERAND = NEG << 2; + // should be -4 -> 0011 1111 << 2 = 1111 1100 + int8 constant public PLEFT_OPERAND = POS << 2; + // use as array length so constant values are evaluated in compTime + uint[NLEFT_OPERAND] a; + uint[PLEFT_OPERAND * -1] b; + + function testEquivalence() public view returns (bool) { + int8 runTimeNResult = NEG << 2; + int8 runTimePResult = POS << 2; + + return + NLEFT_OPERAND == runTimeNResult && + PLEFT_OPERAND == runTimePResult + ; + } +} +// ---- +// testEquivalence() -> true From 1eef2631d2c5b17bc8bde13f8b271d889ec50ecf Mon Sep 17 00:00:00 2001 From: Matheus Aguiar Date: Wed, 15 Apr 2026 03:44:04 -0300 Subject: [PATCH 2/4] Fix ConstantEvaluator not truncating the result of shifts --- libsolidity/analysis/ConstantEvaluator.cpp | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index f094988d9bd6..6573e272ae98 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -275,6 +275,29 @@ TypedValue convertType(TypedValue const& _value, Type const& _type) }, _value.value); } +TypedValue cleanupValue(rational const& _value, Type const& _type) +{ + if (_type.category() != Type::Category::Integer) + return convertType(_value, _type); + + auto const* integerType = dynamic_cast(&_type); + solAssert(integerType); + + bigint integerValue = _value.numerator() / _value.denominator(); + + 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() && (integerValue & sign)) + integerValue = integerValue | ~mask; + + return convertType(rational(integerValue), _type); +} + TypedValue constantToTypedValue(Type const& _type) { if (_type.category() == Type::Category::RationalNumber) @@ -411,7 +434,9 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) std::get(right.value) )) { - TypedValue convertedValue = convertType(*value, *resultType); + TypedValue convertedValue = TokenTraits::isShiftOp(_operation.getOperator()) ? + cleanupValue(*value, *resultType) : + convertType(*value, *resultType); if (!convertedValue.type) m_errorReporter.fatalTypeError( 2643_error, From af47c56334000d588d64681978571056804c722d Mon Sep 17 00:00:00 2001 From: Matheus Aguiar Date: Fri, 17 Apr 2026 15:22:26 -0300 Subject: [PATCH 3/4] fixup! Fix --- libsolidity/analysis/ConstantEvaluator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 6573e272ae98..4e7c986d5277 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -283,6 +283,7 @@ TypedValue cleanupValue(rational const& _value, Type const& _type) auto const* integerType = dynamic_cast(&_type); solAssert(integerType); + solAssert(_value.denominator() == 1); bigint integerValue = _value.numerator() / _value.denominator(); unsigned int numBits = integerType->numBits(); From 86ac94141aff200ca6921865253b8755895a9834 Mon Sep 17 00:00:00 2001 From: Matheus Aguiar Date: Sat, 18 Apr 2026 04:01:28 -0300 Subject: [PATCH 4/4] fixup! Add tests --- ...igned_left_operand_runtime_equivalence.sol | 53 +++++++++++++++++++ ...igned_left_operand_runtime_equivalence.sol | 43 +++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 test/libsolidity/semanticTests/constantEvaluator/shift_signed_left_operand_runtime_equivalence.sol create mode 100644 test/libsolidity/semanticTests/constantEvaluator/shift_unsigned_left_operand_runtime_equivalence.sol 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