Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2022-03-16 15:41:37 +01:00
commit 0d96c5c8a5
542 changed files with 6696 additions and 2399 deletions

View File

@ -504,28 +504,14 @@ defaults:
binary_type: solcjs binary_type: solcjs
compile_only: 1 compile_only: 1
nodejs_version: '14' nodejs_version: '14'
- job_native_compile_ext_gnosis: &job_native_compile_ext_gnosis
<<: *workflow_ubuntu2004_static
name: t_native_compile_ext_gnosis
project: gnosis
binary_type: native
compile_only: 1
nodejs_version: '14'
- job_native_test_ext_gnosis: &job_native_test_ext_gnosis - job_native_test_ext_gnosis: &job_native_test_ext_gnosis
<<: *workflow_emscripten <<: *workflow_ubuntu2004_static
name: t_native_test_ext_gnosis name: t_native_test_ext_gnosis
project: gnosis project: gnosis
binary_type: native binary_type: native
# NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '12' nodejs_version: '16'
- job_native_test_ext_gnosis_v2: &job_native_test_ext_gnosis_v2
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_gnosis_v2
project: gnosis-v2
binary_type: native
# NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1").
nodejs_version: '12'
- job_native_test_ext_zeppelin: &job_native_test_ext_zeppelin - job_native_test_ext_zeppelin: &job_native_test_ext_zeppelin
<<: *workflow_ubuntu2004_static <<: *workflow_ubuntu2004_static
name: t_native_test_ext_zeppelin name: t_native_test_ext_zeppelin
@ -1065,6 +1051,20 @@ jobs:
t_ubu_release_cli: &t_ubu_release_cli t_ubu_release_cli: &t_ubu_release_cli
<<: *t_ubu_cli <<: *t_ubu_cli
t_ubu_locale:
<<: *base_ubuntu2004_small
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install all locales
command: |
apt update --assume-yes
apt install locales-all --assume-yes --no-install-recommends
- run: test/localeTest.sh build/solc/solc
- gitter_notify_failure_unless_pr
t_ubu_asan_cli: t_ubu_asan_cli:
# Runs slightly faster on medium but we only run it nightly so efficiency matters more. # Runs slightly faster on medium but we only run it nightly so efficiency matters more.
<<: *base_ubuntu2004_small <<: *base_ubuntu2004_small
@ -1449,6 +1449,7 @@ workflows:
# Ubuntu build and tests # Ubuntu build and tests
- b_ubu: *workflow_trigger_on_tags - b_ubu: *workflow_trigger_on_tags
- t_ubu_cli: *workflow_ubuntu2004 - t_ubu_cli: *workflow_ubuntu2004
- t_ubu_locale: *workflow_ubuntu2004
- t_ubu_soltest_all: *workflow_ubuntu2004 - t_ubu_soltest_all: *workflow_ubuntu2004
- t_ubu_soltest_enforce_yul: *workflow_ubuntu2004 - t_ubu_soltest_enforce_yul: *workflow_ubuntu2004
- b_ubu_clang: *workflow_trigger_on_tags - b_ubu_clang: *workflow_trigger_on_tags
@ -1466,12 +1467,8 @@ workflows:
- t_ems_ext_hardhat: *workflow_emscripten - t_ems_ext_hardhat: *workflow_emscripten
- t_ems_ext: *job_ems_compile_ext_colony - t_ems_ext: *job_ems_compile_ext_colony
- t_ems_ext: *job_native_compile_ext_gnosis
# FIXME: Gnosis tests are pretty flaky right now. They often fail on CircleCI due to random ProviderError - t_ems_ext: *job_native_test_ext_gnosis
# and there are also other less frequent problems. See https://github.com/gnosis/safe-contracts/issues/216.
#-t_ems_ext: *job_native_test_ext_gnosis
- t_ems_ext: *job_native_test_ext_gnosis_v2
- t_ems_ext: *job_native_test_ext_zeppelin - t_ems_ext: *job_native_test_ext_zeppelin
- t_ems_ext: *job_native_test_ext_ens - t_ems_ext: *job_native_test_ext_ens
- t_ems_ext: *job_native_test_ext_trident - t_ems_ext: *job_native_test_ext_trident
@ -1488,8 +1485,7 @@ workflows:
<<: *workflow_trigger_on_tags <<: *workflow_trigger_on_tags
requires: requires:
- t_ems_compile_ext_colony - t_ems_compile_ext_colony
- t_native_compile_ext_gnosis - t_native_test_ext_gnosis
- t_native_test_ext_gnosis_v2
- t_native_test_ext_zeppelin - t_native_test_ext_zeppelin
- t_native_test_ext_ens - t_native_test_ext_ens
- t_native_test_ext_trident - t_native_test_ext_trident

View File

@ -21,7 +21,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.8.13") set(PROJECT_VERSION "0.8.14")
# OSX target needed in order to support std::visit # OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)

View File

@ -10,18 +10,46 @@ Breaking changes:
* General: The identifier ``basefee`` is a reserved identifier in Yul for all EVM versions. * General: The identifier ``basefee`` is a reserved identifier in Yul for all EVM versions.
### 0.8.13 (unreleased) ### 0.8.14 (unreleased)
Language Features: Language Features:
* General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
Compiler Features: Compiler Features:
* JSON-AST: Added selector field for errors and events.
Bugfixes: Bugfixes:
### 0.8.13 (2022-03-16)
Important Bugfixes:
* Code Generator: Correctly encode literals used in ``abi.encodeCall`` in place of fixed bytes arguments.
Language Features:
* General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
* General: ``using M for Type;`` is allowed at file level and ``M`` can now also be a brace-enclosed list of free functions or library functions.
* General: ``using ... for T global;`` is allowed at file level where the user-defined type ``T`` has been defined, resulting in the effect of the statement being available everywhere ``T`` is available.
Compiler Features:
* Commandline Interface: Allow the use of ``--via-ir`` in place of ``--experimental-via-ir``.
* Compilation via Yul IR is no longer marked as experimental.
* JSON-AST: Added selector field for errors and events.
* LSP: Implements goto-definition.
* Peephole Optimizer: Optimize comparisons in front of conditional jumps and conditional jumps across a single unconditional jump.
* Yul EVM Code Transform: Avoid unnecessary ``pop``s on terminating control flow.
* Yul Optimizer: Remove ``sstore`` and ``mstore`` operations that are never read from.
Bugfixes:
* General: Fix internal error for locales with unusual capitalization rules. Locale set in the environment is now completely ignored.
* Type Checker: Fix incorrect type checker errors when importing overloaded functions.
* Yul IR Code Generation: Optimize embedded creation code with correct settings. This fixes potential mismatches between the constructor code of a contract compiled in isolation and the bytecode in ``type(C).creationCode``, resp. the bytecode used for ``new C(...)``.
### 0.8.12 (2022-02-16) ### 0.8.12 (2022-02-16)
Language Features: Language Features:

View File

@ -48,3 +48,11 @@ function(detect_stray_source_files FILELIST DIRECTORY)
message(SEND_ERROR "The following source files are present but are not compiled: ${sources}") message(SEND_ERROR "The following source files are present but are not compiled: ${sources}")
endif() endif()
endfunction(detect_stray_source_files) endfunction(detect_stray_source_files)
# CreateExportedFunctionsForEMSDK(OUTPUT_VARIABLE Symbol1 Symbol2 ... SymbolN)
function(CreateExportedFunctionsForEMSDK OUTPUT_VARIABLE)
list(TRANSFORM ARGN PREPEND "\"_")
list(TRANSFORM ARGN APPEND "\"")
list(JOIN ARGN "," ARGN)
set(${OUTPUT_VARIABLE} "[${ARGN}]" PARENT_SCOPE)
endfunction()

View File

@ -45,10 +45,10 @@ Solidity language without a compiler change.
pragma solidity >=0.4.16 <0.9.0; pragma solidity >=0.4.16 <0.9.0;
library GetCode { library GetCode {
function at(address _addr) public view returns (bytes memory code) { function at(address addr) public view returns (bytes memory code) {
assembly { assembly {
// retrieve the size of the code, this needs assembly // retrieve the size of the code, this needs assembly
let size := extcodesize(_addr) let size := extcodesize(addr)
// allocate output byte array - this could also be done without assembly // allocate output byte array - this could also be done without assembly
// by using code = new bytes(size) // by using code = new bytes(size)
code := mload(0x40) code := mload(0x40)
@ -57,7 +57,7 @@ Solidity language without a compiler change.
// store length in memory // store length in memory
mstore(code, size) mstore(code, size)
// actually retrieve the code, this needs assembly // actually retrieve the code, this needs assembly
extcodecopy(_addr, add(code, 0x20), 0, size) extcodecopy(addr, add(code, 0x20), 0, size)
} }
} }
} }
@ -74,43 +74,43 @@ efficient code, for example:
library VectorSum { library VectorSum {
// This function is less efficient because the optimizer currently fails to // This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access. // remove the bounds checks in array access.
function sumSolidity(uint[] memory _data) public pure returns (uint sum) { function sumSolidity(uint[] memory data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i) for (uint i = 0; i < data.length; ++i)
sum += _data[i]; sum += data[i];
} }
// We know that we only access the array in bounds, so we can avoid the check. // We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the // 0x20 needs to be added to an array because the first slot contains the
// array length. // array length.
function sumAsm(uint[] memory _data) public pure returns (uint sum) { function sumAsm(uint[] memory data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i) { for (uint i = 0; i < data.length; ++i) {
assembly { assembly {
sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) sum := add(sum, mload(add(add(data, 0x20), mul(i, 0x20))))
} }
} }
} }
// Same as above, but accomplish the entire code within inline assembly. // Same as above, but accomplish the entire code within inline assembly.
function sumPureAsm(uint[] memory _data) public pure returns (uint sum) { function sumPureAsm(uint[] memory data) public pure returns (uint sum) {
assembly { assembly {
// Load the length (first 32 bytes) // Load the length (first 32 bytes)
let len := mload(_data) let len := mload(data)
// Skip over the length field. // Skip over the length field.
// //
// Keep temporary variable so it can be incremented in place. // Keep temporary variable so it can be incremented in place.
// //
// NOTE: incrementing _data would result in an unusable // NOTE: incrementing data would result in an unusable
// _data variable after this assembly block // data variable after this assembly block
let data := add(_data, 0x20) let dataElementLocation := add(data, 0x20)
// Iterate until the bound is not met. // Iterate until the bound is not met.
for for
{ let end := add(data, mul(len, 0x20)) } { let end := add(dataElementLocation, mul(len, 0x20)) }
lt(data, end) lt(dataElementLocation, end)
{ data := add(data, 0x20) } { data := add(dataElementLocation, 0x20) }
{ {
sum := add(sum, mload(data)) sum := add(sum, mload(dataElementLocation))
} }
} }
} }

View File

