ethereum / solidity

Solidity, the Smart Contract Programming Language
https://soliditylang.org
GNU General Public License v3.0
23.39k stars 5.78k forks source link

Insufficient check for uninitialized storage pointer access #14021

Open qwaz-theori opened 1 year ago

qwaz-theori commented 1 year ago

Description

contract Foo {
    struct Hi {
        uint256 hello;
    }

    function foo() internal returns (Hi storage ret) {
        ret = ret;
        ret.hello = 123;
    }
}

This code accesses ret storage pointer without initializing it. It should not compile, but the Solidity compiler accepts this code.

Environment

Steps to Reproduce

Save the above file as test.sol and run solc test.sol.

Without ret = ret; line, the expected warning message is printed:

Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
--> test.sol

Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.8.18;"
--> test.sol

Error: This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
 --> test.sol:6:38:
  |
6 |     function foo() internal returns (Hi storage ret) {
  |                                      ^^^^^^^^^^^^^^

Error: This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour.
 --> test.sol:7:9:
  |
7 |         ret.hello = 123;
  |         ^^^
Note: The variable was declared here.
 --> test.sol:6:38:
  |
6 |     function foo() internal returns (Hi storage ret) {
  |                                      ^^^^^^^^^^^^^^
cameel commented 1 year ago

Yeah, looks like a bug. The compiler normally would not let you use ret in an expression before it is initialized. After the assignment it would stop complaining. Here it's wrongly assuming it has already been assigned when processing the right-hand side.

cameel commented 1 year ago

After discussing it in the team it turns out that it was at least partially intentional. You can suppress a warning about an unused return variable by assigning it to itself - any expression involving the variable on the right-hand side of the assignment will do this. However, this was done way back, before you could assign to storage variables in inline assembly, which would be a better and more explicit way to silence it. The storage case is potentially more dangerous than other cases so we're still going to consider it a bug and disallow it.

ekpyron commented 3 weeks ago

The storage case is potentially more dangerous than other cases so we're still going to consider it a bug and disallow it.

Just to clarify: disallowing this right a way would still definitely be breaking. Our conclusion in the end was that we start emitting a warning for this now (which can then be silenced by an assembly assignment), and then turn that into an error in 0.9, right?

cameel commented 2 weeks ago

Yes.