Skip to content

Make ConstantEvaluator truncate the result of shift operations - #16597

Open
matheusaaguiar wants to merge 3 commits into
developfrom
ConstantEvaluator-truncate-shiftL-value
Open

Make ConstantEvaluator truncate the result of shift operations#16597
matheusaaguiar wants to merge 3 commits into
developfrom
ConstantEvaluator-truncate-shiftL-value

Conversation

@matheusaaguiar

Copy link
Copy Markdown
Contributor

Fix #16596.
Spotted in #16456 (comment).

))
{
TypedValue convertedValue = convertType(*value, *resultType);
TypedValue convertedValue = TokenTraits::isShiftOp(_operation.getOperator()) ?

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.

I guess it could be done only for left shift.

@matheusaaguiar

Copy link
Copy Markdown
Contributor Author

Not sure about a Changelog entry...

@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch from 86ac941 to a2cb1da Compare April 21, 2026 07:11

@clonker clonker left a comment

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.

Some code structure feedback and questions. Overall looks correct to me. I think BitNot has similar issues. For example ~uint8(0) and ~uint8(-128) both fail constant eval. If you agree, this could be folded into this pr or be a follow up.

Not sure about a Changelog entry...

It's user-facing, isn't it? So imo having an entry is the right move.

Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
Comment thread libsolidity/analysis/ConstantEvaluator.cpp
Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch 3 times, most recently from d62fe3f to a0a1230 Compare April 28, 2026 00:26
{
public:
struct TypedValue
class TypedValue

@matheusaaguiar matheusaaguiar Apr 28, 2026

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.

This refactor was suggested here and it is simple enough that I think it can be slipped in this PR...

// ====
// SMTEngine: chc
// ----
// Warning 6031: (186-199): Internal error: Expression undefined for SMT solver.

@matheusaaguiar matheusaaguiar Apr 28, 2026

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.

This is actually due to an issue in the SMT Checker.
I was worried that it had something to do with the changes in this PR and investigated it.
Apparently, the SMTEncoder visitor was written when only literals were constant evaluated.
Later when support for constant folding of variables was added, the relevant part of SMT Checker was not properly adapted and since there were no tests covering, it went unnoticed.

In short, when visiting the unary operation, SMT Encoder checks whether the result is a rational number, which would indicate a literal expression as operand.
Since literal expressions have unlimited precision, it would then skip visiting the operand.
However, the Constant Evaluator later gained the ability to evaluate other expressions and the SMT Encoder then skips visiting those, assuming they are literals.

For example, using unary minus can also trigger the warning:

contract C {
    int256 constant signedConstant = 42;
    function test() public pure returns (int) {
        return -signedConstant;
    }
}

Before the changes in this PR, this test was not triggering the warning because the bitwise not was generating a value out of range of integer, and thus the Constant Evaluator was not returning a rational number, which would make that the operand was visited.

I will open an issue for this.

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.

The test has gone from the PR, is there an issue now?

@matheusaaguiar matheusaaguiar Aug 19, 2026

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.

It was appearing in the PR only because of the issue I mentioned in the comment and #16654 fixed it.

@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch 2 times, most recently from c0cda95 to a8d529c Compare May 7, 2026 20:54

@clonker clonker left a comment

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.

looks pretty good to me! i'd love to see these additional tests (wouldn't expect anything to fail there but at least then the boundaries are tied down) and the unnecessary string copying gone :)

Comment thread Changelog.md Outdated
Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch from a8d529c to 3165798 Compare May 11, 2026 20:53
Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
Comment thread libsolidity/analysis/ConstantEvaluator.h Outdated
Comment thread libsolidity/analysis/ConstantEvaluator.cpp Outdated
@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch from 3165798 to 1ac7b18 Compare May 12, 2026 22:58
@github-actions github-actions Bot added the stale The issue/PR was marked as stale because it has been open for too long. label May 27, 2026
@matheusaaguiar matheusaaguiar removed the stale The issue/PR was marked as stale because it has been open for too long. label May 27, 2026
@github-actions github-actions Bot added the stale The issue/PR was marked as stale because it has been open for too long. label Jun 11, 2026
@matheusaaguiar matheusaaguiar removed the stale The issue/PR was marked as stale because it has been open for too long. label Jun 15, 2026
@argotorg argotorg deleted a comment from github-actions Bot Jun 15, 2026
@argotorg argotorg deleted a comment from github-actions Bot Jun 15, 2026
@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch 3 times, most recently from 81c4f51 to 4f76df6 Compare June 17, 2026 12:54
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

This pull request is stale because it has been open for 14 days with no activity.
It will be closed in 7 days unless the stale label is removed.

@github-actions github-actions Bot added the stale The issue/PR was marked as stale because it has been open for too long. label Jul 2, 2026
@cameel cameel removed the stale The issue/PR was marked as stale because it has been open for too long. label Jul 5, 2026
@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch from 4f76df6 to dfcb77b Compare August 3, 2026 21:02

@clonker clonker left a comment

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.

Hey, sorry for not getting back to this any sooner.

There is no test covering left shift by >= type width with a small type (e.g. U8_64 << 8); the wrap tests all shift by less than the width except the full-256-bit case, right? If so, I think it should be added.

There's also another small inconsistency with how the runtime behaves wrt large shift constants:

uint256 constant ONE = 1;
uint256 constant A = ONE << 300;   // exact 2^300 fits the 4096-bit precision cap
uint256 constant B = ONE << 5000;  // exact 2^5000 exceeds it

contract C {
    uint[A + 1] a;                                        // needs compile-time evaluation
    uint[B + 1] b;                                        // needs compile-time evaluation
    function f() public pure returns (uint256) { return B; }  // runtime only
}

the a case compiles under this PR, the b case doesn't. It's probably not extremely important but still. :)

Comment thread libsolidity/analysis/ConstantEvaluator.h
Comment on lines +1 to +3
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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.

Suggested change
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

int8 constant I8_NEGATIVE_63 = -63;
int8 constant I8_POSITIVE_127 = 127;
int8 constant I8_NEGATIVE_128 = -128;
int16 constant I16_POSITIVE_127 = 127;

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.

Suggested change
int16 constant I16_POSITIVE_127 = 127;

unused

Comment on lines +1 to +2
int8 constant I8_NEGATIVE_128 = -128; // 1000 0000
int8 constant I8_POSITIVE_127 = 127; // 0111 1111

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.

Suggested change
int8 constant I8_NEGATIVE_128 = -128; // 1000 0000
int8 constant I8_POSITIVE_127 = 127; // 0111 1111

unused

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.

in shift right no wrapping should occur, so i find _WRAP a bit misleading in the variable names

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.

in shift right no wrapping should occur, so i find _WRAP a bit misleading in the variable names

@matheusaaguiar
matheusaaguiar force-pushed the ConstantEvaluator-truncate-shiftL-value branch from dfcb77b to 959148a Compare August 21, 2026 05:20
@matheusaaguiar

Copy link
Copy Markdown
Contributor Author

the a case compiles under this PR, the b case doesn't. It's probably not extremely important but still. :)

Yes, in b case the constant evaluator returns "empty" or no value because it overflows the 4096 bit width, however codegen executes outputting 0 as a result.
I modified the code so constant evaluator returns 0 as well. I am running some tests to check there's no problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ConstantEvaluator does not truncate the result of left shift operations

4 participants