@ -1,4 +1,15 @@
[ [
{
"uid": "SOL-2022-1",
"name": "AbiEncodeCallLiteralAsFixedBytesBug",
"summary": "Literals used for a fixed length bytes parameter in ``abi.encodeCall`` were encoded incorrectly.",
"description": "For the encoding, the compiler only considered the types of the expressions in the second argument of ``abi.encodeCall`` itself, but not the parameter types of the function given as first argument. In almost all cases the abi encoding of the type of the expression matches the abi encoding of the parameter type of the given function. This is because the type checker ensures the expression is implicitly convertible to the respective parameter type. However this is not true for number literals used for fixed bytes types shorter than 32 bytes, nor for string literals used for any fixed bytes type. Number literals were encoded as numbers instead of being shifted to become left-aligned. String literals were encoded as dynamically sized memory strings instead of being converted to a left-aligned bytes value.",
"link": "https://blog.soliditylang.org/2022/03/16/encodecall-bug/",
"introduced": "0.8.11",
"fixed": "0.8.13",
"severity": "very low"
},
{ {
"uid": "SOL-2021-4", "uid": "SOL-2021-4",
"name": "UserDefinedValueTypesBug", "name": "UserDefinedValueTypesBug",
@ -8,7 +19,6 @@
"introduced": "0.8.8", "introduced": "0.8.8",
"fixed": "0.8.9", "fixed": "0.8.9",
"severity": "very low" "severity": "very low"
}, },
{ {
"uid": "SOL-2021-3", "uid": "SOL-2021-3",

View File

@ -1549,13 +1549,21 @@
"released": "2021-11-09" "released": "2021-11-09"
}, },
"0.8.11": { "0.8.11": {
"bugs": [], "bugs": [
"AbiEncodeCallLiteralAsFixedBytesBug"
],
"released": "2021-12-20" "released": "2021-12-20"
}, },
"0.8.12": { "0.8.12": {
"bugs": [], "bugs": [
"AbiEncodeCallLiteralAsFixedBytesBug"
],
"released": "2022-02-16" "released": "2022-02-16"
}, },
"0.8.13": {
"bugs": [],
"released": "2022-03-16"
},
"0.8.2": { "0.8.2": {
"bugs": [ "bugs": [
"SignedImmutables", "SignedImmutables",

View File

@ -163,9 +163,9 @@ restrictions highly readable.
// prepend a check that only passes // prepend a check that only passes
// if the function is called from // if the function is called from
// a certain address. // a certain address.
modifier onlyBy(address _account) modifier onlyBy(address account)
{ {
if (msg.sender != _account) if (msg.sender != account)
revert Unauthorized(); revert Unauthorized();
// Do not forget the "_;"! It will // Do not forget the "_;"! It will
// be replaced by the actual function // be replaced by the actual function
@ -173,17 +173,17 @@ restrictions highly readable.
_; _;
} }
/// Make `_newOwner` the new owner of this /// Make `newOwner` the new owner of this
/// contract. /// contract.
function changeOwner(address _newOwner) function changeOwner(address newOwner)
public public
onlyBy(owner) onlyBy(owner)
{ {
owner = _newOwner; owner = newOwner;
} }
modifier onlyAfter(uint _time) { modifier onlyAfter(uint time) {
if (block.timestamp < _time) if (block.timestamp < time)
revert TooEarly(); revert TooEarly();
_; _;
} }
@ -205,21 +205,21 @@ restrictions highly readable.
// refunded, but only after the function body. // refunded, but only after the function body.
// This was dangerous before Solidity version 0.4.0, // This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`. // where it was possible to skip the part after `_;`.
modifier costs(uint _amount) { modifier costs(uint amount) {
if (msg.value < _amount) if (msg.value < amount)
revert NotEnoughEther(); revert NotEnoughEther();
_; _;
if (msg.value > _amount) if (msg.value > amount)
payable(msg.sender).transfer(msg.value - _amount); payable(msg.sender).transfer(msg.value - amount);
} }
function forceOwnerChange(address _newOwner) function forceOwnerChange(address newOwner)
public public
payable payable
costs(200 ether) costs(200 ether)
{ {
owner = _newOwner; owner = newOwner;
// just some example condition // just some example condition
if (uint160(owner) & 0 == 1) if (uint160(owner) & 0 == 1)
// This did not refund for Solidity // This did not refund for Solidity
@ -315,8 +315,8 @@ function finishes.
uint public creationTime = block.timestamp; uint public creationTime = block.timestamp;
modifier atStage(Stages _stage) { modifier atStage(Stages stage_) {
if (stage != _stage) if (stage != stage_)
revert FunctionInvalidAtThisStage(); revert FunctionInvalidAtThisStage();
_; _;
} }

View File

@ -41,14 +41,14 @@ Not all types for constants and immutables are implemented at this time. The onl
uint immutable maxBalance; uint immutable maxBalance;
address immutable owner = msg.sender; address immutable owner = msg.sender;
constructor(uint _decimals, address _reference) { constructor(uint decimals_, address ref) {
decimals = _decimals; decimals = decimals_;
// Assignments to immutables can even access the environment. // Assignments to immutables can even access the environment.
maxBalance = _reference.balance; maxBalance = ref.balance;
} }
function isBalanceTooHigh(address _other) public view returns (bool) { function isBalanceTooHigh(address other) public view returns (bool) {
return _other.balance > maxBalance; return other.balance > maxBalance;
} }
} }

View File

@ -48,7 +48,7 @@ This means that cyclic creation dependencies are impossible.
// This is the constructor which registers the // This is the constructor which registers the
// creator and the assigned name. // creator and the assigned name.
constructor(bytes32 _name) { constructor(bytes32 name_) {
// State variables are accessed via their name // State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can // and not via e.g. `this.owner`. Functions can
// be accessed directly or through `this.f`, // be accessed directly or through `this.f`,
@ -65,7 +65,7 @@ This means that cyclic creation dependencies are impossible.
// no real way to verify that. // no real way to verify that.
// This does not create a new contract. // This does not create a new contract.
creator = TokenCreator(msg.sender); creator = TokenCreator(msg.sender);
name = _name; name = name_;
} }
function changeName(bytes32 newName) public { function changeName(bytes32 newName) public {

View File

@ -80,18 +80,18 @@ four indexed arguments rather than three.
contract ClientReceipt { contract ClientReceipt {
event Deposit( event Deposit(
address indexed _from, address indexed from,
bytes32 indexed _id, bytes32 indexed id,
uint _value uint value
); );
function deposit(bytes32 _id) public payable { function deposit(bytes32 id) public payable {
// Events are emitted using `emit`, followed by // Events are emitted using `emit`, followed by
// the name of the event and the arguments // the name of the event and the arguments
// (if any) in parentheses. Any such invocation // (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from // (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`. // the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, _id, msg.value); emit Deposit(msg.sender, id, msg.value);
} }
} }
@ -126,9 +126,9 @@ The output of the above looks like the following (trimmed):
{ {
"returnValues": { "returnValues": {
"_from": "0x1111…FFFFCCCC", "from": "0x1111…FFFFCCCC",
"_id": "0x50…sd5adb20", "id": "0x50…sd5adb20",
"_value": "0x420042" "value": "0x420042"
}, },
"raw": { "raw": {
"data": "0x7f…91385", "data": "0x7f…91385",

View File

@ -72,8 +72,8 @@ if they are marked ``virtual``. For details, please see
registeredAddresses[msg.sender] = true; registeredAddresses[msg.sender] = true;
} }
function changePrice(uint _price) public onlyOwner { function changePrice(uint price_) public onlyOwner {
price = _price; price = price_;
} }
} }

View File

@ -17,17 +17,17 @@ that call them, similar to internal library functions.
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0; pragma solidity >=0.7.1 <0.9.0;
function sum(uint[] memory _arr) pure returns (uint s) { function sum(uint[] memory arr) pure returns (uint s) {
for (uint i = 0; i < _arr.length; i++) for (uint i = 0; i < arr.length; i++)
s += _arr[i]; s += arr[i];
} }
contract ArrayExample { contract ArrayExample {
bool found; bool found;
function f(uint[] memory _arr) public { function f(uint[] memory arr) public {
// This calls the free function internally. // This calls the free function internally.
// The compiler will add its code to the contract. // The compiler will add its code to the contract.
uint s = sum(_arr); uint s = sum(arr);
require(s >= 10); require(s >= 10);
found = true; found = true;
} }
@ -65,8 +65,8 @@ with two integers, you would use something like the following:
contract Simple { contract Simple {
uint sum; uint sum;
function taker(uint _a, uint _b) public { function taker(uint a, uint b) public {
sum = _a + _b; sum = a + b;
} }
} }
@ -99,13 +99,13 @@ two integers passed as function parameters, then you use something like:
pragma solidity >=0.4.16 <0.9.0; pragma solidity >=0.4.16 <0.9.0;
contract Simple { contract Simple {
function arithmetic(uint _a, uint _b) function arithmetic(uint a, uint b)
public public
pure pure
returns (uint sum, uint product) returns (uint sum, uint product)
{ {
sum = _a + _b; sum = a + b;
product = _a * _b; product = a * b;
} }
} }
@ -126,12 +126,12 @@ statement:
pragma solidity >=0.4.16 <0.9.0; pragma solidity >=0.4.16 <0.9.0;
contract Simple { contract Simple {
function arithmetic(uint _a, uint _b) function arithmetic(uint a, uint b)
public public
pure pure
returns (uint sum, uint product) returns (uint sum, uint product)
{ {
return (_a + _b, _a * _b); return (a + b, a * b);
} }
} }
@ -363,7 +363,7 @@ Fallback Function
----------------- -----------------
A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]`` A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]``
or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)`` or ``fallback (bytes calldata input) external [payable] returns (bytes memory output)``
(both without the ``function`` keyword). (both without the ``function`` keyword).
This function must have ``external`` visibility. A fallback function can be virtual, can override This function must have ``external`` visibility. A fallback function can be virtual, can override
and can have modifiers. and can have modifiers.
@ -374,8 +374,8 @@ all and there is no :ref:`receive Ether function <receive-ether-function>`.
The fallback function always receives data, but in order to also receive Ether The fallback function always receives data, but in order to also receive Ether
it must be marked ``payable``. it must be marked ``payable``.
If the version with parameters is used, ``_input`` will contain the full data sent to the contract If the version with parameters is used, ``input`` will contain the full data sent to the contract
(equal to ``msg.data``) and can return data in ``_output``. The returned data will not be (equal to ``msg.data``) and can return data in ``output``. The returned data will not be
ABI-encoded. Instead it will be returned without modifications (not even padding). ABI-encoded. Instead it will be returned without modifications (not even padding).
In the worst case, if a payable fallback function is also used in In the worst case, if a payable fallback function is also used in
@ -398,7 +398,7 @@ operations as long as there is enough gas passed on to it.
for the function selector and then for the function selector and then
you can use ``abi.decode`` together with the array slice syntax to you can use ``abi.decode`` together with the array slice syntax to
decode ABI-encoded data: decode ABI-encoded data:
``(c, d) = abi.decode(_input[4:], (uint256, uint256));`` ``(c, d) = abi.decode(input[4:], (uint256, uint256));``
Note that this should only be used as a last resort and Note that this should only be used as a last resort and
proper functions should be used instead. proper functions should be used instead.
@ -487,13 +487,13 @@ The following example shows overloading of the function
pragma solidity >=0.4.16 <0.9.0; pragma solidity >=0.4.16 <0.9.0;
contract A { contract A {
function f(uint _in) public pure returns (uint out) { function f(uint value) public pure returns (uint out) {
out = _in; out = value;
} }
function f(uint _in, bool _really) public pure returns (uint out) { function f(uint value, bool really) public pure returns (uint out) {
if (_really) if (really)
out = _in; out = value;
} }
} }
@ -507,12 +507,12 @@ externally visible functions differ by their Solidity types but not by their ext
// This will not compile // This will not compile
contract A { contract A {
function f(B _in) public pure returns (B out) { function f(B value) public pure returns (B out) {
out = _in; out = value;
} }
function f(address _in) public pure returns (address out) { function f(address value) public pure returns (address out) {
out = _in; out = value;
} }
} }
@ -540,12 +540,12 @@ candidate, resolution fails.
pragma solidity >=0.4.16 <0.9.0; pragma solidity >=0.4.16 <0.9.0;
contract A { contract A {
function f(uint8 _in) public pure returns (uint8 out) { function f(uint8 val) public pure returns (uint8 out) {
out = _in; out = val;
} }
function f(uint256 _in) public pure returns (uint256 out) { function f(uint256 val) public pure returns (uint256 out) {
out = _in; out = val;
} }
} }

View File

@ -421,8 +421,8 @@ equivalent to ``constructor() {}``. For example:
abstract contract A { abstract contract A {
uint public a; uint public a;
constructor(uint _a) { constructor(uint a_) {
a = _a; a = a_;
} }
} }
@ -459,7 +459,7 @@ derived contracts need to specify all of them. This can be done in two ways:
contract Base { contract Base {
uint x; uint x;
constructor(uint _x) { x = _x; } constructor(uint x_) { x = x_; }
} }
// Either directly specify in the inheritance list... // Either directly specify in the inheritance list...
@ -469,12 +469,12 @@ derived contracts need to specify all of them. This can be done in two ways:
// or through a "modifier" of the derived constructor. // or through a "modifier" of the derived constructor.
contract Derived2 is Base { contract Derived2 is Base {
constructor(uint _y) Base(_y * _y) {} constructor(uint y) Base(y * y) {}
} }
One way is directly in the inheritance list (``is Base(7)``). The other is in One way is directly in the inheritance list (``is Base(7)``). The other is in
the way a modifier is invoked as part of the way a modifier is invoked as part of
the derived constructor (``Base(_y * _y)``). The first way to the derived constructor (``Base(y * y)``). The first way to
do it is more convenient if the constructor argument is a do it is more convenient if the constructor argument is a
constant and defines the behaviour of the contract or constant and defines the behaviour of the contract or
describes it. The second way has to be used if the describes it. The second way has to be used if the

View File

@ -10,7 +10,7 @@ Interfaces are similar to abstract contracts, but they cannot have any functions
There are further restrictions: There are further restrictions:
- They cannot inherit from other contracts, but they can inherit from other interfaces. - They cannot inherit from other contracts, but they can inherit from other interfaces.
- All declared functions must be external. - All declared functions must be external in the interface, even if they are public in the contract.
- They cannot declare a constructor. - They cannot declare a constructor.
- They cannot declare state variables. - They cannot declare state variables.
- They cannot declare modifiers. - They cannot declare modifiers.

View File

@ -146,16 +146,16 @@ custom types without the overhead of external function calls:
r.limbs[0] = x; r.limbs[0] = x;
} }
function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) { function add(bigint memory a, bigint memory b) internal pure returns (bigint memory r) {
r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); r.limbs = new uint[](max(a.limbs.length, b.limbs.length));
uint carry = 0; uint carry = 0;
for (uint i = 0; i < r.limbs.length; ++i) { for (uint i = 0; i < r.limbs.length; ++i) {
uint a = limb(_a, i); uint limbA = limb(a, i);
uint b = limb(_b, i); uint limbB = limb(b, i);
unchecked { unchecked {
r.limbs[i] = a + b + carry; r.limbs[i] = limbA + limbB + carry;
if (a + b < a || (a + b == type(uint).max && carry > 0)) if (limbA + limbB < limbA || (limbA + limbB == type(uint).max && carry > 0))
carry = 1; carry = 1;
else else
carry = 0; carry = 0;
@ -172,8 +172,8 @@ custom types without the overhead of external function calls:
} }
} }
function limb(bigint memory _a, uint _limb) internal pure returns (uint) { function limb(bigint memory a, uint index) internal pure returns (uint) {
return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; return index < a.limbs.length ? a.limbs[index] : 0;
} }
function max(uint a, uint b) private pure returns (uint) { function max(uint a, uint b) private pure returns (uint) {

View File

@ -6,71 +6,96 @@
Using For Using For
********* *********
The directive ``using A for B;`` can be used to attach library The directive ``using A for B;`` can be used to attach
functions (from the library ``A``) to any type (``B``) functions (``A``) as member functions to any type (``B``).
in the context of a contract.
These functions will receive the object they are called on These functions will receive the object they are called on
as their first parameter (like the ``self`` variable in Python). as their first parameter (like the ``self`` variable in Python).
The effect of ``using A for *;`` is that the functions from It is valid either at file level or inside a contract,
the library ``A`` are attached to *any* type. at contract level.
In both situations, *all* functions in the library are attached, The first part, ``A``, can be one of:
- a list of file-level or library functions (``using {f, g, h, L.t} for uint;``) -
only those functions will be attached to the type.
- the name of a library (``using L for uint;``) -
all functions (both public and internal ones) of the library are attached to the type
At file level, the second part, ``B``, has to be an explicit type (without data location specifier).
Inside contracts, you can also use ``using L for *;``,
which has the effect that all functions of the library ``L``
are attached to *all* types.
If you specify a library, *all* functions in the library are attached,
even those where the type of the first parameter does not even those where the type of the first parameter does not
match the type of the object. The type is checked at the match the type of the object. The type is checked at the
point the function is called and function overload point the function is called and function overload
resolution is performed. resolution is performed.
If you use a list of functions (``using {f, g, h, L.t} for uint;``),
then the type (``uint``) has to be implicitly convertible to the
first parameter of each of these functions. This check is
performed even if none of these functions are called.
The ``using A for B;`` directive is active only within the current The ``using A for B;`` directive is active only within the current
contract, including within all of its functions, and has no effect scope (either the contract or the current module/source unit),
outside of the contract in which it is used. The directive including within all of its functions, and has no effect
may only be used inside a contract, not inside any of its functions. outside of the contract or module in which it is used.
When the directive is used at file level and applied to a
user-defined type which was defined at file level in the same file,
the word ``global`` can be added at the end. This will have the
effect that the functions are attached to the type everywhere
the type is available (including other files), not only in the
scope of the using statement.
Let us rewrite the set example from the Let us rewrite the set example from the
:ref:`libraries` in this way: :ref:`libraries` section in this way, using file-level functions
instead of library functions.
.. code-block:: solidity .. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0; pragma solidity ^0.8.13;
// This is the same code as before, just without comments
struct Data { mapping(uint => bool) flags; } struct Data { mapping(uint => bool) flags; }
// Now we attach functions to the type.
// The attached functions can be used throughout the rest of the module.
// If you import the module, you have to
// repeat the using directive there, for example as
// import "flags.sol" as Flags;
// using {Flags.insert, Flags.remove, Flags.contains}
// for Flags.Data;
using {insert, remove, contains} for Data;
library Set { function insert(Data storage self, uint value)
function insert(Data storage self, uint value) returns (bool)
public {
returns (bool) if (self.flags[value])
{ return false; // already there
if (self.flags[value]) self.flags[value] = true;
return false; // already there return true;
self.flags[value] = true; }
return true;
}
function remove(Data storage self, uint value) function remove(Data storage self, uint value)
public returns (bool)
returns (bool) {
{ if (!self.flags[value])
if (!self.flags[value]) return false; // not there
return false; // not there self.flags[value] = false;
self.flags[value] = false; return true;
return true; }
}
function contains(Data storage self, uint value) function contains(Data storage self, uint value)
public public
view view
returns (bool) returns (bool)
{ {
return self.flags[value]; return self.flags[value];
}
} }
contract C { contract C {
using Set for Data; // this is the crucial change
Data knownValues; Data knownValues;
function register(uint value) public { function register(uint value) public {
@ -82,12 +107,13 @@ Let us rewrite the set example from the
} }
} }
It is also possible to extend elementary types in that way: It is also possible to extend built-in types in that way.
In this example, we will use a library.
.. code-block:: solidity .. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.8 <0.9.0; pragma solidity ^0.8.13;
library Search { library Search {
function indexOf(uint[] storage self, uint value) function indexOf(uint[] storage self, uint value)
@ -100,22 +126,22 @@ It is also possible to extend elementary types in that way:
return type(uint).max; return type(uint).max;
} }
} }
using Search for uint[];
contract C { contract C {
using Search for uint[];
uint[] data; uint[] data;
function append(uint value) public { function append(uint value) public {
data.push(value); data.push(value);
} }
function replace(uint _old, uint _new) public { function replace(uint from, uint to) public {
// This performs the library function call // This performs the library function call
uint index = data.indexOf(_old); uint index = data.indexOf(from);
if (index == type(uint).max) if (index == type(uint).max)
data.push(_new); data.push(to);
else else
data[index] = _new; data[index] = to;
} }
} }

View File

@ -47,6 +47,7 @@ FixedBytes:
'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'; 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32';
For: 'for'; For: 'for';
Function: 'function'; Function: 'function';
Global: 'global'; // not a real keyword
Hex: 'hex'; Hex: 'hex';
If: 'if'; If: 'if';
Immutable: 'immutable'; Immutable: 'immutable';

View File

@ -12,6 +12,7 @@ options { tokenVocab=SolidityLexer; }
sourceUnit: ( sourceUnit: (
pragmaDirective pragmaDirective
| importDirective | importDirective
| usingDirective
| contractDefinition | contractDefinition
| interfaceDefinition | interfaceDefinition
| libraryDefinition | libraryDefinition
@ -311,10 +312,10 @@ errorDefinition:
Semicolon; Semicolon;
/** /**
* Using directive to bind library functions to types. * Using directive to bind library functions and free functions to types.
* Can occur within contracts and libraries. * Can occur within contracts and libraries and at the file level.
*/ */
usingDirective: Using identifierPath For (Mul | typeName) Semicolon; usingDirective: Using (identifierPath | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Global? Semicolon;
/** /**
* A type name can be an elementary type, a function type, a mapping type, a user-defined type * A type name can be an elementary type, a function type, a mapping type, a user-defined type
* (e.g. a contract or struct) or an array type. * (e.g. a contract or struct) or an array type.
@ -388,7 +389,7 @@ inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;
/** /**
* Besides regular non-keyword Identifiers, some keywords like 'from' and 'error' can also be used as identifiers. * Besides regular non-keyword Identifiers, some keywords like 'from' and 'error' can also be used as identifiers.
*/ */
identifier: Identifier | From | Error | Revert; identifier: Identifier | From | Error | Revert | Global;
literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral; literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
booleanLiteral: True | False; booleanLiteral: True | False;

View File

@ -5,10 +5,8 @@ Solidity is an object-oriented, high-level language for implementing smart
contracts. Smart contracts are programs which govern the behaviour of accounts contracts. Smart contracts are programs which govern the behaviour of accounts
within the Ethereum state. within the Ethereum state.
Solidity is a `curly-bracket language <https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages>`_. Solidity is a `curly-bracket language <https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages>`_ designed to target the Ethereum Virtual Machine (EVM).
It is influenced by C++, Python and JavaScript, and is designed to target the Ethereum Virtual Machine (EVM). It is influenced by C++, Python and JavaScript. You can find more details about which languages Solidity has been inspired by in the :doc:`language influences <language-influences>` section.
You can find more details about which languages Solidity has been inspired by in
the :doc:`language influences <language-influences>` section.
Solidity is statically typed, supports inheritance, libraries and complex Solidity is statically typed, supports inheritance, libraries and complex
user-defined types among other features. user-defined types among other features.
@ -90,24 +88,25 @@ our `Gitter channel <https://gitter.im/ethereum/solidity/>`_.
Translations Translations
------------ ------------
Community volunteers help translate this documentation into several languages. Community contributors help translate this documentation into several languages.
They have varying degrees of completeness and up-to-dateness. The English Note that they have varying degrees of completeness and up-to-dateness. The English
version stands as a reference. version stands as a reference.
You can switch between languages by clicking on the flyout menu in the bottom-left corner
and selecting the preferred language.
* `French <https://docs.soliditylang.org/fr/latest/>`_
* `Indonesian <https://github.com/solidity-docs/id-indonesian>`_
* `Persian <https://github.com/solidity-docs/fa-persian>`_
* `Japanese <https://github.com/solidity-docs/ja-japanese>`_
* `Korean <https://github.com/solidity-docs/ko-korean>`_
* `Chinese <https://github.com/solidity-docs/zh-cn-chinese/>`_
.. note:: .. note::
We recently set up a new GitHub organization and translation workflow to help streamline the We recently set up a new GitHub organization and translation workflow to help streamline the
community efforts. Please refer to the `translation guide <https://github.com/solidity-docs/translation-guide>`_ community efforts. Please refer to the `translation guide <https://github.com/solidity-docs/translation-guide>`_
for information on how to contribute to the community translations moving forward. for information on how to start a new language or contribute to the community translations.
* `French <https://solidity-fr.readthedocs.io>`_ (in progress)
* `Italian <https://github.com/damianoazzolini/solidity>`_ (in progress)
* `Japanese <https://solidity-jp.readthedocs.io>`_
* `Korean <https://solidity-kr.readthedocs.io>`_ (in progress)
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
* `Simplified Chinese <https://learnblockchain.cn/docs/solidity/>`_ (in progress)
* `Spanish <https://solidity-es.readthedocs.io>`_
* `Turkish <https://github.com/denizozzgur/Solidity_TR/blob/master/README.md>`_ (partial)
Contents Contents
======== ========

View File

@ -140,8 +140,7 @@ by checking if the lowest bit is set: short (not set) and long (set).
.. note:: .. note::
Handling invalidly encoded slots is currently not supported but may be added in the future. Handling invalidly encoded slots is currently not supported but may be added in the future.
If you are compiling via the experimental IR-based compiler pipeline, reading an invalidly encoded If you are compiling via IR, reading an invalidly encoded slot results in a ``Panic(0x22)`` error.
slot results in a ``Panic(0x22)`` error.
JSON Output JSON Output
=========== ===========

View File

@ -23,7 +23,7 @@ call completely.
Currently, the parameter ``--optimize`` activates the opcode-based optimizer for the Currently, the parameter ``--optimize`` activates the opcode-based optimizer for the
generated bytecode and the Yul optimizer for the Yul code generated internally, for example for ABI coder v2. generated bytecode and the Yul optimizer for the Yul code generated internally, for example for ABI coder v2.
One can use ``solc --ir-optimized --optimize`` to produce an One can use ``solc --ir-optimized --optimize`` to produce an
optimized experimental Yul IR for a Solidity source. Similarly, one can use ``solc --strict-assembly --optimize`` optimized Yul IR for a Solidity source. Similarly, one can use ``solc --strict-assembly --optimize``
for a stand-alone Yul mode. for a stand-alone Yul mode.
You can find more details on both optimizer modules and their optimization steps below. You can find more details on both optimizer modules and their optimization steps below.

View File

@ -168,8 +168,8 @@ following:
.. code-block:: solidity .. code-block:: solidity
function balances(address _account) external view returns (uint) { function balances(address account) external view returns (uint) {
return balances[_account]; return balances[account];
} }
You can use this function to query the balance of a single account. You can use this function to query the balance of a single account.

View File

@ -15,11 +15,7 @@ The IR-based code generator was introduced with an aim to not only allow
code generation to be more transparent and auditable but also code generation to be more transparent and auditable but also
to enable more powerful optimization passes that span across functions. to enable more powerful optimization passes that span across functions.
Currently, the IR-based code generator is still marked experimental, You can enable it on the command line using ``--via-ir``
but it supports all language features and has received a lot of testing,
so we consider it almost ready for production use.
You can enable it on the command line using ``--experimental-via-ir``
or with the option ``{"viaIR": true}`` in standard-json and we or with the option ``{"viaIR": true}`` in standard-json and we
encourage everyone to try it out! encourage everyone to try it out!
@ -34,6 +30,48 @@ Semantic Only Changes
This section lists the changes that are semantic-only, thus potentially This section lists the changes that are semantic-only, thus potentially
hiding new and different behavior in existing code. hiding new and different behavior in existing code.
- The order of state variable initialization has changed in case of inheritance.
The order used to be:
- All state variables are zero-initialized at the beginning.
- Evaluate base constructor arguments from most derived to most base contract.
- Initialize all state variables in the whole inheritance hierarchy from most base to most derived.
- Run the constructor, if present, for all contracts in the linearized hierarchy from most base to most derived.
New order:
- All state variables are zero-initialized at the beginning.
- Evaluate base constructor arguments from most derived to most base contract.
- For every contract in order from most base to most derived in the linearized hierarchy:
1. Initialize state variables.
2. Run the constructor (if present).
This causes differences in contracts where the initial value of a state
variable relies on the result of the constructor in another contract:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1;
contract A {
uint x;
constructor() {
x = 42;
}
function f() public view returns(uint256) {
return x;
}
}
contract B is A {
uint public y = f();
}
Previously, ``y`` would be set to 0. This is due to the fact that we would first initialize state variables: First, ``x`` is set to 0, and when initializing ``y``, ``f()`` would return 0 causing ``y`` to be 0 as well.
With the new rules, ``y`` will be set to 42. We first initialize ``x`` to 0, then call A's constructor which sets ``x`` to 42. Finally, when initializing ``y``, ``f()`` returns 42 causing ``y`` to be 42.
- When storage structs are deleted, every storage slot that contains - When storage structs are deleted, every storage slot that contains
a member of the struct is set to zero entirely. Formerly, padding space a member of the struct is set to zero entirely. Formerly, padding space
was left untouched. was left untouched.
@ -78,8 +116,8 @@ hiding new and different behavior in existing code.
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0; pragma solidity >=0.7.0;
contract C { contract C {
function f(uint _a) public pure mod() returns (uint _r) { function f(uint a) public pure mod() returns (uint r) {
_r = _a++; r = a++;
} }
modifier mod() { _; _; } modifier mod() { _; _; }
} }
@ -116,47 +154,6 @@ hiding new and different behavior in existing code.
- New code generator: ``0`` as all parameters, including return parameters, will be re-initialized before - New code generator: ``0`` as all parameters, including return parameters, will be re-initialized before
each ``_;`` evaluation. each ``_;`` evaluation.
- The order of contract initialization has changed in case of inheritance.
The order used to be:
- All state variables are zero-initialized at the beginning.
- Evaluate base constructor arguments from most derived to most base contract.
- Initialize all state variables in the whole inheritance hierarchy from most base to most derived.
- Run the constructor, if present, for all contracts in the linearized hierarchy from most base to most derived.
New order:
- All state variables are zero-initialized at the beginning.
- Evaluate base constructor arguments from most derived to most base contract.
- For every contract in order from most base to most derived in the linearized hierarchy execute:
1. If present at declaration, initial values are assigned to state variables.
2. Constructor, if present.
This causes differences in some contracts, for example:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1;
contract A {
uint x;
constructor() {
x = 42;
}
function f() public view returns(uint256) {
return x;
}
}
contract B is A {
uint public y = f();
}
Previously, ``y`` would be set to 0. This is due to the fact that we would first initialize state variables: First, ``x`` is set to 0, and when initializing ``y``, ``f()`` would return 0 causing ``y`` to be 0 as well.
With the new rules, ``y`` will be set to 42. We first initialize ``x`` to 0, then call A's constructor which sets ``x`` to 42. Finally, when initializing ``y``, ``f()`` returns 42 causing ``y`` to be 42.
- Copying ``bytes`` arrays from memory to storage is implemented in a different way. - Copying ``bytes`` arrays from memory to storage is implemented in a different way.
The old code generator always copies full words, while the new one cuts the byte The old code generator always copies full words, while the new one cuts the byte
array after its end. The old behaviour can lead to dirty data being copied after array after its end. The old behaviour can lead to dirty data being copied after
@ -170,7 +167,7 @@ This causes differences in some contracts, for example:
contract C { contract C {
bytes x; bytes x;
function f() public returns (uint _r) { function f() public returns (uint r) {
bytes memory m = "tmp"; bytes memory m = "tmp";
assembly { assembly {
mstore(m, 8) mstore(m, 8)
@ -178,7 +175,7 @@ This causes differences in some contracts, for example:
} }
x = m; x = m;
assembly { assembly {
_r := sload(x.slot) r := sload(x.slot)
} }
} }
} }
@ -201,8 +198,8 @@ This causes differences in some contracts, for example:
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1; pragma solidity >=0.8.1;
contract C { contract C {
function preincr_u8(uint8 _a) public pure returns (uint8) { function preincr_u8(uint8 a) public pure returns (uint8) {
return ++_a + _a; return ++a + a;
} }
} }
@ -222,11 +219,11 @@ This causes differences in some contracts, for example:
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1; pragma solidity >=0.8.1;
contract C { contract C {
function add(uint8 _a, uint8 _b) public pure returns (uint8) { function add(uint8 a, uint8 b) public pure returns (uint8) {
return _a + _b; return a + b;
} }
function g(uint8 _a, uint8 _b) public pure returns (uint8) { function g(uint8 a, uint8 b) public pure returns (uint8) {
return add(++_a + ++_b, _a + _b); return add(++a + ++b, a + b);
} }
} }
@ -325,13 +322,13 @@ For example:
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1; pragma solidity >=0.8.1;
contract C { contract C {
function f(uint8 _a) public pure returns (uint _r1, uint _r2) function f(uint8 a) public pure returns (uint r1, uint r2)
{ {
_a = ~_a; a = ~a;
assembly { assembly {
_r1 := _a r1 := a
} }
_r2 = _a; r2 = a;
} }
} }
@ -340,6 +337,6 @@ The function ``f(1)`` returns the following values:
- Old code generator: (``fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe``, ``00000000000000000000000000000000000000000000000000000000000000fe``) - Old code generator: (``fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe``, ``00000000000000000000000000000000000000000000000000000000000000fe``)
- New code generator: (``00000000000000000000000000000000000000000000000000000000000000fe``, ``00000000000000000000000000000000000000000000000000000000000000fe``) - New code generator: (``00000000000000000000000000000000000000000000000000000000000000fe``, ``00000000000000000000000000000000000000000000000000000000000000fe``)
Note that, unlike the new code generator, the old code generator does not perform a cleanup after the bit-not assignment (``_a = ~_a``). Note that, unlike the new code generator, the old code generator does not perform a cleanup after the bit-not assignment (``a = ~a``).
This results in different values being assigned (within the inline assembly block) to return value ``_r1`` between the old and new code generators. This results in different values being assigned (within the inline assembly block) to return value ``r1`` between the old and new code generators.
However, both code generators perform a cleanup before the new value of ``_a`` is assigned to ``_r2``. However, both code generators perform a cleanup before the new value of ``a`` is assigned to ``r2``.

View File

@ -3,8 +3,8 @@ Layout of a Solidity Source File
******************************** ********************************
Source files can contain an arbitrary number of Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives, :ref:`contract definitions<contract_structure>`, import_ ,
:ref:`pragma directives<pragma>` and :ref:`pragma<pragma>` and :ref:`using for<using-for>` directives and
:ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>`, :ref:`error<errors>` :ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>`, :ref:`error<errors>`
and :ref:`constant variable<constants>` definitions. and :ref:`constant variable<constants>` definitions.

View File

@ -210,9 +210,9 @@ using a second proxy:
contract ProxyWithMoreFunctionality { contract ProxyWithMoreFunctionality {
PermissionlessProxy proxy; PermissionlessProxy proxy;
function callOther(address _addr, bytes memory _payload) public function callOther(address addr, bytes memory payload) public
returns (bool, bytes memory) { returns (bool, bytes memory) {
return proxy.callOther(_addr, _payload); return proxy.callOther(addr, payload);
} }
// Other functions and other functionality // Other functions and other functionality
} }
@ -220,9 +220,9 @@ using a second proxy:
// This is the full contract, it has no other functionality and // This is the full contract, it has no other functionality and
// requires no privileges to work. // requires no privileges to work.
contract PermissionlessProxy { contract PermissionlessProxy {
function callOther(address _addr, bytes memory _payload) public function callOther(address addr, bytes memory payload) public
returns (bool, bytes memory) { returns (bool, bytes memory) {
return _addr.call(_payload); return addr.call(payload);
} }
} }

View File

@ -82,12 +82,12 @@ Overflow
uint immutable x; uint immutable x;
uint immutable y; uint immutable y;
function add(uint _x, uint _y) internal pure returns (uint) { function add(uint x_, uint y_) internal pure returns (uint) {
return _x + _y; return x_ + y_;
} }
constructor(uint _x, uint _y) { constructor(uint x_, uint y_) {
(x, y) = (_x, _y); (x, y) = (x_, y_);
} }
function stateAdd() public view returns (uint) { function stateAdd() public view returns (uint) {
@ -116,7 +116,7 @@ Here, it reports the following:
Overflow.add(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935) -- internal call Overflow.add(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935) -- internal call
--> o.sol:9:20: --> o.sol:9:20:
| |
9 | return _x + _y; 9 | return x_ + y_;
| ^^^^^^^ | ^^^^^^^
If we add ``require`` statements that filter out overflow cases, If we add ``require`` statements that filter out overflow cases,
@ -131,12 +131,12 @@ the SMTChecker proves that no overflow is reachable (by not reporting warnings):
uint immutable x; uint immutable x;
uint immutable y; uint immutable y;
function add(uint _x, uint _y) internal pure returns (uint) { function add(uint x_, uint y_) internal pure returns (uint) {
return _x + _y; return x_ + y_;
} }
constructor(uint _x, uint _y) { constructor(uint x_, uint y_) {
(x, y) = (_x, _y); (x, y) = (x_, y_);
} }
function stateAdd() public view returns (uint) { function stateAdd() public view returns (uint) {
@ -155,7 +155,7 @@ An assertion represents an invariant in your code: a property that must be true
The code below defines a function ``f`` that guarantees no overflow. The code below defines a function ``f`` that guarantees no overflow.
Function ``inv`` defines the specification that ``f`` is monotonically increasing: Function ``inv`` defines the specification that ``f`` is monotonically increasing:
for every possible pair ``(_a, _b)``, if ``_b > _a`` then ``f(_b) > f(_a)``. for every possible pair ``(a, b)``, if ``b > a`` then ``f(b) > f(a)``.
Since ``f`` is indeed monotonically increasing, the SMTChecker proves that our Since ``f`` is indeed monotonically increasing, the SMTChecker proves that our
property is correct. You are encouraged to play with the property and the function property is correct. You are encouraged to play with the property and the function
definition to see what results come out! definition to see what results come out!
@ -166,14 +166,14 @@ definition to see what results come out!
pragma solidity >=0.8.0; pragma solidity >=0.8.0;
contract Monotonic { contract Monotonic {
function f(uint _x) internal pure returns (uint) { function f(uint x) internal pure returns (uint) {
require(_x < type(uint128).max); require(x < type(uint128).max);
return _x * 42; return x * 42;
} }
function inv(uint _a, uint _b) public pure { function inv(uint a, uint b) public pure {
require(_b > _a); require(b > a);
assert(f(_b) > f(_a)); assert(f(b) > f(a));
} }
} }
@ -188,14 +188,14 @@ equal every element in the array.
pragma solidity >=0.8.0; pragma solidity >=0.8.0;
contract Max { contract Max {
function max(uint[] memory _a) public pure returns (uint) { function max(uint[] memory a) public pure returns (uint) {
uint m = 0; uint m = 0;
for (uint i = 0; i < _a.length; ++i) for (uint i = 0; i < a.length; ++i)
if (_a[i] > m) if (a[i] > m)
m = _a[i]; m = a[i];
for (uint i = 0; i < _a.length; ++i) for (uint i = 0; i < a.length; ++i)
assert(m >= _a[i]); assert(m >= a[i]);
return m; return m;
} }
@ -222,15 +222,15 @@ For example, changing the code to
pragma solidity >=0.8.0; pragma solidity >=0.8.0;
contract Max { contract Max {
function max(uint[] memory _a) public pure returns (uint) { function max(uint[] memory a) public pure returns (uint) {
require(_a.length >= 5); require(a.length >= 5);
uint m = 0; uint m = 0;
for (uint i = 0; i < _a.length; ++i) for (uint i = 0; i < a.length; ++i)
if (_a[i] > m) if (a[i] > m)
m = _a[i]; m = a[i];
for (uint i = 0; i < _a.length; ++i) for (uint i = 0; i < a.length; ++i)
assert(m > _a[i]); assert(m > a[i]);
return m; return m;
} }
@ -243,7 +243,7 @@ gives us:
Warning: CHC: Assertion violation happens here. Warning: CHC: Assertion violation happens here.
Counterexample: Counterexample:
_a = [0, 0, 0, 0, 0] a = [0, 0, 0, 0, 0]
= 0 = 0
Transaction trace: Transaction trace:
@ -251,7 +251,7 @@ gives us:
Test.max([0, 0, 0, 0, 0]) Test.max([0, 0, 0, 0, 0])
--> max.sol:14:4: --> max.sol:14:4:
| |
14 | assert(m > _a[i]); 14 | assert(m > a[i]);
State Properties State Properties
@ -383,9 +383,9 @@ anything, including reenter the caller contract.
Unknown immutable unknown; Unknown immutable unknown;
constructor(Unknown _u) { constructor(Unknown u) {
require(address(_u) != address(0)); require(address(u) != address(0));
unknown = _u; unknown = u;
} }
modifier mutex { modifier mutex {
@ -395,8 +395,8 @@ anything, including reenter the caller contract.
lock = false; lock = false;
} }
function set(uint _x) mutex public { function set(uint x_) mutex public {
x = _x; x = x_;
} }
function run() mutex public { function run() mutex public {
@ -754,15 +754,15 @@ not mean loss of proving power.
{ {
function f( function f(
bytes32 hash, bytes32 hash,
uint8 _v1, uint8 _v2, uint8 v1, uint8 v2,
bytes32 _r1, bytes32 _r2, bytes32 r1, bytes32 r2,
bytes32 _s1, bytes32 _s2 bytes32 s1, bytes32 s2
) public pure returns (address) { ) public pure returns (address) {
address a1 = ecrecover(hash, _v1, _r1, _s1); address a1 = ecrecover(hash, v1, r1, s1);
require(_v1 == _v2); require(v1 == v2);
require(_r1 == _r2); require(r1 == r2);
require(_s1 == _s2); require(s1 == s2);
address a2 = ecrecover(hash, _v2, _r2, _s2); address a2 = ecrecover(hash, v2, r2, s2);
assert(a1 == a2); assert(a1 == a2);
return a1; return a1;
} }

View File

@ -4,12 +4,12 @@
Mapping Types Mapping Types
============= =============
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables Mapping types use the syntax ``mapping(KeyType => ValueType)`` and variables
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``. of mapping type are declared using the syntax ``mapping(KeyType => ValueType) VariableName``.
The ``_KeyType`` can be any The ``KeyType`` can be any
built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined
or complex types, such as mappings, structs or array types are not allowed. or complex types, such as mappings, structs or array types are not allowed.
``_ValueType`` can be any type, including mappings, arrays and structs. ``ValueType`` can be any type, including mappings, arrays and structs.
You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised
such that every possible key exists and is mapped to a value whose such that every possible key exists and is mapped to a value whose
@ -29,10 +29,10 @@ of contract functions that are publicly visible.
These restrictions are also true for arrays and structs that contain mappings. These restrictions are also true for arrays and structs that contain mappings.
You can mark state variables of mapping type as ``public`` and Solidity creates a You can mark state variables of mapping type as ``public`` and Solidity creates a
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a parameter for the getter. :ref:`getter <visibility-and-getters>` for you. The ``KeyType`` becomes a parameter for the getter.
If ``_ValueType`` is a value type or a struct, the getter returns ``_ValueType``. If ``ValueType`` is a value type or a struct, the getter returns ``ValueType``.
If ``_ValueType`` is an array or a mapping, the getter has one parameter for If ``ValueType`` is an array or a mapping, the getter has one parameter for
each ``_KeyType``, recursively. each ``KeyType``, recursively.
In the example below, the ``MappingExample`` contract defines a public ``balances`` In the example below, the ``MappingExample`` contract defines a public ``balances``
mapping, with the key type an ``address``, and a value type a ``uint``, mapping mapping, with the key type an ``address``, and a value type a ``uint``, mapping

View File

@ -26,6 +26,23 @@ except for comparison operators where the result is always ``bool``.
The operators ``**`` (exponentiation), ``<<`` and ``>>`` use the type of the The operators ``**`` (exponentiation), ``<<`` and ``>>`` use the type of the
left operand for the operation and the result. left operand for the operation and the result.
Ternary Operator
----------------
The ternary operator is used in expressions of the form ``<expression> ? <trueExpression> : <falseExpression>``.
It evaluates one of the latter two given expressions depending upon the result of the evaluation of the main ``<expression>``.
If ``<expression>`` evaluates to ``true``, then ``<trueExpression>`` will be evaluated, otherwise ``<falseExpression>`` is evaluated.
The result of the ternary operator does not have a rational number type, even if all of its operands are rational number literals.
The result type is determined from the types of the two operands in the same way as above, converting to their mobile type first if required.
As a consequence, ``255 + (true ? 1 : 0)`` will revert due to arithmetic overflow.
The reason is that ``(true ? 1 : 0)`` is of ``uint8`` type, which forces the addition to be performed in ``uint8`` as well,
and 256 exceeds the range allowed for this type.
Another consequence is that an expression like ``1.5 + 1.5`` is valid but ``1.5 + (true ? 1.5 : 2.5)`` is not.
This is because the former is a rational expression evaluated in unlimited precision and only its final value matters.
The latter involves a conversion of a fractional rational number to an integer, which is currently disallowed.
.. index:: assignment, lvalue, ! compound operators .. index:: assignment, lvalue, ! compound operators
Compound and Increment/Decrement Operators Compound and Increment/Decrement Operators

View File

@ -511,21 +511,21 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
/// @dev Address of the client contract managed by proxy i.e., this contract /// @dev Address of the client contract managed by proxy i.e., this contract
address client; address client;
constructor(address _client) { constructor(address client_) {
client = _client; client = client_;
} }
/// Forward call to "setOwner(address)" that is implemented by client /// Forward call to "setOwner(address)" that is implemented by client
/// after doing basic validation on the address argument. /// after doing basic validation on the address argument.
function forward(bytes calldata _payload) external { function forward(bytes calldata payload) external {
bytes4 sig = bytes4(_payload[:4]); bytes4 sig = bytes4(payload[:4]);
// Due to truncating behaviour, bytes4(_payload) performs identically. // Due to truncating behaviour, bytes4(payload) performs identically.
// bytes4 sig = bytes4(_payload); // bytes4 sig = bytes4(payload);
if (sig == bytes4(keccak256("setOwner(address)"))) { if (sig == bytes4(keccak256("setOwner(address)"))) {
address owner = abi.decode(_payload[4:], (address)); address owner = abi.decode(payload[4:], (address));
require(owner != address(0), "Address of owner cannot be zero."); require(owner != address(0), "Address of owner cannot be zero.");
} }
(bool status,) = client.delegatecall(_payload); (bool status,) = client.delegatecall(payload);
require(status, "Forwarded call failed."); require(status, "Forwarded call failed.");
} }
} }

View File

@ -463,7 +463,7 @@ There is no additional semantic meaning added to a number literal containing und
the underscores are ignored. the underscores are ignored.
Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by
using them together with a non-literal expression or by explicit conversion). using them together with anything else than a number literal expression (like boolean literals) or by explicit conversion).
This means that computations do not overflow and divisions do not truncate This means that computations do not overflow and divisions do not truncate
in number literal expressions. in number literal expressions.
@ -471,6 +471,15 @@ For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type ``
although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results
in the integer ``4`` (although non-integers were used in between). in the integer ``4`` (although non-integers were used in between).
.. warning::
While most operators produce a literal expression when applied to literals, there are certain operators that do not follow this pattern:
- Ternary operator (``... ? ... : ...``),
- Array subscript (``<array>[<index>]``).
You might expect expressions like ``255 + (true ? 1 : 0)`` or ``255 + [1, 2, 3][0]`` to be equivalent to using the literal 256
directly, but in fact they are computed within the type ``uint8`` and can overflow.
Any operator that can be applied to integers can also be applied to number literal expressions as Any operator that can be applied to integers can also be applied to number literal expressions as
long as the operands are integers. If any of the two is fractional, bit operations are disallowed long as the operands are integers. If any of the two is fractional, bit operations are disallowed
and exponentiation is disallowed if the exponent is fractional (because that might result in and exponentiation is disallowed if the exponent is fractional (because that might result in

View File

@ -297,7 +297,7 @@ Input Description
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin // tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
"evmVersion": "byzantium", "evmVersion": "byzantium",
// Optional: Change compilation pipeline to go through the Yul intermediate representation. // Optional: Change compilation pipeline to go through the Yul intermediate representation.
// This is a highly EXPERIMENTAL feature, not to be used for production. This is false by default. // This is false by default.
"viaIR": true, "viaIR": true,
// Optional: Debugging settings // Optional: Debugging settings
"debug": { "debug": {

View File

@ -1124,6 +1124,11 @@ regular strings in native encoding. For code,
Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the previous chapter. Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the previous chapter.
.. note::
An object with a name that ends in ``_deployed`` is treated as deployed code by the Yul optimizer.
The only consequence of this is a different gas cost heuristic in the optimizer.
.. note:: .. note::
Data objects or sub-objects whose names contain a ``.`` can be defined Data objects or sub-objects whose names contain a ``.`` can be defined
@ -1172,17 +1177,17 @@ An example Yul Object is shown below:
// now return the runtime object (the currently // now return the runtime object (the currently
// executing code is the constructor code) // executing code is the constructor code)
size := datasize("runtime") size := datasize("Contract1_deployed")
offset := allocate(size) offset := allocate(size)
// This will turn into a memory->memory copy for Ewasm and // This will turn into a memory->memory copy for Ewasm and
// a codecopy for EVM // a codecopy for EVM
datacopy(offset, dataoffset("runtime"), size) datacopy(offset, dataoffset("Contract1_deployed"), size)
return(offset, size) return(offset, size)
} }
data "Table2" hex"4123" data "Table2" hex"4123"
object "runtime" { object "Contract1_deployed" {
code { code {
function allocate(size) -> ptr { function allocate(size) -> ptr {
ptr := mload(0x40) ptr := mload(0x40)
@ -1204,7 +1209,7 @@ An example Yul Object is shown below:
// code here ... // code here ...
} }
object "runtime" { object "Contract2_deployed" {
code { code {
// code here ... // code here ...
} }

View File

@ -415,9 +415,8 @@ map<u256, u256> const& Assembly::optimiseInternal(
for (size_t subId = 0; subId < m_subs.size(); ++subId) for (size_t subId = 0; subId < m_subs.size(); ++subId)
{ {
OptimiserSettings settings = _settings; OptimiserSettings settings = _settings;
// Disable creation mode for sub-assemblies. Assembly& sub = *m_subs[subId];
settings.isCreation = false; map<u256, u256> const& subTagReplacements = sub.optimiseInternal(
map<u256, u256> const& subTagReplacements = m_subs[subId]->optimiseInternal(
settings, settings,
JumpdestRemover::referencedTags(m_items, subId) JumpdestRemover::referencedTags(m_items, subId)
); );
@ -436,7 +435,7 @@ map<u256, u256> const& Assembly::optimiseInternal(
m_items, m_items,
_tagsReferencedFromOutside, _tagsReferencedFromOutside,
_settings.expectedExecutionsPerDeployment, _settings.expectedExecutionsPerDeployment,
_settings.isCreation, isCreation(),
_settings.evmVersion _settings.evmVersion
}.optimise(); }.optimise();
@ -537,8 +536,8 @@ map<u256, u256> const& Assembly::optimiseInternal(
if (_settings.runConstantOptimiser) if (_settings.runConstantOptimiser)
ConstantOptimisationMethod::optimiseConstants( ConstantOptimisationMethod::optimiseConstants(
_settings.isCreation, isCreation(),
_settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment, isCreation() ? 1 : _settings.expectedExecutionsPerDeployment,
_settings.evmVersion, _settings.evmVersion,
*this *this
); );

View File

@ -48,7 +48,7 @@ using AssemblyPointer = std::shared_ptr<Assembly>;
class Assembly class Assembly
{ {
public: public:
explicit Assembly(std::string _name = std::string()):m_name(std::move(_name)) { } Assembly(bool _creation, std::string _name): m_creation(_creation), m_name(std::move(_name)) { }
AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
@ -117,7 +117,6 @@ public:
struct OptimiserSettings struct OptimiserSettings
{ {
bool isCreation = false;
bool runInliner = false; bool runInliner = false;
bool runJumpdestRemover = false; bool runJumpdestRemover = false;
bool runPeephole = false; bool runPeephole = false;
@ -157,6 +156,8 @@ public:
std::vector<size_t> decodeSubPath(size_t _subObjectId) const; std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
size_t encodeSubPath(std::vector<size_t> const& _subPath); size_t encodeSubPath(std::vector<size_t> const& _subPath);
bool isCreation() const { return m_creation; }
protected: protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and /// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly /// returns the replaced tags. Also takes an argument containing the tags of this assembly
@ -214,6 +215,8 @@ protected:
mutable std::vector<size_t> m_tagPositionsInBytecode; mutable std::vector<size_t> m_tagPositionsInBytecode;
int m_deposit = 0; int m_deposit = 0;
/// True, if the assembly contains contract creation code.
bool const m_creation = false;
/// Internal name of the assembly object, only used with the Yul backend /// Internal name of the assembly object, only used with the Yul backend
/// currently /// currently
std::string m_name; std::string m_name;

View File

@ -200,9 +200,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case Operation: case Operation:
{ {
assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction."); assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction.");
string name = instructionInfo(instruction()).name; text = util::toLower(instructionInfo(instruction()).name);
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
text = name;
break; break;
} }
case Push: case Push:

View File

@ -219,7 +219,7 @@ void CSECodeGenerator::addDependencies(Id _c)
{ {
if (m_classPositions.count(_c)) if (m_classPositions.count(_c))
return; // it is already on the stack return; // it is already on the stack
if (m_neededBy.count(_c)) if (m_neededBy.find(_c) != m_neededBy.end())
return; // we already computed the dependencies for _c return; // we already computed the dependencies for _c
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
assertThrow(expr.item, OptimizerException, ""); assertThrow(expr.item, OptimizerException, "");
@ -300,8 +300,8 @@ void CSECodeGenerator::addDependencies(Id _c)
void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced) void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
{ {
for (auto it: m_classPositions) for (auto const& it: m_classPositions)
for (auto p: it.second) for (int p: it.second)
if (p > m_stackHeight) if (p > m_stackHeight)
{ {
assertThrow(false, OptimizerException, ""); assertThrow(false, OptimizerException, "");

View File

@ -24,11 +24,12 @@
#pragma once #pragma once
#include <vector>
#include <map> #include <map>
#include <ostream>
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <ostream> #include <unordered_map>
#include <vector>
#include <libsolutil/CommonIO.h> #include <libsolutil/CommonIO.h>
#include <libsolutil/Exceptions.h> #include <libsolutil/Exceptions.h>
#include <libevmasm/ExpressionClasses.h> #include <libevmasm/ExpressionClasses.h>
@ -154,11 +155,11 @@ private:
/// Current height of the stack relative to the start. /// Current height of the stack relative to the start.
int m_stackHeight = 0; int m_stackHeight = 0;
/// If (b, a) is in m_requests then b is needed to compute a. /// If (b, a) is in m_requests then b is needed to compute a.
std::multimap<Id, Id> m_neededBy; std::unordered_multimap<Id, Id> m_neededBy;
/// Current content of the stack. /// Current content of the stack.
std::map<int, Id> m_stack; std::map<int, Id> m_stack;
/// Current positions of equivalence classes, equal to the empty set if already deleted. /// Current positions of equivalence classes, equal to the empty set if already deleted.
std::map<Id, std::set<int>> m_classPositions; std::unordered_map<Id, std::set<int>> m_classPositions;
/// The actual equivalence class items and how to compute them. /// The actual equivalence class items and how to compute them.
ExpressionClasses& m_expressionClasses; ExpressionClasses& m_expressionClasses;

View File

@ -41,51 +41,28 @@ struct OptimiserState
std::back_insert_iterator<AssemblyItems> out; std::back_insert_iterator<AssemblyItems> out;
}; };
template <class Method, size_t Arguments> template<typename FunctionType>
struct ApplyRule struct FunctionParameterCount;
template<typename R, typename... Args>
struct FunctionParameterCount<R(Args...)>
{ {
}; static constexpr auto value = sizeof...(Args);
template <class Method>
struct ApplyRule<Method, 4>
{
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
{
return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out);
}
};
template <class Method>
struct ApplyRule<Method, 3>
{
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
{
return Method::applySimple(_in[0], _in[1], _in[2], _out);
}
};
template <class Method>
struct ApplyRule<Method, 2>
{
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
{
return Method::applySimple(_in[0], _in[1], _out);
}
};
template <class Method>
struct ApplyRule<Method, 1>
{
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
{
return Method::applySimple(_in[0], _out);
}
}; };
template <class Method, size_t WindowSize> template <class Method>
struct SimplePeepholeOptimizerMethod struct SimplePeepholeOptimizerMethod
{ {
template <size_t... Indices>
static bool applyRule(AssemblyItems::const_iterator _in, back_insert_iterator<AssemblyItems> _out, index_sequence<Indices...>)
{
return Method::applySimple(_in[Indices]..., _out);
}
static bool apply(OptimiserState& _state) static bool apply(OptimiserState& _state)
{ {
static constexpr size_t WindowSize = FunctionParameterCount<decltype(Method::applySimple)>::value - 1;
if ( if (
_state.i + WindowSize <= _state.items.size() && _state.i + WindowSize <= _state.items.size() &&
ApplyRule<Method, WindowSize>::applyRule(_state.items.begin() + static_cast<ptrdiff_t>(_state.i), _state.out) applyRule(_state.items.begin() + static_cast<ptrdiff_t>(_state.i), _state.out, make_index_sequence<WindowSize>{})
) )
{ {
_state.i += WindowSize; _state.i += WindowSize;
@ -96,7 +73,7 @@ struct SimplePeepholeOptimizerMethod
} }
}; };
struct Identity: SimplePeepholeOptimizerMethod<Identity, 1> struct Identity: SimplePeepholeOptimizerMethod<Identity>
{ {
static bool applySimple(AssemblyItem const& _item, std::back_insert_iterator<AssemblyItems> _out) static bool applySimple(AssemblyItem const& _item, std::back_insert_iterator<AssemblyItems> _out)
{ {
@ -105,7 +82,7 @@ struct Identity: SimplePeepholeOptimizerMethod<Identity, 1>
} }
}; };
struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2> struct PushPop: SimplePeepholeOptimizerMethod<PushPop>
{ {
static bool applySimple(AssemblyItem const& _push, AssemblyItem const& _pop, std::back_insert_iterator<AssemblyItems>) static bool applySimple(AssemblyItem const& _push, AssemblyItem const& _pop, std::back_insert_iterator<AssemblyItems>)
{ {
@ -118,7 +95,7 @@ struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
} }
}; };
struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2> struct OpPop: SimplePeepholeOptimizerMethod<OpPop>
{ {
static bool applySimple( static bool applySimple(
AssemblyItem const& _op, AssemblyItem const& _op,
@ -140,7 +117,36 @@ struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2>
} }
}; };
struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2> struct OpStop: SimplePeepholeOptimizerMethod<OpStop>
{
static bool applySimple(
AssemblyItem const& _op,
AssemblyItem const& _stop,
std::back_insert_iterator<AssemblyItems> _out
)
{
if (_stop == Instruction::STOP)
{
if (_op.type() == Operation)
{
Instruction instr = _op.instruction();
if (!instructionInfo(instr).sideEffects)
{
*_out = {Instruction::STOP, _op.location()};
return true;
}
}
else if (_op.type() == Push)
{
*_out = {Instruction::STOP, _op.location()};
return true;
}
}
return false;
}
};
struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap>
{ {
static size_t applySimple(AssemblyItem const& _s1, AssemblyItem const& _s2, std::back_insert_iterator<AssemblyItems>) static size_t applySimple(AssemblyItem const& _s1, AssemblyItem const& _s2, std::back_insert_iterator<AssemblyItems>)
{ {
@ -148,7 +154,7 @@ struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2>
} }
}; };
struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2> struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush>
{ {
static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator<AssemblyItems> _out) static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator<AssemblyItems> _out)
{ {
@ -163,7 +169,7 @@ struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
} }
}; };
struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2> struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap>
{ {
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out) static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
{ {
@ -181,7 +187,7 @@ struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2>
} }
}; };
struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2> struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison>
{ {
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out) static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
{ {
@ -207,7 +213,7 @@ struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
}; };
/// Remove swapN after dupN /// Remove swapN after dupN
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2> struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap>
{ {
static size_t applySimple( static size_t applySimple(
AssemblyItem const& _dupN, AssemblyItem const& _dupN,
@ -230,7 +236,7 @@ struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2>
}; };
struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4> struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI>
{ {
static size_t applySimple( static size_t applySimple(
AssemblyItem const& _iszero1, AssemblyItem const& _iszero1,
@ -256,7 +262,66 @@ struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4>
} }
}; };
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3> struct EqIsZeroJumpI: SimplePeepholeOptimizerMethod<EqIsZeroJumpI>
{
static size_t applySimple(
AssemblyItem const& _eq,
AssemblyItem const& _iszero,
AssemblyItem const& _pushTag,
AssemblyItem const& _jumpi,
std::back_insert_iterator<AssemblyItems> _out
)
{
if (
_eq == Instruction::EQ &&
_iszero == Instruction::ISZERO &&
_pushTag.type() == PushTag &&
_jumpi == Instruction::JUMPI
)
{
*_out = AssemblyItem(Instruction::SUB, _eq.location());
*_out = _pushTag;
*_out = _jumpi;
return true;
}
else
return false;
}
};
// push_tag_1 jumpi push_tag_2 jump tag_1: -> iszero push_tag_2 jumpi tag_1:
struct DoubleJump: SimplePeepholeOptimizerMethod<DoubleJump>
{
static size_t applySimple(
AssemblyItem const& _pushTag1,
AssemblyItem const& _jumpi,
AssemblyItem const& _pushTag2,
AssemblyItem const& _jump,
AssemblyItem const& _tag1,
std::back_insert_iterator<AssemblyItems> _out
)
{
if (
_pushTag1.type() == PushTag &&
_jumpi == Instruction::JUMPI &&
_pushTag2.type() == PushTag &&
_jump == Instruction::JUMP &&
_tag1.type() == Tag &&
_pushTag1.data() == _tag1.data()
)
{
*_out = AssemblyItem(Instruction::ISZERO, _jumpi.location());
*_out = _pushTag2;
*_out = _jumpi;
*_out = _tag1;
return true;
}
else
return false;
}
};
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext>
{ {
static size_t applySimple( static size_t applySimple(
AssemblyItem const& _pushTag, AssemblyItem const& _pushTag,
@ -282,7 +347,7 @@ struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
} }
}; };
struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3> struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions>
{ {
static bool applySimple( static bool applySimple(
AssemblyItem const& _pushTag, AssemblyItem const& _pushTag,
@ -317,7 +382,7 @@ struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
} }
}; };
struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd, 3> struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd>
{ {
static bool applySimple( static bool applySimple(
AssemblyItem const& _push, AssemblyItem const& _push,
@ -394,8 +459,8 @@ bool PeepholeOptimiser::optimise()
while (state.i < m_items.size()) while (state.i < m_items.size())
applyMethods( applyMethods(
state, state,
PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), PushPop(), OpPop(), OpStop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
DupSwap(), IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(), DupSwap(), IsZeroIsZeroJumpI(), EqIsZeroJumpI(), DoubleJump(), JumpToNext(), UnreachableCode(),
TagConjunctions(), TruthyAnd(), Identity() TagConjunctions(), TruthyAnd(), Identity()
); );
if (m_optimisedItems.size() < m_items.size() || ( if (m_optimisedItems.size() < m_items.size() || (

View File

@ -121,7 +121,9 @@ vector<SemanticInformation::Operation> SemanticInformation::readWriteOperations(
Location::Memory, Location::Memory,
Effect::Write, Effect::Write,
paramCount - 2, paramCount - 2,
paramCount - 1, // Length is in paramCount - 1, but it is only a max length,
// there is no guarantee that the full area is written to.
{},
{} {}
}); });
return operations; return operations;

View File

@ -40,10 +40,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with solidity. If not, see <http://www.gnu.org/licenses/>. // along with solidity. If not, see <http://www.gnu.org/licenses/>.
#include <liblangutil/Token.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <liblangutil/Token.h>
#include <libsolutil/StringUtils.h>
#include <map> #include <map>
using namespace std; using namespace std;
namespace solidity::langutil namespace solidity::langutil
@ -180,11 +183,11 @@ tuple<Token, unsigned int, unsigned int> fromIdentifierOrKeyword(string const& _
return ret; return ret;
}; };
auto positionM = find_if(_literal.begin(), _literal.end(), ::isdigit); auto positionM = find_if(_literal.begin(), _literal.end(), util::isDigit);
if (positionM != _literal.end()) if (positionM != _literal.end())
{ {
string baseType(_literal.begin(), positionM); string baseType(_literal.begin(), positionM);
auto positionX = find_if_not(positionM, _literal.end(), ::isdigit); auto positionX = find_if_not(positionM, _literal.end(), util::isDigit);
int m = parseSize(positionM, positionX); int m = parseSize(positionM, positionX);
Token keyword = keywordByName(baseType); Token keyword = keywordByName(baseType);
if (keyword == Token::Bytes) if (keyword == Token::Bytes)
@ -208,7 +211,7 @@ tuple<Token, unsigned int, unsigned int> fromIdentifierOrKeyword(string const& _
positionM < positionX && positionM < positionX &&
positionX < _literal.end() && positionX < _literal.end() &&
*positionX == 'x' && *positionX == 'x' &&
all_of(positionX + 1, _literal.end(), ::isdigit) all_of(positionX + 1, _literal.end(), util::isDigit)
) { ) {
int n = parseSize(positionX + 1, _literal.end()); int n = parseSize(positionX + 1, _literal.end());
if ( if (

View File

@ -1,8 +1,17 @@
if (EMSCRIPTEN) if (EMSCRIPTEN)
CreateExportedFunctionsForEMSDK(
ExportedFunctions
solidity_license
solidity_version
solidity_compile
solidity_alloc
solidity_free
solidity_reset
)
# Specify which functions to export in soljson.js. # Specify which functions to export in soljson.js.
# Note that additional Emscripten-generated methods needed by solc-js are # Note that additional Emscripten-generated methods needed by solc-js are
# defined to be exported in cmake/EthCompilerSettings.cmake. # defined to be exported in cmake/EthCompilerSettings.cmake.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\",\"_solidity_alloc\",\"_solidity_free\",\"_solidity_reset\"]'") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS='${ExportedFunctions}'")
add_executable(soljson libsolc.cpp libsolc.h) add_executable(soljson libsolc.cpp libsolc.h)
target_link_libraries(soljson PRIVATE solidity) target_link_libraries(soljson PRIVATE solidity)
else() else()

View File

@ -155,12 +155,18 @@ set(sources
interface/StorageLayout.h interface/StorageLayout.h
interface/Version.cpp interface/Version.cpp
interface/Version.h interface/Version.h
lsp/LanguageServer.cpp
lsp/LanguageServer.h
lsp/FileRepository.cpp lsp/FileRepository.cpp
lsp/FileRepository.h lsp/FileRepository.h
lsp/GotoDefinition.cpp
lsp/GotoDefinition.h
lsp/HandlerBase.cpp
lsp/HandlerBase.h
lsp/LanguageServer.cpp
lsp/LanguageServer.h
lsp/Transport.cpp lsp/Transport.cpp
lsp/Transport.h lsp/Transport.h
lsp/Utils.cpp
lsp/Utils.h
parsing/DocStringParser.cpp parsing/DocStringParser.cpp
parsing/DocStringParser.h parsing/DocStringParser.h
parsing/Parser.cpp parsing/Parser.cpp

View File

@ -129,7 +129,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
// Propagate changes to all exits and queue them for traversal, if needed. // Propagate changes to all exits and queue them for traversal, if needed.
for (auto const& exit: currentNode->exits) for (auto const& exit: currentNode->exits)
if ( if (
auto exists = valueOrNullptr(nodeInfos, exit); auto exists = util::valueOrNullptr(nodeInfos, exit);
nodeInfos[exit].propagateFrom(nodeInfo) || !exists nodeInfos[exit].propagateFrom(nodeInfo) || !exists
) )
nodesToTraverse.insert(exit); nodesToTraverse.insert(exit);

View File

@ -457,8 +457,7 @@ void ControlFlowBuilder::operator()(yul::Switch const& _switch)
} }
mergeFlow(nodes); mergeFlow(nodes);
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; }); if (!hasDefaultCase(_switch))
if (!hasDefault)
connect(beforeSwitch, m_currentNode); connect(beforeSwitch, m_currentNode);
} }

View File

@ -57,7 +57,7 @@ void ControlFlowRevertPruner::run()
void ControlFlowRevertPruner::findRevertStates() void ControlFlowRevertPruner::findRevertStates()
{ {
std::set<CFG::FunctionContractTuple> pendingFunctions = keys(m_functions); std::set<CFG::FunctionContractTuple> pendingFunctions = util::keys(m_functions);
// We interrupt the search whenever we encounter a call to a function with (yet) unknown // We interrupt the search whenever we encounter a call to a function with (yet) unknown
// revert behaviour. The ``wakeUp`` data structure contains information about which // revert behaviour. The ``wakeUp`` data structure contains information about which
// searches to restart once we know about the behaviour. // searches to restart once we know about the behaviour.

View File

@ -25,6 +25,7 @@
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <libsolutil/Algorithms.h> #include <libsolutil/Algorithms.h>
#include <libsolutil/Visitor.h>
#include <range/v3/view/transform.hpp> #include <range/v3/view/transform.hpp>
@ -451,12 +452,39 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor) bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor)
{ {
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>( if (_usingFor.usesBraces())
_usingFor.libraryName().annotation().referencedDeclaration {
); for (ASTPointer<IdentifierPath> const& function: _usingFor.functionsOrLibrary())
if (auto functionDefinition = dynamic_cast<FunctionDefinition const*>(function->annotation().referencedDeclaration))
{
if (!functionDefinition->isFree() && !(
dynamic_cast<ContractDefinition const*>(functionDefinition->scope()) &&
dynamic_cast<ContractDefinition const*>(functionDefinition->scope())->isLibrary()
))
m_errorReporter.typeError(
4167_error,
function->location(),
"Only file-level functions and library functions can be bound to a type in a \"using\" statement"
);
}
else
m_errorReporter.fatalTypeError(8187_error, function->location(), "Expected function name." );
}
else
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
m_errorReporter.fatalTypeError(
4357_error,
_usingFor.functionsOrLibrary().front()->location(),
"Library name expected. If you want to attach a function, use '{...}'."
);
}
if (!library || !library->isLibrary()) // We do not visit _usingFor.functions() because it will lead to an error since
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected."); // library names cannot be mentioned stand-alone.
if (_usingFor.typeName()) if (_usingFor.typeName())
_usingFor.typeName()->accept(*this); _usingFor.typeName()->accept(*this);

View File

@ -97,7 +97,7 @@ CallGraph FunctionCallGraphBuilder::buildDeployedGraph(
// assigned to state variables and as such may be reachable after deployment as well. // assigned to state variables and as such may be reachable after deployment as well.
builder.m_currentNode = CallGraph::SpecialNode::InternalDispatch; builder.m_currentNode = CallGraph::SpecialNode::InternalDispatch;
set<CallGraph::Node, CallGraph::CompareByID> defaultNode; set<CallGraph::Node, CallGraph::CompareByID> defaultNode;
for (CallGraph::Node const& dispatchTarget: valueOrDefault(_creationGraph.edges, CallGraph::SpecialNode::InternalDispatch, defaultNode)) for (CallGraph::Node const& dispatchTarget: util::valueOrDefault(_creationGraph.edges, CallGraph::SpecialNode::InternalDispatch, defaultNode))
{ {
solAssert(!holds_alternative<CallGraph::SpecialNode>(dispatchTarget), ""); solAssert(!holds_alternative<CallGraph::SpecialNode>(dispatchTarget), "");
solAssert(get<CallableDeclaration const*>(dispatchTarget) != nullptr, ""); solAssert(get<CallableDeclaration const*>(dispatchTarget) != nullptr, "");

View File

@ -411,12 +411,12 @@ struct ReservedErrorSelector: public PostTypeChecker::Checker
); );
else else
{ {
uint32_t selector = selectorFromSignature32(_error.functionType(true)->externalSignature()); uint32_t selector = util::selectorFromSignature32(_error.functionType(true)->externalSignature());
if (selector == 0 || ~selector == 0) if (selector == 0 || ~selector == 0)
m_errorReporter.syntaxError( m_errorReporter.syntaxError(
2855_error, 2855_error,
_error.location(), _error.location(),
"The selector 0x" + toHex(toCompactBigEndian(selector, 4)) + " is reserved. Please rename the error to avoid the collision." "The selector 0x" + util::toHex(toCompactBigEndian(selector, 4)) + " is reserved. Please rename the error to avoid the collision."
); );
} }
} }

View File

@ -52,7 +52,7 @@ bool PostTypeContractLevelChecker::check(ContractDefinition const& _contract)
for (ErrorDefinition const* error: _contract.interfaceErrors()) for (ErrorDefinition const* error: _contract.interfaceErrors())
{ {
string signature = error->functionType(true)->externalSignature(); string signature = error->functionType(true)->externalSignature();
uint32_t hash = selectorFromSignature32(signature); uint32_t hash = util::selectorFromSignature32(signature);
// Fail if there is a different signature for the same hash. // Fail if there is a different signature for the same hash.
if (!errorHashes[hash].empty() && !errorHashes[hash].count(signature)) if (!errorHashes[hash].empty() && !errorHashes[hash].count(signature))
{ {

View File

@ -403,6 +403,42 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
m_currentContractKind = std::nullopt; m_currentContractKind = std::nullopt;
} }
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
{
if (!m_currentContractKind && !_usingFor.typeName())
m_errorReporter.syntaxError(
8118_error,
_usingFor.location(),
"The type has to be specified explicitly at file level (cannot use '*')."
);
else if (_usingFor.usesBraces() && !_usingFor.typeName())
m_errorReporter.syntaxError(
3349_error,
_usingFor.location(),
"The type has to be specified explicitly when attaching specific functions."
);
if (_usingFor.global() && !_usingFor.typeName())
m_errorReporter.syntaxError(
2854_error,
_usingFor.location(),
"Can only globally bind functions to specific types."
);
if (_usingFor.global() && m_currentContractKind)
m_errorReporter.syntaxError(
3367_error,
_usingFor.location(),
"\"global\" can only be used at file level."
);
if (m_currentContractKind == ContractKind::Interface)
m_errorReporter.syntaxError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
);
return true;
}
bool SyntaxChecker::visit(FunctionDefinition const& _function) bool SyntaxChecker::visit(FunctionDefinition const& _function)
{ {
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), ""); solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");

View File

@ -88,6 +88,9 @@ private:
bool visit(ContractDefinition const& _contract) override; bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override; void endVisit(ContractDefinition const& _contract) override;
bool visit(UsingForDirective const& _usingFor) override;
bool visit(FunctionDefinition const& _function) override; bool visit(FunctionDefinition const& _function) override;
bool visit(FunctionTypeName const& _node) override; bool visit(FunctionTypeName const& _node) override;

View File

@ -35,6 +35,7 @@
#include <libsolutil/Algorithms.h> #include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
#include <libsolutil/Views.h> #include <libsolutil/Views.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
@ -267,6 +268,11 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
return {TypeProvider::meta(dynamic_cast<TypeType const&>(*firstArgType).actualType())}; return {TypeProvider::meta(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
} }
bool TypeChecker::visit(ImportDirective const&)
{
return false;
}
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{ {
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
@ -659,7 +665,7 @@ void TypeChecker::visitManually(
if (auto const* modifierContract = dynamic_cast<ContractDefinition const*>(modifierDecl->scope())) if (auto const* modifierContract = dynamic_cast<ContractDefinition const*>(modifierDecl->scope()))
if (m_currentContract) if (m_currentContract)
{ {
if (!contains(m_currentContract->annotation().linearizedBaseContracts, modifierContract)) if (!util::contains(m_currentContract->annotation().linearizedBaseContracts, modifierContract))
m_errorReporter.typeError( m_errorReporter.typeError(
9428_error, 9428_error,
_modifier.location(), _modifier.location(),
@ -2143,7 +2149,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
functionPointerType->declaration().scope() == m_currentContract functionPointerType->declaration().scope() == m_currentContract
) )
msg += " Did you forget to prefix \"this.\"?"; msg += " Did you forget to prefix \"this.\"?";
else if (contains( else if (util::contains(
m_currentContract->annotation().linearizedBaseContracts, m_currentContract->annotation().linearizedBaseContracts,
functionPointerType->declaration().scope() functionPointerType->declaration().scope()
) && functionPointerType->declaration().scope() != m_currentContract) ) && functionPointerType->declaration().scope() != m_currentContract)
@ -2204,9 +2210,9 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
"Cannot implicitly convert component at position " + "Cannot implicitly convert component at position " +
to_string(i) + to_string(i) +
" from \"" + " from \"" +
argType.canonicalName() + argType.toString() +
"\" to \"" + "\" to \"" +
functionPointerType->parameterTypes()[i]->canonicalName() + functionPointerType->parameterTypes()[i]->toString() +
"\"" + "\"" +
(result.message().empty() ? "." : ": " + result.message()) (result.message().empty() ? "." : ": " + result.message())
); );
@ -3635,12 +3641,89 @@ void TypeChecker::endVisit(Literal const& _literal)
void TypeChecker::endVisit(UsingForDirective const& _usingFor) void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{ {
if (m_currentContract->isInterface()) if (!_usingFor.usesBraces())
m_errorReporter.typeError( {
9088_error, solAssert(_usingFor.functionsOrLibrary().size() == 1);
_usingFor.location(), ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
"The \"using for\" directive is not allowed inside interfaces." _usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
); );
solAssert(library && library->isLibrary());
// No type checking for libraries
return;
}
if (!_usingFor.typeName())
{
solAssert(m_errorReporter.hasErrors());
return;
}
solAssert(_usingFor.typeName()->annotation().type);
Type const* normalizedType = TypeProvider::withLocationIfReference(
DataLocation::Storage,
_usingFor.typeName()->annotation().type
);
solAssert(normalizedType);
if (_usingFor.global())
{
if (m_currentContract)
solAssert(m_errorReporter.hasErrors());
if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition())
{
if (typeDefinition->scope() != m_currentSourceUnit)
m_errorReporter.typeError(
4117_error,
_usingFor.location(),
"Can only use \"global\" with types defined in the same source unit at file level."
);
}
else
m_errorReporter.typeError(
8841_error,
_usingFor.location(),
"Can only use \"global\" with user-defined types."
);
}
for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
{
solAssert(path->annotation().referencedDeclaration);
FunctionDefinition const& functionDefinition =
dynamic_cast<FunctionDefinition const&>(*path->annotation().referencedDeclaration);
solAssert(functionDefinition.type());
if (functionDefinition.parameters().empty())
m_errorReporter.fatalTypeError(
4731_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" " +
"does not have any parameters, and therefore cannot be bound to the type \"" +
(normalizedType ? normalizedType->toString(true) : "*") + "\"."
);
FunctionType const* functionType = dynamic_cast<FunctionType const&>(*functionDefinition.type()).asBoundFunction();
solAssert(functionType && functionType->selfType(), "");
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionType->selfType())
);
if (!result)
m_errorReporter.typeError(
3100_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
"cannot be bound to the type \"" + _usingFor.typeName()->annotation().type->toString() +
"\" because the type cannot be implicitly converted to the first argument" +
" of the function (\"" + functionType->selfType()->toString() + "\")" +
(
result.message().empty() ?
"." :
": " + result.message()
)
);
}
} }
void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable) void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)

View File

@ -125,6 +125,8 @@ private:
FunctionType const* _functionType FunctionType const* _functionType
); );
bool visit(ImportDirective const&) override;
void endVisit(InheritanceSpecifier const& _inheritance) override; void endVisit(InheritanceSpecifier const& _inheritance) override;
void endVisit(ModifierDefinition const& _modifier) override; void endVisit(ModifierDefinition const& _modifier) override;
bool visit(FunctionDefinition const& _function) override; bool visit(FunctionDefinition const& _function) override;

View File

@ -134,6 +134,11 @@ bool ViewPureChecker::check()
return !m_errors; return !m_errors;
} }
bool ViewPureChecker::visit(ImportDirective const&)
{
return false;
}
bool ViewPureChecker::visit(FunctionDefinition const& _funDef) bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
{ {
solAssert(!m_currentFunction, ""); solAssert(!m_currentFunction, "");

View File

@ -50,6 +50,8 @@ private:
langutil::SourceLocation location; langutil::SourceLocation location;
}; };
bool visit(ImportDirective const&) override;
bool visit(FunctionDefinition const& _funDef) override; bool visit(FunctionDefinition const& _funDef) override;
void endVisit(FunctionDefinition const& _funDef) override; void endVisit(FunctionDefinition const& _funDef) override;
bool visit(ModifierDefinition const& _modifierDef) override; bool visit(ModifierDefinition const& _modifierDef) override;

View File

@ -221,7 +221,7 @@ vector<EventDefinition const*> const ContractDefinition::usedInterfaceEvents() c
{ {
solAssert(annotation().creationCallGraph.set(), ""); solAssert(annotation().creationCallGraph.set(), "");
return convertContainer<std::vector<EventDefinition const*>>( return util::convertContainer<std::vector<EventDefinition const*>>(
(*annotation().creationCallGraph)->emittedEvents + (*annotation().creationCallGraph)->emittedEvents +
(*annotation().deployedCallGraph)->emittedEvents (*annotation().deployedCallGraph)->emittedEvents
); );
@ -239,7 +239,7 @@ vector<ErrorDefinition const*> ContractDefinition::interfaceErrors(bool _require
result += result +=
(*annotation().creationCallGraph)->usedErrors + (*annotation().creationCallGraph)->usedErrors +
(*annotation().deployedCallGraph)->usedErrors; (*annotation().deployedCallGraph)->usedErrors;
return convertContainer<vector<ErrorDefinition const*>>(move(result)); return util::convertContainer<vector<ErrorDefinition const*>>(move(result));
} }
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const

View File

@ -33,6 +33,7 @@
#include <libevmasm/Instruction.h> #include <libevmasm/Instruction.h>
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h> #include <libsolutil/LazyInit.h>
#include <libsolutil/Visitor.h>
#include <json/json.h> #include <json/json.h>
@ -630,9 +631,20 @@ private:
}; };
/** /**
* `using LibraryName for uint` will attach all functions from the library LibraryName * Using for directive:
* to `uint` if the first parameter matches the type. `using LibraryName for *` attaches *
* the function to any matching type. * 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`
* 2. `using LibraryName for *` attaches to all types.
* 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ...,
* `fn`, respectively to `T`.
*
* For version 3, T has to be implicitly convertible to the first parameter type of
* all functions, and this is checked at the point of the using statement. For versions 1 and
* 2, this check is only done when a function is called.
*
* Finally, `using {f1, f2, ..., fn} for T global` is also valid at file level, as long as T is
* a user-defined type defined in the same file at file level. In this case, the methods are
* attached to all objects of that type regardless of scope.
*/ */
class UsingForDirective: public ASTNode class UsingForDirective: public ASTNode
{ {
@ -640,24 +652,36 @@ public:
UsingForDirective( UsingForDirective(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<IdentifierPath> _libraryName, std::vector<ASTPointer<IdentifierPath>> _functions,
ASTPointer<TypeName> _typeName bool _usesBraces,
ASTPointer<TypeName> _typeName,
bool _global
): ):
ASTNode(_id, _location), m_libraryName(std::move(_libraryName)), m_typeName(std::move(_typeName)) ASTNode(_id, _location),
m_functions(_functions),
m_usesBraces(_usesBraces),
m_typeName(std::move(_typeName)),
m_global{_global}
{ {
solAssert(m_libraryName != nullptr, "Name cannot be null.");
} }
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
IdentifierPath const& libraryName() const { return *m_libraryName; }
/// @returns the type name the library is attached to, null for `*`. /// @returns the type name the library is attached to, null for `*`.
TypeName const* typeName() const { return m_typeName.get(); } TypeName const* typeName() const { return m_typeName.get(); }
/// @returns a list of functions or the single library.
std::vector<ASTPointer<IdentifierPath>> const& functionsOrLibrary() const { return m_functions; }
bool usesBraces() const { return m_usesBraces; }
bool global() const { return m_global; }
private: private:
ASTPointer<IdentifierPath> m_libraryName; /// Either the single library or a list of functions.
std::vector<ASTPointer<IdentifierPath>> m_functions;
bool m_usesBraces;
ASTPointer<TypeName> m_typeName; ASTPointer<TypeName> m_typeName;
bool m_global = false;
}; };
class StructDefinition: public Declaration, public ScopeOpener class StructDefinition: public Declaration, public ScopeOpener

View File

@ -47,7 +47,6 @@ namespace solidity::frontend
class Type; class Type;
class ArrayType; class ArrayType;
using namespace util;
struct CallGraph; struct CallGraph;
@ -91,13 +90,13 @@ struct StructurallyDocumentedAnnotation
struct SourceUnitAnnotation: ASTAnnotation struct SourceUnitAnnotation: ASTAnnotation
{ {
/// The "absolute" (in the compiler sense) path of this source unit. /// The "absolute" (in the compiler sense) path of this source unit.
SetOnce<std::string> path; util::SetOnce<std::string> path;
/// The exported symbols (all global symbols). /// The exported symbols (all global symbols).
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols; util::SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
/// Experimental features. /// Experimental features.
std::set<ExperimentalFeature> experimentalFeatures; std::set<ExperimentalFeature> experimentalFeatures;
/// Using the new ABI coder. Set to `false` if using ABI coder v1. /// Using the new ABI coder. Set to `false` if using ABI coder v1.
SetOnce<bool> useABICoderV2; util::SetOnce<bool> useABICoderV2;
}; };
struct ScopableAnnotation struct ScopableAnnotation
@ -127,7 +126,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
struct ImportAnnotation: DeclarationAnnotation struct ImportAnnotation: DeclarationAnnotation
{ {
/// The absolute path of the source unit to import. /// The absolute path of the source unit to import.
SetOnce<std::string> absolutePath; util::SetOnce<std::string> absolutePath;
/// The actual source unit. /// The actual source unit.
SourceUnit const* sourceUnit = nullptr; SourceUnit const* sourceUnit = nullptr;
}; };
@ -135,7 +134,7 @@ struct ImportAnnotation: DeclarationAnnotation
struct TypeDeclarationAnnotation: DeclarationAnnotation struct TypeDeclarationAnnotation: DeclarationAnnotation
{ {
/// The name of this type, prefixed by proper namespaces if globally accessible. /// The name of this type, prefixed by proper namespaces if globally accessible.
SetOnce<std::string> canonicalName; util::SetOnce<std::string> canonicalName;
}; };
struct StructDeclarationAnnotation: TypeDeclarationAnnotation struct StructDeclarationAnnotation: TypeDeclarationAnnotation
@ -162,9 +161,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu
/// These can either be inheritance specifiers or modifier invocations. /// These can either be inheritance specifiers or modifier invocations.
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments; std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
/// A graph with edges representing calls between functions that may happen during contract construction. /// A graph with edges representing calls between functions that may happen during contract construction.
SetOnce<std::shared_ptr<CallGraph const>> creationCallGraph; util::SetOnce<std::shared_ptr<CallGraph const>> creationCallGraph;
/// A graph with edges representing calls between functions that may happen in a deployed contract. /// A graph with edges representing calls between functions that may happen in a deployed contract.
SetOnce<std::shared_ptr<CallGraph const>> deployedCallGraph; util::SetOnce<std::shared_ptr<CallGraph const>> deployedCallGraph;
/// List of contracts whose bytecode is referenced by this contract, e.g. through "new". /// List of contracts whose bytecode is referenced by this contract, e.g. through "new".
/// The Value represents the ast node that referenced the contract. /// The Value represents the ast node that referenced the contract.
@ -223,7 +222,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation
/// True, if the assembly block was annotated to be memory-safe. /// True, if the assembly block was annotated to be memory-safe.
bool markedMemorySafe = false; bool markedMemorySafe = false;
/// True, if the assembly block involves any memory opcode or assigns to variables in memory. /// True, if the assembly block involves any memory opcode or assigns to variables in memory.
SetOnce<bool> hasMemoryEffects; util::SetOnce<bool> hasMemoryEffects;
}; };
struct BlockAnnotation: StatementAnnotation, ScopableAnnotation struct BlockAnnotation: StatementAnnotation, ScopableAnnotation
@ -256,7 +255,7 @@ struct IdentifierPathAnnotation: ASTAnnotation
/// Referenced declaration, set during reference resolution stage. /// Referenced declaration, set during reference resolution stage.
Declaration const* referencedDeclaration = nullptr; Declaration const* referencedDeclaration = nullptr;
/// What kind of lookup needs to be done (static, virtual, super) find the declaration. /// What kind of lookup needs to be done (static, virtual, super) find the declaration.
SetOnce<VirtualLookup> requiredLookup; util::SetOnce<VirtualLookup> requiredLookup;
}; };
struct ExpressionAnnotation: ASTAnnotation struct ExpressionAnnotation: ASTAnnotation
@ -264,11 +263,11 @@ struct ExpressionAnnotation: ASTAnnotation
/// Inferred type of the expression. /// Inferred type of the expression.
Type const* type = nullptr; Type const* type = nullptr;
/// Whether the expression is a constant variable /// Whether the expression is a constant variable
SetOnce<bool> isConstant; util::SetOnce<bool> isConstant;
/// Whether the expression is pure, i.e. compile-time constant. /// Whether the expression is pure, i.e. compile-time constant.
SetOnce<bool> isPure; util::SetOnce<bool> isPure;
/// Whether it is an LValue (i.e. something that can be assigned to). /// Whether it is an LValue (i.e. something that can be assigned to).
SetOnce<bool> isLValue; util::SetOnce<bool> isLValue;
/// Whether the expression is used in a context where the LValue is actually required. /// Whether the expression is used in a context where the LValue is actually required.
bool willBeWrittenTo = false; bool willBeWrittenTo = false;
/// Whether the expression is an lvalue that is only assigned. /// Whether the expression is an lvalue that is only assigned.
@ -295,7 +294,7 @@ struct IdentifierAnnotation: ExpressionAnnotation
/// Referenced declaration, set at latest during overload resolution stage. /// Referenced declaration, set at latest during overload resolution stage.
Declaration const* referencedDeclaration = nullptr; Declaration const* referencedDeclaration = nullptr;
/// What kind of lookup needs to be done (static, virtual, super) find the declaration. /// What kind of lookup needs to be done (static, virtual, super) find the declaration.
SetOnce<VirtualLookup> requiredLookup; util::SetOnce<VirtualLookup> requiredLookup;
/// List of possible declarations it could refer to (can contain duplicates). /// List of possible declarations it could refer to (can contain duplicates).
std::vector<Declaration const*> candidateDeclarations; std::vector<Declaration const*> candidateDeclarations;
/// List of possible declarations it could refer to. /// List of possible declarations it could refer to.
@ -307,7 +306,7 @@ struct MemberAccessAnnotation: ExpressionAnnotation
/// Referenced declaration, set at latest during overload resolution stage. /// Referenced declaration, set at latest during overload resolution stage.
Declaration const* referencedDeclaration = nullptr; Declaration const* referencedDeclaration = nullptr;
/// What kind of lookup needs to be done (static, virtual, super) find the declaration. /// What kind of lookup needs to be done (static, virtual, super) find the declaration.
SetOnce<VirtualLookup> requiredLookup; util::SetOnce<VirtualLookup> requiredLookup;
}; };
struct BinaryOperationAnnotation: ExpressionAnnotation struct BinaryOperationAnnotation: ExpressionAnnotation

View File

@ -32,6 +32,7 @@
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h> #include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/Keccak256.h> #include <libsolutil/Keccak256.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
@ -311,10 +312,26 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
bool ASTJsonConverter::visit(UsingForDirective const& _node) bool ASTJsonConverter::visit(UsingForDirective const& _node)
{ {
setJsonNode(_node, "UsingForDirective", { vector<pair<string, Json::Value>> attributes = {
make_pair("libraryName", toJson(_node.libraryName())),
make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue) make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue)
}); };
if (_node.usesBraces())
{
Json::Value functionList;
for (auto const& function: _node.functionsOrLibrary())
{
Json::Value functionNode;
functionNode["function"] = toJson(*function);
functionList.append(move(functionNode));
}
attributes.emplace_back("functionList", move(functionList));
}
else
attributes.emplace_back("libraryName", toJson(*_node.functionsOrLibrary().front()));
attributes.emplace_back("global", _node.global());
setJsonNode(_node, "UsingForDirective", move(attributes));
return false; return false;
} }
@ -505,7 +522,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
_attributes.emplace_back( _attributes.emplace_back(
make_pair( make_pair(
"eventSelector", "eventSelector",
toHex(u256(h256::Arith(util::keccak256(_node.functionType(true)->externalSignature())))) toHex(u256(util::h256::Arith(util::keccak256(_node.functionType(true)->externalSignature()))))
)); ));
setJsonNode(_node, "EventDefinition", std::move(_attributes)); setJsonNode(_node, "EventDefinition", std::move(_attributes));

View File

@ -348,10 +348,19 @@ ASTPointer<InheritanceSpecifier> ASTJsonImporter::createInheritanceSpecifier(Jso
ASTPointer<UsingForDirective> ASTJsonImporter::createUsingForDirective(Json::Value const& _node) ASTPointer<UsingForDirective> ASTJsonImporter::createUsingForDirective(Json::Value const& _node)
{ {
vector<ASTPointer<IdentifierPath>> functions;
if (_node.isMember("libraryName"))
functions.emplace_back(createIdentifierPath(_node["libraryName"]));
else if (_node.isMember("functionList"))
for (Json::Value const& function: _node["functionList"])
functions.emplace_back(createIdentifierPath(function["function"]));
return createASTNode<UsingForDirective>( return createASTNode<UsingForDirective>(
_node, _node,
createIdentifierPath(member(_node, "libraryName")), move(functions),
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"]) !_node.isMember("libraryName"),
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"]),
memberAsBool(_node, "global")
); );
} }

View File

@ -18,12 +18,35 @@
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTUtils.h> #include <libsolidity/ast/ASTUtils.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolutil/Algorithms.h> #include <libsolutil/Algorithms.h>
namespace solidity::frontend namespace solidity::frontend
{ {
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit)
{
ASTNode const* innermostMatch = nullptr;
auto locator = SimpleASTVisitor(
[&](ASTNode const& _node) -> bool
{
// In the AST parent location always covers the whole child location.
// The parent is visited first so to get the innermost node we simply
// take the last one that still contains the offset.
if (!_node.location().containsOffset(_offsetInFile))
return false;
innermostMatch = &_node;
return true;
},
[](ASTNode const&) {}
);
_sourceUnit.accept(locator);
return innermostMatch;
}
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl) bool isConstantVariableRecursive(VariableDeclaration const& _varDecl)
{ {
solAssert(_varDecl.isConstant(), "Constant variable expected"); solAssert(_varDecl.isConstant(), "Constant variable expected");

View File

@ -21,9 +21,11 @@
namespace solidity::frontend namespace solidity::frontend
{ {
class VariableDeclaration; class ASTNode;
class Declaration; class Declaration;
class Expression; class Expression;
class SourceUnit;
class VariableDeclaration;
/// Find the topmost referenced constant variable declaration when the given variable /// Find the topmost referenced constant variable declaration when the given variable
/// declaration value is an identifier. Works only for constant variable declarations. /// declaration value is an identifier. Works only for constant variable declarations.
@ -33,4 +35,7 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
/// Returns true if the constant variable declaration is recursive. /// Returns true if the constant variable declaration is recursive.
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl); bool isConstantVariableRecursive(VariableDeclaration const& _varDecl);
/// Returns the innermost AST node that covers the given location or nullptr if not found.
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit);
} }

View File

@ -194,7 +194,7 @@ void UsingForDirective::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_libraryName->accept(_visitor); listAccept(functionsOrLibrary(), _visitor);
if (m_typeName) if (m_typeName)
m_typeName->accept(_visitor); m_typeName->accept(_visitor);
} }
@ -205,7 +205,7 @@ void UsingForDirective::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_libraryName->accept(_visitor); listAccept(functionsOrLibrary(), _visitor);
if (m_typeName) if (m_typeName)
m_typeName->accept(_visitor); m_typeName->accept(_visitor);
} }

View File

@ -33,7 +33,9 @@
#include <libsolutil/CommonIO.h> #include <libsolutil/CommonIO.h>
#include <libsolutil/FunctionSelector.h> #include <libsolutil/FunctionSelector.h>
#include <libsolutil/Keccak256.h> #include <libsolutil/Keccak256.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/UTF8.h> #include <libsolutil/UTF8.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
@ -330,30 +332,55 @@ Type const* Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _scope) MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _scope)
{ {
vector<UsingForDirective const*> usingForDirectives; vector<UsingForDirective const*> usingForDirectives;
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope)) SourceUnit const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope);
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes()); if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope))
else if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope)) {
usingForDirectives += sourceUnit = &contract->sourceUnit();
contract->usingForDirectives() + usingForDirectives += contract->usingForDirectives();
ASTNode::filteredNodes<UsingForDirective>(contract->sourceUnit().nodes()); }
else else
solAssert(false, ""); solAssert(sourceUnit, "");
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());
if (Declaration const* typeDefinition = _type.typeDefinition())
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(typeDefinition->scope()))
for (auto usingFor: ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes()))
// We do not yet compare the type name because of normalization.
if (usingFor->global() && usingFor->typeName())
usingForDirectives.emplace_back(usingFor);
// Normalise data location of type. // Normalise data location of type.
DataLocation typeLocation = DataLocation::Storage; DataLocation typeLocation = DataLocation::Storage;
if (auto refType = dynamic_cast<ReferenceType const*>(&_type)) if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
typeLocation = refType->location(); typeLocation = refType->location();
set<Declaration const*> seenFunctions;
MemberList::MemberMap members; MemberList::MemberMap members;
set<pair<string, Declaration const*>> seenFunctions;
auto addFunction = [&](FunctionDefinition const& _function, optional<string> _name = {})
{
if (!_name)
_name = _function.name();
Type const* functionType =
_function.libraryFunction() ? _function.typeViaContractName() : _function.type();
solAssert(functionType, "");
FunctionType const* asBoundFunction =
dynamic_cast<FunctionType const&>(*functionType).asBoundFunction();
solAssert(asBoundFunction, "");
if (_type.isImplicitlyConvertibleTo(*asBoundFunction->selfType()))
if (seenFunctions.insert(make_pair(*_name, &_function)).second)
members.emplace_back(&_function, asBoundFunction, *_name);
};
for (UsingForDirective const* ufd: usingForDirectives) for (UsingForDirective const* ufd: usingForDirectives)
{ {
// Convert both types to pointers for comparison to see if the `using for` // Convert both types to pointers for comparison to see if the `using for`
// directive applies. // directive applies.
// Further down, we check more detailed for each function if `_type` is // Further down, we check more detailed for each function if `_type` is
// convertible to the function parameter type. // convertible to the function parameter type.
if (ufd->typeName() && if (
ufd->typeName() &&
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) != *TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
*TypeProvider::withLocationIfReference( *TypeProvider::withLocationIfReference(
typeLocation, typeLocation,
@ -362,20 +389,28 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc
) )
) )
continue; continue;
auto const& library = dynamic_cast<ContractDefinition const&>(
*ufd->libraryName().annotation().referencedDeclaration for (auto const& pathPointer: ufd->functionsOrLibrary())
);
for (FunctionDefinition const* function: library.definedFunctions())
{ {
if (!function->isOrdinary() || !function->isVisibleAsLibraryMember() || seenFunctions.count(function)) solAssert(pathPointer);
continue; Declaration const* declaration = pathPointer->annotation().referencedDeclaration;
seenFunctions.insert(function); solAssert(declaration);
if (function->parameters().empty())
continue; if (ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(declaration))
FunctionTypePointer fun = {
dynamic_cast<FunctionType const&>(*function->typeViaContractName()).asBoundFunction(); solAssert(library->isLibrary());
if (_type.isImplicitlyConvertibleTo(*fun->selfType())) for (FunctionDefinition const* function: library->definedFunctions())
members.emplace_back(function, fun); {
if (!function->isOrdinary() || !function->isVisibleAsLibraryMember() || function->parameters().empty())
continue;
addFunction(*function);
}
}
else
addFunction(
dynamic_cast<FunctionDefinition const&>(*declaration),
pathPointer->path().back()
);
} }
} }
@ -781,8 +816,8 @@ tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
if (radixPoint != _value.end()) if (radixPoint != _value.end())
{ {
if ( if (
!all_of(radixPoint + 1, _value.end(), ::isdigit) || !all_of(radixPoint + 1, _value.end(), util::isDigit) ||
!all_of(_value.begin(), radixPoint, ::isdigit) !all_of(_value.begin(), radixPoint, util::isDigit)
) )
return make_tuple(false, rational(0)); return make_tuple(false, rational(0));
@ -2344,6 +2379,11 @@ TypeResult StructType::interfaceType(bool _inLibrary) const
return *m_interfaceType_library; return *m_interfaceType_library;
} }
Declaration const* StructType::typeDefinition() const
{
return &structDefinition();
}
BoolResult StructType::validForLocation(DataLocation _loc) const BoolResult StructType::validForLocation(DataLocation _loc) const
{ {
for (auto const& member: m_struct.members()) for (auto const& member: m_struct.members())
@ -2476,6 +2516,11 @@ Type const* EnumType::encodingType() const
return TypeProvider::uint(8); return TypeProvider::uint(8);
} }
Declaration const* EnumType::typeDefinition() const
{
return &enumDefinition();
}
TypeResult EnumType::unaryOperatorResult(Token _operator) const TypeResult EnumType::unaryOperatorResult(Token _operator) const
{ {
return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr;
@ -2544,6 +2589,11 @@ Type const& UserDefinedValueType::underlyingType() const
return *type; return *type;
} }
Declaration const* UserDefinedValueType::typeDefinition() const
{
return &m_definition;
}
string UserDefinedValueType::richIdentifier() const string UserDefinedValueType::richIdentifier() const
{ {
return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id()); return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id());

View File

@ -369,6 +369,10 @@ public:
/// are returned without modification. /// are returned without modification.
virtual TypeResult interfaceType(bool /*_inLibrary*/) const { return nullptr; } virtual TypeResult interfaceType(bool /*_inLibrary*/) const { return nullptr; }
/// @returns the declaration of a user defined type (enum, struct, user defined value type).
/// Returns nullptr otherwise.
virtual Declaration const* typeDefinition() const { return nullptr; }
/// Clears all internally cached values (if any). /// Clears all internally cached values (if any).
virtual void clearCache() const; virtual void clearCache() const;
@ -1004,6 +1008,8 @@ public:
Type const* encodingType() const override; Type const* encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override; TypeResult interfaceType(bool _inLibrary) const override;
Declaration const* typeDefinition() const override;
BoolResult validForLocation(DataLocation _loc) const override; BoolResult validForLocation(DataLocation _loc) const override;
bool recursive() const; bool recursive() const;
@ -1069,6 +1075,8 @@ public:
return _inLibrary ? this : encodingType(); return _inLibrary ? this : encodingType();
} }
Declaration const* typeDefinition() const override;
EnumDefinition const& enumDefinition() const { return m_enum; } EnumDefinition const& enumDefinition() const { return m_enum; }
/// @returns the value that the string has in the Enum /// @returns the value that the string has in the Enum
unsigned int memberValue(ASTString const& _member) const; unsigned int memberValue(ASTString const& _member) const;
@ -1101,6 +1109,9 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override { return &underlyingType(); } Type const* encodingType() const override { return &underlyingType(); }
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); } TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
Declaration const* typeDefinition() const override;
std::string richIdentifier() const override; std::string richIdentifier() const override;
bool operator==(Type const& _other) const override; bool operator==(Type const& _other) const override;

View File

@ -403,7 +403,7 @@ void CompilerContext::appendInlineAssembly(
{ {
if (_insideFunction) if (_insideFunction)
return false; return false;
return contains(_localVariables, _identifier.name.str()); return util::contains(_localVariables, _identifier.name.str());
}; };
identifierAccess.generateCode = [&]( identifierAccess.generateCode = [&](
yul::Identifier const& _identifier, yul::Identifier const& _identifier,
@ -572,8 +572,7 @@ void CompilerContext::updateSourceLocation()
evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings)
{ {
// Constructing it this way so that we notice changes in the fields. // Constructing it this way so that we notice changes in the fields.
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, m_evmVersion, 0}; evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0};
asmSettings.isCreation = true;
asmSettings.runInliner = _settings.runInliner; asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole; asmSettings.runPeephole = _settings.runPeephole;

View File

@ -65,7 +65,7 @@ public:
RevertStrings _revertStrings, RevertStrings _revertStrings,
CompilerContext* _runtimeContext = nullptr CompilerContext* _runtimeContext = nullptr
): ):
m_asm(std::make_shared<evmasm::Assembly>()), m_asm(std::make_shared<evmasm::Assembly>(_runtimeContext != nullptr, std::string{})),
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_reservedMemory{0}, m_reservedMemory{0},

View File

@ -235,7 +235,7 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
m_context.pushSubroutineOffset(m_context.runtimeSub()); m_context.pushSubroutineOffset(m_context.runtimeSub());
// This code replaces the address added by appendDeployTimeAddress(). // This code replaces the address added by appendDeployTimeAddress().
m_context.appendInlineAssembly( m_context.appendInlineAssembly(
Whiskers(R"( util::Whiskers(R"(
{ {
// If code starts at 11, an mstore(0) writes to the full PUSH20 plus data // If code starts at 11, an mstore(0) writes to the full PUSH20 plus data
// without the need for a shift. // without the need for a shift.
@ -672,7 +672,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_function.location()) << errinfo_sourceLocation(_function.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1)) while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
if (stackLayout.back() < 0) if (stackLayout.back() < 0)
@ -842,7 +842,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
_assembly.appendInstruction(dupInstruction(stackDiff)); _assembly.appendInstruction(dupInstruction(stackDiff));
} }
@ -916,7 +916,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") util::errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
); );
_assembly.appendInstruction(swapInstruction(stackDiff)); _assembly.appendInstruction(swapInstruction(stackDiff));
_assembly.appendInstruction(Instruction::POP); _assembly.appendInstruction(Instruction::POP);
@ -1045,7 +1045,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
solAssert(m_context.evmVersion().supportsReturndata(), ""); solAssert(m_context.evmVersion().supportsReturndata(), "");
// stack: <selector> // stack: <selector>
m_context << Instruction::DUP1 << selectorFromSignature32("Error(string)") << Instruction::EQ; m_context << Instruction::DUP1 << util::selectorFromSignature32("Error(string)") << Instruction::EQ;
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(panicTag); m_context.appendConditionalJumpTo(panicTag);
m_context << Instruction::POP; // remove selector m_context << Instruction::POP; // remove selector
@ -1077,7 +1077,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
solAssert(m_context.evmVersion().supportsReturndata(), ""); solAssert(m_context.evmVersion().supportsReturndata(), "");
// stack: <selector> // stack: <selector>
m_context << selectorFromSignature32("Panic(uint256)") << Instruction::EQ; m_context << util::selectorFromSignature32("Panic(uint256)") << Instruction::EQ;
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(fallbackTag); m_context.appendConditionalJumpTo(fallbackTag);

View File

@ -268,7 +268,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_varDecl.location()) << errinfo_sourceLocation(_varDecl.location()) <<
errinfo_comment("Stack too deep.") util::errinfo_comment("Stack too deep.")
); );
m_context << dupInstruction(retSizeOnStack + 1); m_context << dupInstruction(retSizeOnStack + 1);
m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction);
@ -350,7 +350,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_assignment.location()) << errinfo_sourceLocation(_assignment.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
// value [lvalue_ref] updated_value // value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i) for (unsigned i = 0; i < itemSize; ++i)
@ -1258,6 +1258,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
function.kind() == FunctionType::Kind::ABIEncodeWithSignature; function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes; TypePointers argumentTypes;
TypePointers targetTypes;
ASTNode::listAccept(arguments, *this); ASTNode::listAccept(arguments, *this);
@ -1265,14 +1266,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
solAssert(arguments.size() == 2); solAssert(arguments.size() == 2);
auto const functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type);
solAssert(functionPtr);
// Account for tuples with one component which become that component // Account for tuples with one component which become that component
if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type)) if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type))
argumentTypes = tupleType->components(); argumentTypes = tupleType->components();
else else
argumentTypes.emplace_back(arguments[1]->annotation().type); argumentTypes.emplace_back(arguments[1]->annotation().type);
auto functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type);
solAssert(functionPtr);
functionPtr = functionPtr->asExternallyCallableFunction(false);
solAssert(functionPtr);
targetTypes = functionPtr->parameterTypes();
} }
else else
for (unsigned i = 0; i < arguments.size(); ++i) for (unsigned i = 0; i < arguments.size(); ++i)
@ -1292,12 +1296,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (isPacked) if (isPacked)
{ {
solAssert(!function.padArguments(), ""); solAssert(!function.padArguments(), "");
utils().packedEncode(argumentTypes, TypePointers()); utils().packedEncode(argumentTypes, targetTypes);
} }
else else
{ {
solAssert(function.padArguments(), ""); solAssert(function.padArguments(), "");
utils().abiEncode(argumentTypes, TypePointers()); utils().abiEncode(argumentTypes, targetTypes);
} }
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
// stack: [<selector/functionPointer/signature>] <data_encoding_area_end> <bytes_memory_ptr> // stack: [<selector/functionPointer/signature>] <data_encoding_area_end> <bytes_memory_ptr>
@ -1452,7 +1456,7 @@ bool ExpressionCompiler::visit(FunctionCallOptions const& _functionCallOptions)
solAssert(false, "Unexpected option name!"); solAssert(false, "Unexpected option name!");
acceptAndConvert(*_functionCallOptions.options()[i], *requiredType); acceptAndConvert(*_functionCallOptions.options()[i], *requiredType);
solAssert(!contains(presentOptions, newOption), ""); solAssert(!util::contains(presentOptions, newOption), "");
ptrdiff_t insertPos = presentOptions.end() - lower_bound(presentOptions.begin(), presentOptions.end(), newOption); ptrdiff_t insertPos = presentOptions.end() - lower_bound(presentOptions.begin(), presentOptions.end(), newOption);
utils().moveIntoStack(static_cast<unsigned>(insertPos), 1); utils().moveIntoStack(static_cast<unsigned>(insertPos), 1);
@ -2862,7 +2866,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() BOOST_THROW_EXCEPTION(InternalCompilerError()
<< errinfo_sourceLocation(_expression.location()) << errinfo_sourceLocation(_expression.location())
<< errinfo_comment("Identifier type not supported or identifier not found.")); << util::errinfo_comment("Identifier type not supported or identifier not found."));
} }
void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)

