Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
38 changes: 35 additions & 3 deletions libsolidity/analysis/ConstantEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IntegerType const*>(&_type);
solAssert(integerType);

solAssert(_value.denominator() == 1);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can we assume that, because it's an IntegerType? Is rational always in reduced/normalized form?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's IntegerType. IIRC, in comptime, it is always normalized. I will confirm this.

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)
Expand Down Expand Up @@ -358,7 +382,11 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation)

if (std::optional<rational> result = evaluateUnaryOperator(_operation.getOperator(), std::get<rational>(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,
Expand Down Expand Up @@ -411,8 +439,12 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
std::get<rational>(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(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ contract C {
// ====
// SMTEngine: chc
// ----
// Warning 6031: (186-199): Internal error: Expression undefined for SMT solver.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
uint constant N = 100;
contract C layout at N / ~N {}
// ----
// TypeError 3667: (48-50): Arithmetic error when computing constant value.