View File

@ -50,7 +50,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch."); solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch.");
for (unsigned i = 0; i < m_size; ++i) for (unsigned i = 0; i < m_size; ++i)
@ -64,7 +64,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
else if (stackDiff > 0) else if (stackDiff > 0)
for (unsigned i = 0; i < m_size; ++i) for (unsigned i = 0; i < m_size; ++i)
@ -436,7 +436,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
InternalCompilerError() InternalCompilerError()
<< errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location)
<< errinfo_comment("Invalid non-value type for assignment.")); << util::errinfo_comment("Invalid non-value type for assignment."));
} }
} }

View File

@ -93,7 +93,7 @@ pair<string, string> IRGenerator::run(
map<ContractDefinition const*, string_view const> const& _otherYulSources map<ContractDefinition const*, string_view const> const& _otherYulSources
) )
{ {
string const ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources)); string ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
yul::AssemblyStack asmStack( yul::AssemblyStack asmStack(
m_evmVersion, m_evmVersion,
@ -113,15 +113,7 @@ pair<string, string> IRGenerator::run(
} }
asmStack.optimize(); asmStack.optimize();
string warning = return {move(ir), asmStack.print(m_context.soliditySourceProvider())};
"/*=====================================================*\n"
" * WARNING *\n"
" * Solidity to Yul compilation is still EXPERIMENTAL *\n"
" * It can result in LOSS OF FUNDS or worse *\n"
" * !USE AT YOUR OWN RISK! *\n"
" *=====================================================*/\n\n";
return {warning + ir, warning + asmStack.print(m_context.soliditySourceProvider())};
} }
string IRGenerator::generate( string IRGenerator::generate(
@ -234,7 +226,7 @@ string IRGenerator::generate(
t("deployedFunctions", m_context.functionCollector().requestedFunctions()); t("deployedFunctions", m_context.functionCollector().requestedFunctions());
t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated())); t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated()));
t("metadataName", yul::Object::metadataName()); t("metadataName", yul::Object::metadataName());
t("cborMetadata", toHex(_cborMetadata)); t("cborMetadata", util::toHex(_cborMetadata));
t("useSrcMapDeployed", formatUseSrcMap(m_context)); t("useSrcMapDeployed", formatUseSrcMap(m_context));
@ -788,7 +780,7 @@ pair<string, map<ContractDefinition const*, vector<string>>> IRGenerator::evalua
{ {
bool operator()(ContractDefinition const* _c1, ContractDefinition const* _c2) const bool operator()(ContractDefinition const* _c1, ContractDefinition const* _c2) const
{ {
solAssert(contains(linearizedBaseContracts, _c1) && contains(linearizedBaseContracts, _c2), ""); solAssert(util::contains(linearizedBaseContracts, _c1) && util::contains(linearizedBaseContracts, _c2), "");
auto it1 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c1); auto it1 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c1);
auto it2 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c2); auto it2 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c2);
return it1 < it2; return it1 < it2;

View File

@ -203,7 +203,7 @@ private:
else else
solAssert(false); solAssert(false);
if (isdigit(value.front())) if (isDigit(value.front()))
return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}}; return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}};
else else
return yul::Identifier{_identifier.debugData, yul::YulString{value}}; return yul::Identifier{_identifier.debugData, yul::YulString{value}};
@ -1160,10 +1160,22 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
for (auto const& argument: argumentsOfEncodeFunction) for (auto const& argument: argumentsOfEncodeFunction)
{ {
argumentTypes.emplace_back(&type(*argument)); argumentTypes.emplace_back(&type(*argument));
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));
argumentVars += IRVariable(*argument).stackSlots(); argumentVars += IRVariable(*argument).stackSlots();
} }
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
auto encodedFunctionType = dynamic_cast<FunctionType const*>(arguments.front()->annotation().type);
solAssert(encodedFunctionType);
encodedFunctionType = encodedFunctionType->asExternallyCallableFunction(false);
solAssert(encodedFunctionType);
targetTypes = encodedFunctionType->parameterTypes();
}
else
for (auto const& argument: argumentsOfEncodeFunction)
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{ {
auto const& selectorType = dynamic_cast<FunctionType const&>(type(*arguments.front())); auto const& selectorType = dynamic_cast<FunctionType const&>(type(*arguments.front()));

View File

@ -33,6 +33,7 @@
#include <libsmtutil/CHCSmtLib2Interface.h> #include <libsmtutil/CHCSmtLib2Interface.h>
#include <liblangutil/CharStreamProvider.h> #include <liblangutil/CharStreamProvider.h>
#include <libsolutil/Algorithms.h> #include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h>
#ifdef HAVE_Z3_DLOPEN #ifdef HAVE_Z3_DLOPEN
#include <z3_version.h> #include <z3_version.h>
@ -1497,7 +1498,7 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
auto const* contract = function->annotation().contract; auto const* contract = function->annotation().contract;
auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
solAssert(kind != FunctionType::Kind::Internal || function->isFree() || (contract && contract->isLibrary()) || contains(hierarchy, contract), ""); solAssert(kind != FunctionType::Kind::Internal || function->isFree() || (contract && contract->isLibrary()) || util::contains(hierarchy, contract), "");
bool usesStaticCall = function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View; bool usesStaticCall = function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View;
@ -1998,9 +1999,9 @@ map<unsigned, vector<unsigned>> CHC::summaryCalls(CHCSolverInterface::CexGraph c
// Predicates that do not have a CALLID have a predicate id at the end of <suffix>, // Predicates that do not have a CALLID have a predicate id at the end of <suffix>,
// so the assertion below should still hold. // so the assertion below should still hold.
auto beg = _s.data(); auto beg = _s.data();
while (beg != _s.data() + _s.size() && !isdigit(*beg)) ++beg; while (beg != _s.data() + _s.size() && !isDigit(*beg)) ++beg;
auto end = beg; auto end = beg;
while (end != _s.data() + _s.size() && isdigit(*end)) ++end; while (end != _s.data() + _s.size() && isDigit(*end)) ++end;
solAssert(beg != end, "Expected to find numerical call or predicate id."); solAssert(beg != end, "Expected to find numerical call or predicate id.");

View File

@ -48,7 +48,7 @@ map<Predicate const*, set<string>> collectInvariants(
map<string, pair<smtutil::Expression, smtutil::Expression>> equalities; map<string, pair<smtutil::Expression, smtutil::Expression>> equalities;
// Collect equalities where one of the sides is a predicate we're interested in. // Collect equalities where one of the sides is a predicate we're interested in.
BreadthFirstSearch<smtutil::Expression const*>{{&_proof}}.run([&](auto&& _expr, auto&& _addChild) { util::BreadthFirstSearch<smtutil::Expression const*>{{&_proof}}.run([&](auto&& _expr, auto&& _addChild) {
if (_expr->name == "=") if (_expr->name == "=")
for (auto const& t: targets) for (auto const& t: targets)
{ {

View File

@ -350,7 +350,7 @@ vector<optional<string>> Predicate::summaryStateValues(vector<smtutil::Expressio
vector<smtutil::Expression> stateArgs(stateFirst, stateLast); vector<smtutil::Expression> stateArgs(stateFirst, stateLast);
solAssert(stateArgs.size() == stateVars->size(), ""); solAssert(stateArgs.size() == stateVars->size(), "");
auto stateTypes = applyMap(*stateVars, [&](auto const& _var) { return _var->type(); }); auto stateTypes = util::applyMap(*stateVars, [&](auto const& _var) { return _var->type(); });
return formatExpressions(stateArgs, stateTypes); return formatExpressions(stateArgs, stateTypes);
} }
@ -412,7 +412,7 @@ pair<vector<optional<string>>, vector<VariableDeclaration const*>> Predicate::lo
auto first = _args.end() - static_cast<int>(localVars.size()); auto first = _args.end() - static_cast<int>(localVars.size());
vector<smtutil::Expression> outValues(first, _args.end()); vector<smtutil::Expression> outValues(first, _args.end());
auto mask = applyMap( auto mask = util::applyMap(
localVars, localVars,
[this](auto _var) { [this](auto _var) {
auto varScope = dynamic_cast<ScopeOpener const*>(_var->scope()); auto varScope = dynamic_cast<ScopeOpener const*>(_var->scope());
@ -422,7 +422,7 @@ pair<vector<optional<string>>, vector<VariableDeclaration const*>> Predicate::lo
auto localVarsInScope = util::filter(localVars, mask); auto localVarsInScope = util::filter(localVars, mask);
auto outValuesInScope = util::filter(outValues, mask); auto outValuesInScope = util::filter(outValues, mask);
auto outTypes = applyMap(localVarsInScope, [](auto _var) { return _var->type(); }); auto outTypes = util::applyMap(localVarsInScope, [](auto _var) { return _var->type(); });
return {formatExpressions(outValuesInScope, outTypes), localVarsInScope}; return {formatExpressions(outValuesInScope, outTypes), localVarsInScope};
} }
@ -496,7 +496,7 @@ optional<string> Predicate::expressionToString(smtutil::Expression const& _expr,
if (_expr.name == "0") if (_expr.name == "0")
return "0x0"; return "0x0";
// For some reason the code below returns "0x" for "0". // For some reason the code below returns "0x" for "0".
return toHex(toCompactBigEndian(bigint(_expr.name)), HexPrefix::Add, HexCase::Lower); return util::toHex(toCompactBigEndian(bigint(_expr.name)), util::HexPrefix::Add, util::HexCase::Lower);
} }
catch (out_of_range const&) catch (out_of_range const&)
{ {

View File

@ -115,6 +115,12 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract)
m_context.popSolver(); m_context.popSolver();
} }
bool SMTEncoder::visit(ImportDirective const&)
{
// do not visit because the identifier therein will confuse us.
return false;
}
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl) void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
{ {
// State variables are handled by the constructor. // State variables are handled by the constructor.
@ -313,7 +319,7 @@ bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{ {
auto const& vars = _assignment.variableNames; auto const& vars = _assignment.variableNames;
for (auto const& identifier: vars) for (auto const& identifier: vars)
if (auto externalInfo = valueOrNullptr(externalReferences, &identifier)) if (auto externalInfo = util::valueOrNullptr(externalReferences, &identifier))
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(externalInfo->declaration)) if (auto varDecl = dynamic_cast<VariableDeclaration const*>(externalInfo->declaration))
assignedVars.insert(varDecl); assignedVars.insert(varDecl);
} }

View File

@ -136,6 +136,7 @@ protected:
// because the order of expression evaluation is undefined // because the order of expression evaluation is undefined
// TODO: or just force a certain order, but people might have a different idea about that. // TODO: or just force a certain order, but people might have a different idea about that.
bool visit(ImportDirective const& _node) override;
bool visit(ContractDefinition const& _node) override; bool visit(ContractDefinition const& _node) override;
void endVisit(ContractDefinition const& _node) override; void endVisit(ContractDefinition const& _node) override;
void endVisit(VariableDeclaration const& _node) override; void endVisit(VariableDeclaration const& _node) override;

View File

@ -211,7 +211,7 @@ void SymbolicState::buildABIFunctions(set<FunctionCall const*> const& _abiFuncti
auto argTypes = [](auto const& _args) { auto argTypes = [](auto const& _args) {
return applyMap(_args, [](auto arg) { return arg->annotation().type; }); return util::applyMap(_args, [](auto arg) { return arg->annotation().type; });
}; };
/// Since each abi.* function may have a different number of input/output parameters, /// Since each abi.* function may have a different number of input/output parameters,

View File

@ -580,7 +580,7 @@ optional<smtutil::Expression> symbolicTypeConversion(frontend::Type const* _from
return smtutil::Expression(size_t(0)); return smtutil::Expression(size_t(0));
auto bytesVec = util::asBytes(strType->value()); auto bytesVec = util::asBytes(strType->value());
bytesVec.resize(fixedBytesType->numBytes(), 0); bytesVec.resize(fixedBytesType->numBytes(), 0);
return smtutil::Expression(u256(toHex(bytesVec, util::HexPrefix::Add))); return smtutil::Expression(u256(util::toHex(bytesVec, util::HexPrefix::Add)));
} }
return std::nullopt; return std::nullopt;

View File

@ -278,7 +278,7 @@ void CompilerStack::setMetadataHash(MetadataHash _metadataHash)
void CompilerStack::selectDebugInfo(DebugInfoSelection _debugInfoSelection) void CompilerStack::selectDebugInfo(DebugInfoSelection _debugInfoSelection)
{ {
if (m_stackState >= CompilationSuccessful) if (m_stackState >= CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must select debug info components before compilation.")); BOOST_THROW_EXCEPTION(CompilerError() << util::errinfo_comment("Must select debug info components before compilation."));
m_debugInfoSelection = _debugInfoSelection; m_debugInfoSelection = _debugInfoSelection;
} }
@ -573,7 +573,7 @@ bool CompilerStack::analyze()
if (noErrors) if (noErrors)
{ {
ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile); ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile);
auto allSources = applyMap(m_sourceOrder, [](Source const* _source) { return _source->ast; }); auto allSources = util::applyMap(m_sourceOrder, [](Source const* _source) { return _source->ast; });
modelChecker.enableAllEnginesIfPragmaPresent(allSources); modelChecker.enableAllEnginesIfPragmaPresent(allSources);
modelChecker.checkRequestedSourcesAndContracts(allSources); modelChecker.checkRequestedSourcesAndContracts(allSources);
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
@ -1030,7 +1030,7 @@ Json::Value CompilerStack::interfaceSymbols(string const& _contractName) const
for (ErrorDefinition const* error: contractDefinition(_contractName).interfaceErrors()) for (ErrorDefinition const* error: contractDefinition(_contractName).interfaceErrors())
{ {
string signature = error->functionType(true)->externalSignature(); string signature = error->functionType(true)->externalSignature();
interfaceSymbols["errors"][signature] = toHex(toCompactBigEndian(selectorFromSignature32(signature), 4)); interfaceSymbols["errors"][signature] = util::toHex(toCompactBigEndian(util::selectorFromSignature32(signature), 4));
} }
for (EventDefinition const* event: ranges::concat_view( for (EventDefinition const* event: ranges::concat_view(
@ -1040,7 +1040,7 @@ Json::Value CompilerStack::interfaceSymbols(string const& _contractName) const
if (!event->isAnonymous()) if (!event->isAnonymous())
{ {
string signature = event->functionType(true)->externalSignature(); string signature = event->functionType(true)->externalSignature();
interfaceSymbols["events"][signature] = toHex(u256(h256::Arith(keccak256(signature)))); interfaceSymbols["events"][signature] = toHex(u256(h256::Arith(util::keccak256(signature))));
} }
return interfaceSymbols; return interfaceSymbols;
@ -1494,7 +1494,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
continue; continue;
solAssert(s.second.charStream, "Character stream not available"); solAssert(s.second.charStream, "Character stream not available");
meta["sources"][s.first]["keccak256"] = "0x" + toHex(s.second.keccak256().asBytes()); meta["sources"][s.first]["keccak256"] = "0x" + util::toHex(s.second.keccak256().asBytes());
if (optional<string> licenseString = s.second.ast->licenseString()) if (optional<string> licenseString = s.second.ast->licenseString())
meta["sources"][s.first]["license"] = *licenseString; meta["sources"][s.first]["license"] = *licenseString;
if (m_metadataLiteralSources) if (m_metadataLiteralSources)
@ -1502,7 +1502,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
else else
{ {
meta["sources"][s.first]["urls"] = Json::arrayValue; meta["sources"][s.first]["urls"] = Json::arrayValue;
meta["sources"][s.first]["urls"].append("bzz-raw://" + toHex(s.second.swarmHash().asBytes())); meta["sources"][s.first]["urls"].append("bzz-raw://" + util::toHex(s.second.swarmHash().asBytes()));
meta["sources"][s.first]["urls"].append(s.second.ipfsUrl()); meta["sources"][s.first]["urls"].append(s.second.ipfsUrl());
} }
} }
@ -1565,7 +1565,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
meta["settings"]["libraries"] = Json::objectValue; meta["settings"]["libraries"] = Json::objectValue;
for (auto const& library: m_libraries) for (auto const& library: m_libraries)
meta["settings"]["libraries"][library.first] = "0x" + toHex(library.second.asBytes()); meta["settings"]["libraries"][library.first] = "0x" + util::toHex(library.second.asBytes());
meta["output"]["abi"] = contractABI(_contract); meta["output"]["abi"] = contractABI(_contract);
meta["output"]["userdoc"] = natspecUser(_contract); meta["output"]["userdoc"] = natspecUser(_contract);
@ -1677,7 +1677,7 @@ bytes CompilerStack::createCBORMetadata(Contract const& _contract, bool _forIR)
else else
solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash"); solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash");
if (experimentalMode || _forIR) if (experimentalMode)
encoder.pushBool("experimental", true); encoder.pushBool("experimental", true);
if (m_metadataFormat == MetadataFormat::WithReleaseVersionTag) if (m_metadataFormat == MetadataFormat::WithReleaseVersionTag)
encoder.pushBytes("solc", VersionCompactBytes); encoder.pushBytes("solc", VersionCompactBytes);

View File

@ -189,7 +189,7 @@ public:
/// Enable EVM Bytecode generation. This is enabled by default. /// Enable EVM Bytecode generation. This is enabled by default.
void enableEvmBytecodeGeneration(bool _enable = true) { m_generateEvmBytecode = _enable; } void enableEvmBytecodeGeneration(bool _enable = true) { m_generateEvmBytecode = _enable; }
/// Enable experimental generation of Yul IR code. /// Enable generation of Yul IR code.
void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; } void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; }
/// Enable experimental generation of Ewasm code. If enabled, IR is also generated. /// Enable experimental generation of Ewasm code. If enabled, IR is also generated.
@ -373,8 +373,8 @@ private:
std::shared_ptr<evmasm::Assembly> evmRuntimeAssembly; std::shared_ptr<evmasm::Assembly> evmRuntimeAssembly;
evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object). evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object).
evmasm::LinkerObject runtimeObject; ///< Runtime object. evmasm::LinkerObject runtimeObject; ///< Runtime object.
std::string yulIR; ///< Experimental Yul IR code. std::string yulIR; ///< Yul IR code.
std::string yulIROptimized; ///< Optimized experimental Yul IR code. std::string yulIROptimized; ///< Optimized Yul IR code.
std::string ewasm; ///< Experimental Ewasm text representation std::string ewasm; ///< Experimental Ewasm text representation
evmasm::LinkerObject ewasmObject; ///< Experimental Ewasm code evmasm::LinkerObject ewasmObject; ///< Experimental Ewasm code
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain. util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
@ -447,8 +447,7 @@ private:
/// Can only be called after state is SourcesSet. /// Can only be called after state is SourcesSet.
Source const& source(std::string const& _sourceName) const; Source const& source(std::string const& _sourceName) const;
/// @param _forIR If true, include a flag that indicates that the bytecode comes from the /// @param _forIR If true, include a flag that indicates that the bytecode comes from IR codegen.
/// experimental IR codegen.
/// @returns the metadata JSON as a compact string for the given contract. /// @returns the metadata JSON as a compact string for the given contract.
std::string createMetadata(Contract const& _contract, bool _forIR) const; std::string createMetadata(Contract const& _contract, bool _forIR) const;

View File

@ -55,7 +55,7 @@ struct OptimiserSettings
"xa[rul]" // Prune a bit more in SSA "xa[rul]" // Prune a bit more in SSA
"xa[r]cL" // Turn into SSA again and simplify "xa[r]cL" // Turn into SSA again and simplify
"gvif" // Run full inliner "gvif" // Run full inliner
"CTUca[r]LsTFOtfDnca[r]Iulc" // SSA plus simplify "CTUca[r]LSsTFOtfDnca[r]Iulc" // SSA plus simplify
"]" "]"
"jmul[jul] VcTOcul jmul"; // Make source short and pretty "jmul[jul] VcTOcul jmul"; // Make source short and pretty

View File

@ -1448,10 +1448,6 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
return output; return output;
} }
// TODO: move this warning to AssemblyStack
output["errors"] = Json::arrayValue;
output["errors"].append(formatError(Error::Severity::Warning, "Warning", "general", "Yul is still experimental. Please use the output with care."));
string contractName = stack.parserResult()->name.str(); string contractName = stack.parserResult()->name.str();
bool const wildcardMatchesExperimental = true; bool const wildcardMatchesExperimental = true;

View File

@ -0,0 +1,66 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/lsp/GotoDefinition.h>
#include <libsolidity/lsp/Transport.h> // for RequestError
#include <libsolidity/lsp/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTUtils.h>
#include <fmt/format.h>
#include <memory>
#include <string>
#include <vector>
using namespace solidity::frontend;
using namespace solidity::langutil;
using namespace solidity::lsp;
using namespace std;
void GotoDefinition::operator()(MessageID _id, Json::Value const& _args)
{
auto const [sourceUnitName, lineColumn] = extractSourceUnitNameAndLineColumn(_args);
ASTNode const* sourceNode = m_server.astNodeAtSourceLocation(sourceUnitName, lineColumn);
vector<SourceLocation> locations;
if (auto const* expression = dynamic_cast<Expression const*>(sourceNode))
{
// Handles all expressions that can have one or more declaration annotation.
if (auto const* declaration = referencedDeclaration(expression))
if (auto location = declarationLocation(declaration))
locations.emplace_back(move(location.value()));
}
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(sourceNode))
{
if (auto const* declaration = identifierPath->annotation().referencedDeclaration)
if (auto location = declarationLocation(declaration))
locations.emplace_back(move(location.value()));
}
else if (auto const* importDirective = dynamic_cast<ImportDirective const*>(sourceNode))
{
auto const& path = *importDirective->annotation().absolutePath;
if (fileRepository().sourceUnits().count(path))
locations.emplace_back(SourceLocation{0, 0, make_shared<string const>(path)});
}
Json::Value reply = Json::arrayValue;
for (SourceLocation const& location: locations)
reply.append(toJson(location));
client().reply(_id, reply);
}

View File

@ -0,0 +1,31 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/lsp/HandlerBase.h>
namespace solidity::lsp
{
class GotoDefinition: public HandlerBase
{
public:
explicit GotoDefinition(LanguageServer& _server): HandlerBase(_server) {}
void operator()(MessageID, Json::Value const&);
};
}

View File

@ -0,0 +1,78 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolutil/Exceptions.h>
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/Utils.h>
#include <libsolidity/ast/AST.h>
#include <liblangutil/Exceptions.h>
#include <fmt/format.h>
using namespace solidity::langutil;
using namespace solidity::lsp;
using namespace solidity::util;
using namespace std;
Json::Value HandlerBase::toRange(SourceLocation const& _location) const
{
if (!_location.hasText())
return toJsonRange({}, {});
solAssert(_location.sourceName, "");
langutil::CharStream const& stream = charStreamProvider().charStream(*_location.sourceName);
LineColumn start = stream.translatePositionToLineColumn(_location.start);
LineColumn end = stream.translatePositionToLineColumn(_location.end);
return toJsonRange(start, end);
}
Json::Value HandlerBase::toJson(SourceLocation const& _location) const
{
solAssert(_location.sourceName);
Json::Value item = Json::objectValue;
item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName);
item["range"] = toRange(_location);
return item;
}
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
{
string const uri = _args["textDocument"]["uri"].asString();
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
if (!fileRepository().sourceUnits().count(sourceUnitName))
BOOST_THROW_EXCEPTION(
RequestError(ErrorCode::RequestFailed) <<
errinfo_comment("Unknown file: " + uri)
);
auto const lineColumn = parseLineColumn(_args["position"]);
if (!lineColumn)
BOOST_THROW_EXCEPTION(
RequestError(ErrorCode::RequestFailed) <<
errinfo_comment(fmt::format(
"Unknown position {line}:{column} in file: {file}",
fmt::arg("line", lineColumn.value().line),
fmt::arg("column", lineColumn.value().column),
fmt::arg("file", sourceUnitName)
))
);
return {sourceUnitName, *lineColumn};
}

View File

@ -0,0 +1,56 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <liblangutil/SourceLocation.h>
#include <liblangutil/CharStreamProvider.h>
#include <optional>
namespace solidity::lsp
{
class Transport;
/**
* Helper base class for implementing handlers.
*/
class HandlerBase
{
public:
explicit HandlerBase(LanguageServer& _server): m_server{_server} {}
Json::Value toRange(langutil::SourceLocation const& _location) const;
Json::Value toJson(langutil::SourceLocation const& _location) const;
/// @returns source unit name and the line column position as extracted
/// from the JSON-RPC parameters.
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); }
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); }
Transport& client() const noexcept { return m_server.client(); }
protected:
LanguageServer& m_server;
};
}

View File

@ -21,6 +21,11 @@
#include <libsolidity/interface/ReadFile.h> #include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/lsp/LanguageServer.h> #include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/Utils.h>
// LSP feature implementations
#include <libsolidity/lsp/GotoDefinition.h>
#include <liblangutil/SourceReferenceExtractor.h> #include <liblangutil/SourceReferenceExtractor.h>
#include <liblangutil/CharStream.h> #include <liblangutil/CharStream.h>
@ -48,31 +53,6 @@ using namespace solidity::frontend;
namespace namespace
{ {
Json::Value toJson(LineColumn _pos)
{
Json::Value json = Json::objectValue;
json["line"] = max(_pos.line, 0);
json["character"] = max(_pos.column, 0);
return json;
}
Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end)
{
Json::Value json;
json["start"] = toJson(_start);
json["end"] = toJson(_end);
return json;
}
optional<LineColumn> parseLineColumn(Json::Value const& _lineColumn)
{
if (_lineColumn.isObject() && _lineColumn["line"].isInt() && _lineColumn["character"].isInt())
return LineColumn{_lineColumn["line"].asInt(), _lineColumn["character"].asInt()};
else
return nullopt;
}
int toDiagnosticSeverity(Error::Type _errorType) int toDiagnosticSeverity(Error::Type _errorType)
{ {
// 1=Error, 2=Warning, 3=Info, 4=Hint // 1=Error, 2=Warning, 3=Info, 4=Hint
@ -97,9 +77,11 @@ LanguageServer::LanguageServer(Transport& _transport):
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)}, {"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
{"initialized", [](auto, auto) {}}, {"initialized", [](auto, auto) {}},
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }}, {"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
{"textDocument/definition", GotoDefinition(*this) },
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)}, {"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)}, {"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)}, {"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
{"textDocument/implementation", GotoDefinition(*this) },
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)}, {"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
}, },
m_fileRepository("/" /* basePath */), m_fileRepository("/" /* basePath */),
@ -107,55 +89,14 @@ LanguageServer::LanguageServer(Transport& _transport):
{ {
} }
optional<SourceLocation> LanguageServer::parsePosition( Json::Value LanguageServer::toRange(SourceLocation const& _location)
string const& _sourceUnitName,
Json::Value const& _position
) const
{ {
if (!m_fileRepository.sourceUnits().count(_sourceUnitName)) return HandlerBase(*this).toRange(_location);
return nullopt;
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
m_fileRepository.sourceUnits().at(_sourceUnitName),
*lineColumn
))
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
return nullopt;
} }
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) const Json::Value LanguageServer::toJson(SourceLocation const& _location)
{ {
if (!_range.isObject()) return HandlerBase(*this).toJson(_location);
return nullopt;
optional<SourceLocation> start = parsePosition(_sourceUnitName, _range["start"]);
optional<SourceLocation> end = parsePosition(_sourceUnitName, _range["end"]);
if (!start || !end)
return nullopt;
solAssert(*start->sourceName == *end->sourceName);
start->end = end->end;
return start;
}
Json::Value LanguageServer::toRange(SourceLocation const& _location) const
{
if (!_location.hasText())
return toJsonRange({}, {});
solAssert(_location.sourceName, "");
CharStream const& stream = m_compilerStack.charStream(*_location.sourceName);
LineColumn start = stream.translatePositionToLineColumn(_location.start);
LineColumn end = stream.translatePositionToLineColumn(_location.end);
return toJsonRange(start, end);
}
Json::Value LanguageServer::toJson(SourceLocation const& _location) const
{
solAssert(_location.sourceName);
Json::Value item = Json::objectValue;
item["uri"] = m_fileRepository.sourceUnitNameToClientPath(*_location.sourceName);
item["range"] = toRange(_location);
return item;
} }
void LanguageServer::changeConfiguration(Json::Value const& _settings) void LanguageServer::changeConfiguration(Json::Value const& _settings)
@ -253,7 +194,7 @@ bool LanguageServer::run()
string const methodName = (*jsonMessage)["method"].asString(); string const methodName = (*jsonMessage)["method"].asString();
id = (*jsonMessage)["id"]; id = (*jsonMessage)["id"];
if (auto handler = valueOrDefault(m_handlers, methodName)) if (auto handler = util::valueOrDefault(m_handlers, methodName))
handler(id, (*jsonMessage)["params"]); handler(id, (*jsonMessage)["params"]);
else else
m_client.error(id, ErrorCode::MethodNotFound, "Unknown method " + methodName); m_client.error(id, ErrorCode::MethodNotFound, "Unknown method " + methodName);
@ -316,8 +257,10 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
Json::Value replyArgs; Json::Value replyArgs;
replyArgs["serverInfo"]["name"] = "solc"; replyArgs["serverInfo"]["name"] = "solc";
replyArgs["serverInfo"]["version"] = string(VersionNumber); replyArgs["serverInfo"]["version"] = string(VersionNumber);
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true; replyArgs["capabilities"]["definitionProvider"] = true;
replyArgs["capabilities"]["implementationProvider"] = true;
replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
m_client.reply(_id, move(replyArgs)); m_client.reply(_id, move(replyArgs));
} }
@ -371,11 +314,11 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
string text = jsonContentChange["text"].asString(); string text = jsonContentChange["text"].asString();
if (jsonContentChange["range"].isObject()) // otherwise full content update if (jsonContentChange["range"].isObject()) // otherwise full content update
{ {
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]); optional<SourceLocation> change = parseRange(m_fileRepository, sourceUnitName, jsonContentChange["range"]);
lspAssert( lspAssert(
change && change->hasText(), change && change->hasText(),
ErrorCode::RequestFailed, ErrorCode::RequestFailed,
"Invalid source range: " + jsonCompactPrint(jsonContentChange["range"]) "Invalid source range: " + util::jsonCompactPrint(jsonContentChange["range"])
); );
string buffer = m_fileRepository.sourceUnits().at(sourceUnitName); string buffer = m_fileRepository.sourceUnits().at(sourceUnitName);
@ -403,3 +346,19 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
compileAndUpdateDiagnostics(); compileAndUpdateDiagnostics();
} }
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
{
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
return nullptr;
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
return nullptr;
if (optional<int> sourcePos =
m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos))
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
else
return nullptr;
}

View File

@ -57,6 +57,11 @@ public:
/// @return boolean indicating normal or abnormal termination. /// @return boolean indicating normal or abnormal termination.
bool run(); bool run();
FileRepository& fileRepository() noexcept { return m_fileRepository; }
Transport& client() noexcept { return m_client; }
frontend::ASTNode const* astNodeAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_compilerStack; }
private: private:
/// Checks if the server is initialized (to be used by messages that need it to be initialized). /// Checks if the server is initialized (to be used by messages that need it to be initialized).
/// Reports an error and returns false if not. /// Reports an error and returns false if not.
@ -66,28 +71,19 @@ private:
void handleTextDocumentDidOpen(Json::Value const& _args); void handleTextDocumentDidOpen(Json::Value const& _args);
void handleTextDocumentDidChange(Json::Value const& _args); void handleTextDocumentDidChange(Json::Value const& _args);
void handleTextDocumentDidClose(Json::Value const& _args); void handleTextDocumentDidClose(Json::Value const& _args);
void handleGotoDefinition(MessageID _id, Json::Value const& _args);
/// Invoked when the server user-supplied configuration changes (initiated by the client). /// Invoked when the server user-supplied configuration changes (initiated by the client).
void changeConfiguration(Json::Value const&); void changeConfiguration(Json::Value const&);
/// Compile everything until after analysis phase. /// Compile everything until after analysis phase.
void compile(); void compile();
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
std::optional<langutil::SourceLocation> parsePosition( Json::Value toRange(langutil::SourceLocation const& _location);
std::string const& _sourceUnitName, Json::Value toJson(langutil::SourceLocation const& _location);
Json::Value const& _position
) const;
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parseRange(
std::string const& _sourceUnitName,
Json::Value const& _range
) const;
Json::Value toRange(langutil::SourceLocation const& _location) const;
Json::Value toJson(langutil::SourceLocation const& _location) const;
// LSP related member fields // LSP related member fields
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown }; enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown };
State m_state = State::Started; State m_state = State::Started;

View File

@ -69,7 +69,7 @@ private:
{ \ { \
BOOST_THROW_EXCEPTION( \ BOOST_THROW_EXCEPTION( \
RequestError(errorCode) << \ RequestError(errorCode) << \
errinfo_comment(errorMessage) \ util::errinfo_comment(errorMessage) \
); \ ); \
} }

118
libsolidity/lsp/Utils.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/Exceptions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/Utils.h>
#include <fmt/format.h>
#include <fstream>
namespace solidity::lsp
{
using namespace frontend;
using namespace langutil;
using namespace std;
optional<LineColumn> parseLineColumn(Json::Value const& _lineColumn)
{
if (_lineColumn.isObject() && _lineColumn["line"].isInt() && _lineColumn["character"].isInt())
return LineColumn{_lineColumn["line"].asInt(), _lineColumn["character"].asInt()};
else
return nullopt;
}
Json::Value toJson(LineColumn const& _pos)
{
Json::Value json = Json::objectValue;
json["line"] = max(_pos.line, 0);
json["character"] = max(_pos.column, 0);
return json;
}
Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end)
{
Json::Value json;
json["start"] = toJson(_start);
json["end"] = toJson(_end);
return json;
}
Declaration const* referencedDeclaration(Expression const* _expression)
{
if (auto const* identifier = dynamic_cast<Identifier const*>(_expression))
if (Declaration const* referencedDeclaration = identifier->annotation().referencedDeclaration)
return referencedDeclaration;
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression))
if (memberAccess->annotation().referencedDeclaration)
return memberAccess->annotation().referencedDeclaration;
return nullptr;
}
optional<SourceLocation> declarationLocation(Declaration const* _declaration)
{
if (!_declaration)
return nullopt;
if (_declaration->nameLocation().isValid())
return _declaration->nameLocation();
if (_declaration->location().isValid())
return _declaration->location();
return nullopt;
}
optional<SourceLocation> parsePosition(
FileRepository const& _fileRepository,
string const& _sourceUnitName,
Json::Value const& _position
)
{
if (!_fileRepository.sourceUnits().count(_sourceUnitName))
return nullopt;
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
_fileRepository.sourceUnits().at(_sourceUnitName),
*lineColumn
))
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
return nullopt;
}
optional<SourceLocation> parseRange(FileRepository const& _fileRepository, string const& _sourceUnitName, Json::Value const& _range)
{
if (!_range.isObject())
return nullopt;
optional<SourceLocation> start = parsePosition(_fileRepository, _sourceUnitName, _range["start"]);
optional<SourceLocation> end = parsePosition(_fileRepository, _sourceUnitName, _range["end"]);
if (!start || !end)
return nullopt;
solAssert(*start->sourceName == *end->sourceName);
start->end = end->end;
return start;
}
}

79
libsolidity/lsp/Utils.h Normal file
View File

@ -0,0 +1,79 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <liblangutil/SourceLocation.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolutil/JSON.h>
#include <optional>
#include <vector>
#if !defined(NDEBUG)
#include <fstream>
#define lspDebug(message) (std::ofstream("/tmp/solc.log", std::ios::app) << (message) << std::endl)
#else
#define lspDebug(message) do {} while (0)
#endif
namespace solidity::langutil
{
class CharStreamProvider;
}
namespace solidity::lsp
{
class FileRepository;
std::optional<langutil::LineColumn> parseLineColumn(Json::Value const& _lineColumn);
Json::Value toJson(langutil::LineColumn const& _pos);
Json::Value toJsonRange(langutil::LineColumn const& _start, langutil::LineColumn const& _end);
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parsePosition(
FileRepository const& _fileRepository,
std::string const& _sourceUnitName,
Json::Value const& _position
);
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parseRange(
FileRepository const& _fileRepository,
std::string const& _sourceUnitName,
Json::Value const& _range
);
/// Extracts the resolved declaration of the given expression AST node.
///
/// This may for example be the type declaration of an identifier,
/// or the type declaration of a structured member identifier.
///
/// @returns the resolved type declaration if found, or nullptr otherwise.
frontend::Declaration const* referencedDeclaration(frontend::Expression const* _expression);
/// @returns the location of the declaration's name, if present, or the location of the complete
/// declaration otherwise. If the input declaration is nullptr, std::nullopt is returned instead.
std::optional<langutil::SourceLocation> declarationLocation(frontend::Declaration const* _declaration);
}

View File

@ -117,6 +117,9 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
case Token::Type: case Token::Type:
nodes.push_back(parseUserDefinedValueTypeDefinition()); nodes.push_back(parseUserDefinedValueTypeDefinition());
break; break;
case Token::Using:
nodes.push_back(parseUsingDirective());
break;
case Token::Function: case Token::Function:
nodes.push_back(parseFunctionDefinition(true)); nodes.push_back(parseFunctionDefinition(true));
break; break;
@ -962,16 +965,37 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
expectToken(Token::Using); expectToken(Token::Using);
ASTPointer<IdentifierPath> library(parseIdentifierPath());
vector<ASTPointer<IdentifierPath>> functions;
bool const usesBraces = m_scanner->currentToken() == Token::LBrace;
if (usesBraces)
{
do
{
advance();
functions.emplace_back(parseIdentifierPath());
}
while (m_scanner->currentToken() == Token::Comma);
expectToken(Token::RBrace);
}
else
functions.emplace_back(parseIdentifierPath());
ASTPointer<TypeName> typeName; ASTPointer<TypeName> typeName;
expectToken(Token::For); expectToken(Token::For);
if (m_scanner->currentToken() == Token::Mul) if (m_scanner->currentToken() == Token::Mul)
advance(); advance();
else else
typeName = parseTypeName(); typeName = parseTypeName();
bool global = false;
if (m_scanner->currentToken() == Token::Identifier && currentLiteral() == "global")
{
global = true;
advance();
}
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::Semicolon); expectToken(Token::Semicolon);
return nodeFactory.createNode<UsingForDirective>(library, typeName); return nodeFactory.createNode<UsingForDirective>(move(functions), usesBraces, typeName, global);
} }
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()

View File

@ -20,11 +20,12 @@
* @date 2014 * @date 2014
*/ */
#include <libsolutil/Assertions.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Exceptions.h> #include <libsolutil/Exceptions.h>
#include <libsolutil/Assertions.h>
#include <libsolutil/Keccak256.h>
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
#include <libsolutil/Keccak256.h>
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -154,9 +155,9 @@ string solidity::util::getChecksummedAddress(string const& _addr)
char addressCharacter = s[i]; char addressCharacter = s[i];
uint8_t nibble = hash[i / 2u] >> (4u * (1u - (i % 2u))) & 0xf; uint8_t nibble = hash[i / 2u] >> (4u * (1u - (i % 2u))) & 0xf;
if (nibble >= 8) if (nibble >= 8)
ret += static_cast<char>(toupper(addressCharacter)); ret += toUpper(addressCharacter);
else else
ret += static_cast<char>(tolower(addressCharacter)); ret += toLower(addressCharacter);
} }
return ret; return ret;
} }
@ -211,7 +212,7 @@ string solidity::util::escapeAndQuoteString(string const& _input)
out += "\\r"; out += "\\r";
else if (c == '\t') else if (c == '\t')
out += "\\t"; out += "\\t";
else if (!isprint(c, locale::classic())) else if (!isPrint(c))
{ {
ostringstream o; ostringstream o;
o << "\\x" << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c); o << "\\x" << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c);

View File

@ -59,7 +59,7 @@ bytes encodeLinkData(bytes const& _data)
string base58Encode(bytes const& _data) string base58Encode(bytes const& _data)
{ {
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}; static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
bigint data(toHex(_data, HexPrefix::Add)); bigint data(util::toHex(_data, HexPrefix::Add));
string output; string output;
while (data) while (data)
{ {

View File

@ -27,7 +27,9 @@
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Numeric.h> #include <libsolutil/Numeric.h>
#include <algorithm>
#include <limits> #include <limits>
#include <locale>
#include <string> #include <string>
#include <vector> #include <vector>
@ -197,4 +199,47 @@ inline std::optional<unsigned> toUnsignedInt(std::string const& _value)
} }
} }
/// Converts parameter _c to its lowercase equivalent if c is an uppercase letter and has a lowercase equivalent. It uses the classic "C" locale semantics.
/// @param _c value to be converted
/// @return the converted value
inline char toLower(char _c)
{
return tolower(_c, std::locale::classic());
}
/// Converts parameter _c to its uppercase equivalent if c is an lowercase letter and has a uppercase equivalent. It uses the classic "C" locale semantics.
/// @param _c value to be converted
/// @return the converted value
inline char toUpper(char _c)
{
return toupper(_c, std::locale::classic());
}
/// Converts parameter _s to its lowercase equivalent. It uses the classic "C" locale semantics.
/// @param _s value to be converted
/// @return the converted value
inline std::string toLower(std::string _s)
{
std::transform(_s.begin(), _s.end(), _s.begin(), [](char _c) {
return toLower(_c);
});
return _s;
}
/// Checks whether _c is a decimal digit character. It uses the classic "C" locale semantics.
/// @param _c character to be checked
/// @return true if _c is a decimal digit character, false otherwise
inline bool isDigit(char _c)
{
return isdigit(_c, std::locale::classic());
}
// Checks if character is printable using classic "C" locale
/// @param _c character to be checked
/// @return true if _c is a printable character, false otherwise.
inline bool isPrint(char _c)
{
return isprint(_c, std::locale::classic());
}
} }

View File

@ -144,4 +144,13 @@ template <class... Args> inline std::shared_ptr<DebugData const> debugDataOf(std
return std::visit([](auto const& _arg) { return debugDataOf(_arg); }, _node); return std::visit([](auto const& _arg) { return debugDataOf(_arg); }, _node);
} }
inline bool hasDefaultCase(Switch const& _switch)
{
return std::any_of(
_switch.cases.begin(),
_switch.cases.end(),
[](Case const& _case) { return !_case.value; }
);
}
} }

View File

@ -38,6 +38,7 @@
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <liblangutil/Scanner.h> #include <liblangutil/Scanner.h>
#include <boost/algorithm/string.hpp>
#include <optional> #include <optional>
using namespace std; using namespace std;
@ -71,8 +72,7 @@ evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
) )
{ {
// Constructing it this way so that we notice changes in the fields. // Constructing it this way so that we notice changes in the fields.
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, _evmVersion, 0}; evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, _evmVersion, 0};
asmSettings.isCreation = true;
asmSettings.runInliner = _settings.runInliner; asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole; asmSettings.runPeephole = _settings.runPeephole;
@ -194,7 +194,10 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
yulAssert(_object.analysisInfo, ""); yulAssert(_object.analysisInfo, "");
for (auto& subNode: _object.subObjects) for (auto& subNode: _object.subObjects)
if (auto subObject = dynamic_cast<Object*>(subNode.get())) if (auto subObject = dynamic_cast<Object*>(subNode.get()))
optimize(*subObject, false); {
bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
optimize(*subObject, isCreation);
}
Dialect const& dialect = languageToDialect(m_language, m_evmVersion); Dialect const& dialect = languageToDialect(m_language, m_evmVersion);
unique_ptr<GasMeter> meter; unique_ptr<GasMeter> meter;
@ -281,7 +284,7 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
yulAssert(m_parserResult->code, ""); yulAssert(m_parserResult->code, "");
yulAssert(m_parserResult->analysisInfo, ""); yulAssert(m_parserResult->analysisInfo, "");
evmasm::Assembly assembly; evmasm::Assembly assembly(true, {});
EthAssemblyAdapter adapter(assembly); EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation); compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);

View File

@ -179,6 +179,8 @@ add_library(yul
optimiser/UnusedAssignEliminator.h optimiser/UnusedAssignEliminator.h
optimiser/UnusedStoreBase.cpp optimiser/UnusedStoreBase.cpp
optimiser/UnusedStoreBase.h optimiser/UnusedStoreBase.h
optimiser/UnusedStoreEliminator.cpp
optimiser/UnusedStoreEliminator.h
optimiser/Rematerialiser.cpp optimiser/Rematerialiser.cpp
optimiser/Rematerialiser.h optimiser/Rematerialiser.h
optimiser/SMTSolver.cpp optimiser/SMTSolver.cpp

Some files were not shown because too many files have changed in this diff Show More