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
compile_only: 1
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
<<: *workflow_emscripten
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_gnosis
project: gnosis
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_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'
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_native_test_ext_zeppelin: &job_native_test_ext_zeppelin
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_zeppelin
@ -1065,6 +1051,20 @@ jobs:
t_ubu_release_cli: &t_ubu_release_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:
# Runs slightly faster on medium but we only run it nightly so efficiency matters more.
<<: *base_ubuntu2004_small
@ -1449,6 +1449,7 @@ workflows:
# Ubuntu build and tests
- b_ubu: *workflow_trigger_on_tags
- t_ubu_cli: *workflow_ubuntu2004
- t_ubu_locale: *workflow_ubuntu2004
- t_ubu_soltest_all: *workflow_ubuntu2004
- t_ubu_soltest_enforce_yul: *workflow_ubuntu2004
- b_ubu_clang: *workflow_trigger_on_tags
@ -1466,12 +1467,8 @@ workflows:
- t_ems_ext_hardhat: *workflow_emscripten
- 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
# 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_gnosis
- t_ems_ext: *job_native_test_ext_zeppelin
- t_ems_ext: *job_native_test_ext_ens
- t_ems_ext: *job_native_test_ext_trident
@ -1488,8 +1485,7 @@ workflows:
<<: *workflow_trigger_on_tags
requires:
- t_ems_compile_ext_colony
- t_native_compile_ext_gnosis
- t_native_test_ext_gnosis_v2
- t_native_test_ext_gnosis
- t_native_test_ext_zeppelin
- t_native_test_ext_ens
- t_native_test_ext_trident

View File

@ -21,7 +21,7 @@ include(EthPolicy)
eth_policy()
# 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
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
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.
### 0.8.13 (unreleased)
### 0.8.14 (unreleased)
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:
* JSON-AST: Added selector field for errors and events.
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)
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}")
endif()
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;
library GetCode {
function at(address _addr) public view returns (bytes memory code) {
function at(address addr) public view returns (bytes memory code) {
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
// by using code = new bytes(size)
code := mload(0x40)
@ -57,7 +57,7 @@ Solidity language without a compiler change.
// store length in memory
mstore(code, size)
// 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 {
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i)
sum += _data[i];
function sumSolidity(uint[] memory data) public pure returns (uint sum) {
for (uint i = 0; i < data.length; ++i)
sum += data[i];
}
// 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
// array length.
function sumAsm(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i) {
function sumAsm(uint[] memory data) public pure returns (uint sum) {
for (uint i = 0; i < data.length; ++i) {
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.
function sumPureAsm(uint[] memory _data) public pure returns (uint sum) {
function sumPureAsm(uint[] memory data) public pure returns (uint sum) {
assembly {
// Load the length (first 32 bytes)
let len := mload(_data)
let len := mload(data)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// NOTE: incrementing data would result in an unusable
// data variable after this assembly block
let dataElementLocation := add(data, 0x20)
// Iterate until the bound is not met.
for
{ let end := add(data, mul(len, 0x20)) }
lt(data, end)
{ data := add(data, 0x20) }
{ let end := add(dataElementLocation, mul(len, 0x20)) }
lt(dataElementLocation, end)
{ 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",
"name": "UserDefinedValueTypesBug",
@ -8,7 +19,6 @@
"introduced": "0.8.8",
"fixed": "0.8.9",
"severity": "very low"
},
{
"uid": "SOL-2021-3",

View File

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

View File

@ -163,9 +163,9 @@ restrictions highly readable.
// prepend a check that only passes
// if the function is called from
// a certain address.
modifier onlyBy(address _account)
modifier onlyBy(address account)
{
if (msg.sender != _account)
if (msg.sender != account)
revert Unauthorized();
// Do not forget the "_;"! It will
// 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.
function changeOwner(address _newOwner)
function changeOwner(address newOwner)
public
onlyBy(owner)
{
owner = _newOwner;
owner = newOwner;
}
modifier onlyAfter(uint _time) {
if (block.timestamp < _time)
modifier onlyAfter(uint time) {
if (block.timestamp < time)
revert TooEarly();
_;
}
@ -205,21 +205,21 @@ restrictions highly readable.
// refunded, but only after the function body.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
if (msg.value < _amount)
modifier costs(uint amount) {
if (msg.value < amount)
revert NotEnoughEther();
_;
if (msg.value > _amount)
payable(msg.sender).transfer(msg.value - _amount);
if (msg.value > amount)
payable(msg.sender).transfer(msg.value - amount);
}
function forceOwnerChange(address _newOwner)
function forceOwnerChange(address newOwner)
public
payable
costs(200 ether)
{
owner = _newOwner;
owner = newOwner;
// just some example condition
if (uint160(owner) & 0 == 1)
// This did not refund for Solidity
@ -315,8 +315,8 @@ function finishes.
uint public creationTime = block.timestamp;
modifier atStage(Stages _stage) {
if (stage != _stage)
modifier atStage(Stages stage_) {
if (stage != stage_)
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;
address immutable owner = msg.sender;
constructor(uint _decimals, address _reference) {
decimals = _decimals;
constructor(uint decimals_, address ref) {
decimals = decimals_;
// Assignments to immutables can even access the environment.
maxBalance = _reference.balance;
maxBalance = ref.balance;
}
function isBalanceTooHigh(address _other) public view returns (bool) {
return _other.balance > maxBalance;
function isBalanceTooHigh(address other) public view returns (bool) {
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
// creator and the assigned name.
constructor(bytes32 _name) {
constructor(bytes32 name_) {
// State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can
// 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.
// This does not create a new contract.
creator = TokenCreator(msg.sender);
name = _name;
name = name_;
}
function changeName(bytes32 newName) public {

View File

@ -80,18 +80,18 @@ four indexed arguments rather than three.
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
address indexed from,
bytes32 indexed id,
uint value
);
function deposit(bytes32 _id) public payable {
function deposit(bytes32 id) public payable {
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// 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": {
"_from": "0x1111…FFFFCCCC",
"_id": "0x50…sd5adb20",
"_value": "0x420042"
"from": "0x1111…FFFFCCCC",
"id": "0x50…sd5adb20",
"value": "0x420042"
},
"raw": {
"data": "0x7f…91385",

View File

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

View File

@ -17,17 +17,17 @@ that call them, similar to internal library functions.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
function sum(uint[] memory _arr) pure returns (uint s) {
for (uint i = 0; i < _arr.length; i++)
s += _arr[i];
function sum(uint[] memory arr) pure returns (uint s) {
for (uint i = 0; i < arr.length; i++)
s += arr[i];
}
contract ArrayExample {
bool found;
function f(uint[] memory _arr) public {
function f(uint[] memory arr) public {
// This calls the free function internally.
// The compiler will add its code to the contract.
uint s = sum(_arr);
uint s = sum(arr);
require(s >= 10);
found = true;
}
@ -65,8 +65,8 @@ with two integers, you would use something like the following:
contract Simple {
uint sum;
function taker(uint _a, uint _b) public {
sum = _a + _b;
function taker(uint a, uint b) public {
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;
contract Simple {
function arithmetic(uint _a, uint _b)
function arithmetic(uint a, uint b)
public
pure
returns (uint sum, uint product)
{
sum = _a + _b;
product = _a * _b;
sum = a + b;
product = a * b;
}
}
@ -126,12 +126,12 @@ statement:
pragma solidity >=0.4.16 <0.9.0;
contract Simple {
function arithmetic(uint _a, uint _b)
function arithmetic(uint a, uint b)
public
pure
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]``
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).
This function must have ``external`` visibility. A fallback function can be virtual, can override
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
it must be marked ``payable``.
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
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
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
@ -398,7 +398,7 @@ operations as long as there is enough gas passed on to it.
for the function selector and then
you can use ``abi.decode`` together with the array slice syntax to
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
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;
contract A {
function f(uint _in) public pure returns (uint out) {
out = _in;
function f(uint value) public pure returns (uint out) {
out = value;
}
function f(uint _in, bool _really) public pure returns (uint out) {
if (_really)
out = _in;
function f(uint value, bool really) public pure returns (uint out) {
if (really)
out = value;
}
}
@ -507,12 +507,12 @@ externally visible functions differ by their Solidity types but not by their ext
// This will not compile
contract A {
function f(B _in) public pure returns (B out) {
out = _in;
function f(B value) public pure returns (B out) {
out = value;
}
function f(address _in) public pure returns (address out) {
out = _in;
function f(address value) public pure returns (address out) {
out = value;
}
}
@ -540,12 +540,12 @@ candidate, resolution fails.
pragma solidity >=0.4.16 <0.9.0;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
function f(uint8 val) public pure returns (uint8 out) {
out = val;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
function f(uint256 val) public pure returns (uint256 out) {
out = val;
}
}

View File

@ -421,8 +421,8 @@ equivalent to ``constructor() {}``. For example:
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
constructor(uint a_) {
a = a_;
}
}
@ -459,7 +459,7 @@ derived contracts need to specify all of them. This can be done in two ways:
contract Base {
uint x;
constructor(uint _x) { x = _x; }
constructor(uint x_) { x = x_; }
}
// 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.
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
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
constant and defines the behaviour of the contract or
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:
- 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 state variables.
- They cannot declare modifiers.

View File

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

View File

@ -6,71 +6,96 @@
Using For
*********
The directive ``using A for B;`` can be used to attach library
functions (from the library ``A``) to any type (``B``)
in the context of a contract.
The directive ``using A for B;`` can be used to attach
functions (``A``) as member functions to any type (``B``).
These functions will receive the object they are called on
as their first parameter (like the ``self`` variable in Python).
The effect of ``using A for *;`` is that the functions from
the library ``A`` are attached to *any* type.
It is valid either at file level or inside a contract,
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
match the type of the object. The type is checked at the
point the function is called and function overload
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
contract, including within all of its functions, and has no effect
outside of the contract in which it is used. The directive
may only be used inside a contract, not inside any of its functions.
scope (either the contract or the current module/source unit),
including within all of its functions, and has no effect
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
:ref:`libraries` in this way:
:ref:`libraries` section in this way, using file-level functions
instead of library functions.
.. code-block:: solidity
// 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; }
// 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)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
contract C {
using Set for Data; // this is the crucial change
Data knownValues;
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
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.8 <0.9.0;
pragma solidity ^0.8.13;
library Search {
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;
}
}
using Search for uint[];
contract C {
using Search for uint[];
uint[] data;
function append(uint value) public {
data.push(value);
}
function replace(uint _old, uint _new) public {
function replace(uint from, uint to) public {
// This performs the library function call
uint index = data.indexOf(_old);
uint index = data.indexOf(from);
if (index == type(uint).max)
data.push(_new);
data.push(to);
else
data[index] = _new;
data[index] = to;
}
}

View File

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

View File

@ -12,6 +12,7 @@ options { tokenVocab=SolidityLexer; }
sourceUnit: (
pragmaDirective
| importDirective
| usingDirective
| contractDefinition
| interfaceDefinition
| libraryDefinition
@ -311,10 +312,10 @@ errorDefinition:
Semicolon;
/**
* Using directive to bind library functions to types.
* Can occur within contracts and libraries.
* Using directive to bind library functions and free functions to types.
* 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
* (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.
*/
identifier: Identifier | From | Error | Revert;
identifier: Identifier | From | Error | Revert | Global;
literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
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
within the Ethereum state.
Solidity is a `curly-bracket language <https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages>`_.
It is influenced by C++, Python and JavaScript, and is designed to target the Ethereum Virtual Machine (EVM).
You can find more details about which languages Solidity has been inspired by in
the :doc:`language influences <language-influences>` section.
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. 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
user-defined types among other features.
@ -90,24 +88,25 @@ our `Gitter channel <https://gitter.im/ethereum/solidity/>`_.
Translations
------------
Community volunteers help translate this documentation into several languages.
They have varying degrees of completeness and up-to-dateness. The English
Community contributors help translate this documentation into several languages.
Note that they have varying degrees of completeness and up-to-dateness. The English
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::
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>`_
for information on how to contribute to the community translations moving forward.
* `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)
for information on how to start a new language or contribute to the community translations.
Contents
========

View File

@ -140,8 +140,7 @@ by checking if the lowest bit is set: short (not set) and long (set).
.. note::
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
slot results in a ``Panic(0x22)`` error.
If you are compiling via IR, reading an invalidly encoded slot results in a ``Panic(0x22)`` error.
JSON Output
===========

View File

@ -23,7 +23,7 @@ call completely.
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.
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.
You can find more details on both optimizer modules and their optimization steps below.

View File

@ -168,8 +168,8 @@ following:
.. code-block:: solidity
function balances(address _account) external view returns (uint) {
return balances[_account];
function balances(address account) external view returns (uint) {
return balances[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
to enable more powerful optimization passes that span across functions.
Currently, the IR-based code generator is still marked experimental,
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``
You can enable it on the command line using ``--via-ir``
or with the option ``{"viaIR": true}`` in standard-json and we
encourage everyone to try it out!
@ -34,6 +30,48 @@ Semantic Only Changes
This section lists the changes that are semantic-only, thus potentially
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
a member of the struct is set to zero entirely. Formerly, padding space
was left untouched.
@ -78,8 +116,8 @@ hiding new and different behavior in existing code.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0;
contract C {
function f(uint _a) public pure mod() returns (uint _r) {
_r = _a++;
function f(uint a) public pure mod() returns (uint r) {
r = a++;
}
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
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.
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
@ -170,7 +167,7 @@ This causes differences in some contracts, for example:
contract C {
bytes x;
function f() public returns (uint _r) {
function f() public returns (uint r) {
bytes memory m = "tmp";
assembly {
mstore(m, 8)
@ -178,7 +175,7 @@ This causes differences in some contracts, for example:
}
x = m;
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
pragma solidity >=0.8.1;
contract C {
function preincr_u8(uint8 _a) public pure returns (uint8) {
return ++_a + _a;
function preincr_u8(uint8 a) public pure returns (uint8) {
return ++a + a;
}
}
@ -222,11 +219,11 @@ This causes differences in some contracts, for example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;
contract C {
function add(uint8 _a, uint8 _b) public pure returns (uint8) {
return _a + _b;
function add(uint8 a, uint8 b) public pure returns (uint8) {
return a + b;
}
function g(uint8 _a, uint8 _b) public pure returns (uint8) {
return add(++_a + ++_b, _a + _b);
function g(uint8 a, uint8 b) public pure returns (uint8) {
return add(++a + ++b, a + b);
}
}
@ -325,13 +322,13 @@ For example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;
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 {
_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``)
- 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``).
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``.
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.
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
:ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and
:ref:`contract definitions<contract_structure>`, import_ ,
:ref:`pragma<pragma>` and :ref:`using for<using-for>` directives and
:ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>`, :ref:`error<errors>`
and :ref:`constant variable<constants>` definitions.

View File

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

View File

@ -82,12 +82,12 @@ Overflow
uint immutable x;
uint immutable y;
function add(uint _x, uint _y) internal pure returns (uint) {
return _x + _y;
function add(uint x_, uint y_) internal pure returns (uint) {
return x_ + y_;
}
constructor(uint _x, uint _y) {
(x, y) = (_x, _y);
constructor(uint x_, uint y_) {
(x, y) = (x_, y_);
}
function stateAdd() public view returns (uint) {
@ -116,7 +116,7 @@ Here, it reports the following:
Overflow.add(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935) -- internal call
--> o.sol:9:20:
|
9 | return _x + _y;
9 | return x_ + y_;
| ^^^^^^^
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 y;
function add(uint _x, uint _y) internal pure returns (uint) {
return _x + _y;
function add(uint x_, uint y_) internal pure returns (uint) {
return x_ + y_;
}
constructor(uint _x, uint _y) {
(x, y) = (_x, _y);
constructor(uint x_, uint y_) {
(x, y) = (x_, y_);
}
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.
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
property is correct. You are encouraged to play with the property and the function
definition to see what results come out!
@ -166,14 +166,14 @@ definition to see what results come out!
pragma solidity >=0.8.0;
contract Monotonic {
function f(uint _x) internal pure returns (uint) {
require(_x < type(uint128).max);
return _x * 42;
function f(uint x) internal pure returns (uint) {
require(x < type(uint128).max);
return x * 42;
}
function inv(uint _a, uint _b) public pure {
require(_b > _a);
assert(f(_b) > f(_a));
function inv(uint a, uint b) public pure {
require(b > a);
assert(f(b) > f(a));
}
}
@ -188,14 +188,14 @@ equal every element in the array.
pragma solidity >=0.8.0;
contract Max {
function max(uint[] memory _a) public pure returns (uint) {
function max(uint[] memory a) public pure returns (uint) {
uint m = 0;
for (uint i = 0; i < _a.length; ++i)
if (_a[i] > m)
m = _a[i];
for (uint i = 0; i < a.length; ++i)
if (a[i] > m)
m = a[i];
for (uint i = 0; i < _a.length; ++i)
assert(m >= _a[i]);
for (uint i = 0; i < a.length; ++i)
assert(m >= a[i]);
return m;
}
@ -222,15 +222,15 @@ For example, changing the code to
pragma solidity >=0.8.0;
contract Max {
function max(uint[] memory _a) public pure returns (uint) {
require(_a.length >= 5);
function max(uint[] memory a) public pure returns (uint) {
require(a.length >= 5);
uint m = 0;
for (uint i = 0; i < _a.length; ++i)
if (_a[i] > m)
m = _a[i];
for (uint i = 0; i < a.length; ++i)
if (a[i] > m)
m = a[i];
for (uint i = 0; i < _a.length; ++i)
assert(m > _a[i]);
for (uint i = 0; i < a.length; ++i)
assert(m > a[i]);
return m;
}
@ -243,7 +243,7 @@ gives us:
Warning: CHC: Assertion violation happens here.
Counterexample:
_a = [0, 0, 0, 0, 0]
a = [0, 0, 0, 0, 0]
= 0
Transaction trace:
@ -251,7 +251,7 @@ gives us:
Test.max([0, 0, 0, 0, 0])
--> max.sol:14:4:
|
14 | assert(m > _a[i]);
14 | assert(m > a[i]);
State Properties
@ -383,9 +383,9 @@ anything, including reenter the caller contract.
Unknown immutable unknown;
constructor(Unknown _u) {
require(address(_u) != address(0));
unknown = _u;
constructor(Unknown u) {
require(address(u) != address(0));
unknown = u;
}
modifier mutex {
@ -395,8 +395,8 @@ anything, including reenter the caller contract.
lock = false;
}
function set(uint _x) mutex public {
x = _x;
function set(uint x_) mutex public {
x = x_;
}
function run() mutex public {
@ -754,15 +754,15 @@ not mean loss of proving power.
{
function f(
bytes32 hash,
uint8 _v1, uint8 _v2,
bytes32 _r1, bytes32 _r2,
bytes32 _s1, bytes32 _s2
uint8 v1, uint8 v2,
bytes32 r1, bytes32 r2,
bytes32 s1, bytes32 s2
) public pure returns (address) {
address a1 = ecrecover(hash, _v1, _r1, _s1);
require(_v1 == _v2);
require(_r1 == _r2);
require(_s1 == _s2);
address a2 = ecrecover(hash, _v2, _r2, _s2);
address a1 = ecrecover(hash, v1, r1, s1);
require(v1 == v2);
require(r1 == r2);
require(s1 == s2);
address a2 = ecrecover(hash, v2, r2, s2);
assert(a1 == a2);
return a1;
}

View File

@ -4,12 +4,12 @@
Mapping Types
=============
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
The ``_KeyType`` can be any
Mapping types use the syntax ``mapping(KeyType => ValueType)`` and variables
of mapping type are declared using the syntax ``mapping(KeyType => ValueType) VariableName``.
The ``KeyType`` can be any
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.
``_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
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.
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.
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
each ``_KeyType``, recursively.
: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 an array or a mapping, the getter has one parameter for
each ``KeyType``, recursively.
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

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
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
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
address client;
constructor(address _client) {
client = _client;
constructor(address client_) {
client = client_;
}
/// Forward call to "setOwner(address)" that is implemented by client
/// after doing basic validation on the address argument.
function forward(bytes calldata _payload) external {
bytes4 sig = bytes4(_payload[:4]);
// Due to truncating behaviour, bytes4(_payload) performs identically.
// bytes4 sig = bytes4(_payload);
function forward(bytes calldata payload) external {
bytes4 sig = bytes4(payload[:4]);
// Due to truncating behaviour, bytes4(payload) performs identically.
// bytes4 sig = bytes4(payload);
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.");
}
(bool status,) = client.delegatecall(_payload);
(bool status,) = client.delegatecall(payload);
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.
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
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
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
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

View File

@ -297,7 +297,7 @@ Input Description
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
"evmVersion": "byzantium",
// 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,
// Optional: Debugging settings
"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.
.. 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::
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
// executing code is the constructor code)
size := datasize("runtime")
size := datasize("Contract1_deployed")
offset := allocate(size)
// This will turn into a memory->memory copy for Ewasm and
// a codecopy for EVM
datacopy(offset, dataoffset("runtime"), size)
datacopy(offset, dataoffset("Contract1_deployed"), size)
return(offset, size)
}
data "Table2" hex"4123"
object "runtime" {
object "Contract1_deployed" {
code {
function allocate(size) -> ptr {
ptr := mload(0x40)
@ -1204,7 +1209,7 @@ An example Yul Object is shown below:
// code here ...
}
object "runtime" {
object "Contract2_deployed" {
code {
// code here ...
}

View File

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

View File

@ -48,7 +48,7 @@ using AssemblyPointer = std::shared_ptr<Assembly>;
class Assembly
{
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 newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
@ -117,7 +117,6 @@ public:
struct OptimiserSettings
{
bool isCreation = false;
bool runInliner = false;
bool runJumpdestRemover = false;
bool runPeephole = false;
@ -157,6 +156,8 @@ public:
std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
size_t encodeSubPath(std::vector<size_t> const& _subPath);
bool isCreation() const { return m_creation; }
protected:
/// 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
@ -214,6 +215,8 @@ protected:
mutable std::vector<size_t> m_tagPositionsInBytecode;
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
/// currently
std::string m_name;

View File

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

View File

@ -219,7 +219,7 @@ void CSECodeGenerator::addDependencies(Id _c)
{
if (m_classPositions.count(_c))
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
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
assertThrow(expr.item, OptimizerException, "");
@ -300,8 +300,8 @@ void CSECodeGenerator::addDependencies(Id _c)
void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
{
for (auto it: m_classPositions)
for (auto p: it.second)
for (auto const& it: m_classPositions)
for (int p: it.second)
if (p > m_stackHeight)
{
assertThrow(false, OptimizerException, "");

View File

@ -24,11 +24,12 @@
#pragma once
#include <vector>
#include <map>
#include <ostream>
#include <set>
#include <tuple>
#include <ostream>
#include <unordered_map>
#include <vector>
#include <libsolutil/CommonIO.h>
#include <libsolutil/Exceptions.h>
#include <libevmasm/ExpressionClasses.h>
@ -154,11 +155,11 @@ private:
/// Current height of the stack relative to the start.
int m_stackHeight = 0;
/// 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.
std::map<int, Id> m_stack;
/// 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.
ExpressionClasses& m_expressionClasses;

View File

@ -41,51 +41,28 @@ struct OptimiserState
std::back_insert_iterator<AssemblyItems> out;
};
template <class Method, size_t Arguments>
struct ApplyRule
template<typename FunctionType>
struct FunctionParameterCount;
template<typename R, typename... Args>
struct FunctionParameterCount<R(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);
}
static constexpr auto value = sizeof...(Args);
};
template <class Method, size_t WindowSize>
template <class Method>
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 constexpr size_t WindowSize = FunctionParameterCount<decltype(Method::applySimple)>::value - 1;
if (
_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;
@ -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)
{
@ -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>)
{
@ -118,7 +95,7 @@ struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
}
};
struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2>
struct OpPop: SimplePeepholeOptimizerMethod<OpPop>
{
static bool applySimple(
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>)
{
@ -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)
{
@ -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)
{
@ -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)
{
@ -207,7 +213,7 @@ struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
};
/// Remove swapN after dupN
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2>
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap>
{
static size_t applySimple(
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(
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(
AssemblyItem const& _pushTag,
@ -282,7 +347,7 @@ struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
}
};
struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions>
{
static bool applySimple(
AssemblyItem const& _pushTag,
@ -317,7 +382,7 @@ struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
}
};
struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd, 3>
struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd>
{
static bool applySimple(
AssemblyItem const& _push,
@ -394,8 +459,8 @@ bool PeepholeOptimiser::optimise()
while (state.i < m_items.size())
applyMethods(
state,
PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
DupSwap(), IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(),
PushPop(), OpPop(), OpStop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
DupSwap(), IsZeroIsZeroJumpI(), EqIsZeroJumpI(), DoubleJump(), JumpToNext(), UnreachableCode(),
TagConjunctions(), TruthyAnd(), Identity()
);
if (m_optimisedItems.size() < m_items.size() || (

View File

@ -121,7 +121,9 @@ vector<SemanticInformation::Operation> SemanticInformation::readWriteOperations(
Location::Memory,
Effect::Write,
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;

View File

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

View File

@ -1,8 +1,17 @@
if (EMSCRIPTEN)
CreateExportedFunctionsForEMSDK(
ExportedFunctions
solidity_license
solidity_version
solidity_compile
solidity_alloc
solidity_free
solidity_reset
)
# Specify which functions to export in soljson.js.
# Note that additional Emscripten-generated methods needed by solc-js are
# 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)
target_link_libraries(soljson PRIVATE solidity)
else()

View File

@ -155,12 +155,18 @@ set(sources
interface/StorageLayout.h
interface/Version.cpp
interface/Version.h
lsp/LanguageServer.cpp
lsp/LanguageServer.h
lsp/FileRepository.cpp
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.h
lsp/Utils.cpp
lsp/Utils.h
parsing/DocStringParser.cpp
parsing/DocStringParser.h
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.
for (auto const& exit: currentNode->exits)
if (
auto exists = valueOrNullptr(nodeInfos, exit);
auto exists = util::valueOrNullptr(nodeInfos, exit);
nodeInfos[exit].propagateFrom(nodeInfo) || !exists
)
nodesToTraverse.insert(exit);

View File

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

View File

@ -57,7 +57,7 @@ void ControlFlowRevertPruner::run()
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
// revert behaviour. The ``wakeUp`` data structure contains information about which
// searches to restart once we know about the behaviour.

View File

@ -25,6 +25,7 @@
#include <liblangutil/ErrorReporter.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/Visitor.h>
#include <range/v3/view/transform.hpp>
@ -451,12 +452,39 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor)
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.libraryName().annotation().referencedDeclaration
);
if (_usingFor.usesBraces())
{
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())
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected.");
// We do not visit _usingFor.functions() because it will lead to an error since
// library names cannot be mentioned stand-alone.
if (_usingFor.typeName())
_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.
builder.m_currentNode = CallGraph::SpecialNode::InternalDispatch;
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(get<CallableDeclaration const*>(dispatchTarget) != nullptr, "");

View File

@ -411,12 +411,12 @@ struct ReservedErrorSelector: public PostTypeChecker::Checker
);
else
{
uint32_t selector = selectorFromSignature32(_error.functionType(true)->externalSignature());
uint32_t selector = util::selectorFromSignature32(_error.functionType(true)->externalSignature());
if (selector == 0 || ~selector == 0)
m_errorReporter.syntaxError(
2855_error,
_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())
{
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.
if (!errorHashes[hash].empty() && !errorHashes[hash].count(signature))
{

View File

@ -403,6 +403,42 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
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)
{
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");

View File

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

View File

@ -35,6 +35,7 @@
#include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/Views.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
@ -267,6 +268,11 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
return {TypeProvider::meta(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
}
bool TypeChecker::visit(ImportDirective const&)
{
return false;
}
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
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 (m_currentContract)
{
if (!contains(m_currentContract->annotation().linearizedBaseContracts, modifierContract))
if (!util::contains(m_currentContract->annotation().linearizedBaseContracts, modifierContract))
m_errorReporter.typeError(
9428_error,
_modifier.location(),
@ -2143,7 +2149,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
functionPointerType->declaration().scope() == m_currentContract
)
msg += " Did you forget to prefix \"this.\"?";
else if (contains(
else if (util::contains(
m_currentContract->annotation().linearizedBaseContracts,
functionPointerType->declaration().scope()
) && functionPointerType->declaration().scope() != m_currentContract)
@ -2204,9 +2210,9 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
"Cannot implicitly convert component at position " +
to_string(i) +
" from \"" +
argType.canonicalName() +
argType.toString() +
"\" to \"" +
functionPointerType->parameterTypes()[i]->canonicalName() +
functionPointerType->parameterTypes()[i]->toString() +
"\"" +
(result.message().empty() ? "." : ": " + result.message())
);
@ -3635,12 +3641,89 @@ void TypeChecker::endVisit(Literal const& _literal)
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{
if (m_currentContract->isInterface())
m_errorReporter.typeError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
if (!_usingFor.usesBraces())
{
solAssert(_usingFor.functionsOrLibrary().size() == 1);
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_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)

View File

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

View File

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

View File

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

View File

@ -221,7 +221,7 @@ vector<EventDefinition const*> const ContractDefinition::usedInterfaceEvents() c
{
solAssert(annotation().creationCallGraph.set(), "");
return convertContainer<std::vector<EventDefinition const*>>(
return util::convertContainer<std::vector<EventDefinition const*>>(
(*annotation().creationCallGraph)->emittedEvents +
(*annotation().deployedCallGraph)->emittedEvents
);
@ -239,7 +239,7 @@ vector<ErrorDefinition const*> ContractDefinition::interfaceErrors(bool _require
result +=
(*annotation().creationCallGraph)->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

View File

@ -33,6 +33,7 @@
#include <libevmasm/Instruction.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h>
#include <libsolutil/Visitor.h>
#include <json/json.h>
@ -630,9 +631,20 @@ private:
};
/**
* `using LibraryName for uint` will attach all functions from the library LibraryName
* to `uint` if the first parameter matches the type. `using LibraryName for *` attaches
* the function to any matching type.
* Using for directive:
*
* 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
{
@ -640,24 +652,36 @@ public:
UsingForDirective(
int64_t _id,
SourceLocation const& _location,
ASTPointer<IdentifierPath> _libraryName,
ASTPointer<TypeName> _typeName
std::vector<ASTPointer<IdentifierPath>> _functions,
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(ASTConstVisitor& _visitor) const override;
IdentifierPath const& libraryName() const { return *m_libraryName; }
/// @returns the type name the library is attached to, null for `*`.
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:
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;
bool m_global = false;
};
class StructDefinition: public Declaration, public ScopeOpener

View File

@ -47,7 +47,6 @@ namespace solidity::frontend
class Type;
class ArrayType;
using namespace util;
struct CallGraph;
@ -91,13 +90,13 @@ struct StructurallyDocumentedAnnotation
struct SourceUnitAnnotation: ASTAnnotation
{
/// 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).
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
util::SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
/// Experimental features.
std::set<ExperimentalFeature> experimentalFeatures;
/// Using the new ABI coder. Set to `false` if using ABI coder v1.
SetOnce<bool> useABICoderV2;
util::SetOnce<bool> useABICoderV2;
};
struct ScopableAnnotation
@ -127,7 +126,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
struct ImportAnnotation: DeclarationAnnotation
{
/// The absolute path of the source unit to import.
SetOnce<std::string> absolutePath;
util::SetOnce<std::string> absolutePath;
/// The actual source unit.
SourceUnit const* sourceUnit = nullptr;
};
@ -135,7 +134,7 @@ struct ImportAnnotation: DeclarationAnnotation
struct TypeDeclarationAnnotation: DeclarationAnnotation
{
/// The name of this type, prefixed by proper namespaces if globally accessible.
SetOnce<std::string> canonicalName;
util::SetOnce<std::string> canonicalName;
};
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
@ -162,9 +161,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu
/// These can either be inheritance specifiers or modifier invocations.
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
/// 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.
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".
/// 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.
bool markedMemorySafe = false;
/// 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
@ -256,7 +255,7 @@ struct IdentifierPathAnnotation: ASTAnnotation
/// Referenced declaration, set during reference resolution stage.
Declaration const* referencedDeclaration = nullptr;
/// What kind of lookup needs to be done (static, virtual, super) find the declaration.
SetOnce<VirtualLookup> requiredLookup;
util::SetOnce<VirtualLookup> requiredLookup;
};
struct ExpressionAnnotation: ASTAnnotation
@ -264,11 +263,11 @@ struct ExpressionAnnotation: ASTAnnotation
/// Inferred type of the expression.
Type const* type = nullptr;
/// Whether the expression is a constant variable
SetOnce<bool> isConstant;
util::SetOnce<bool> isConstant;
/// 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).
SetOnce<bool> isLValue;
util::SetOnce<bool> isLValue;
/// Whether the expression is used in a context where the LValue is actually required.
bool willBeWrittenTo = false;
/// 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.
Declaration const* referencedDeclaration = nullptr;
/// 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).
std::vector<Declaration const*> candidateDeclarations;
/// List of possible declarations it could refer to.
@ -307,7 +306,7 @@ struct MemberAccessAnnotation: ExpressionAnnotation
/// Referenced declaration, set at latest during overload resolution stage.
Declaration const* referencedDeclaration = nullptr;
/// What kind of lookup needs to be done (static, virtual, super) find the declaration.
SetOnce<VirtualLookup> requiredLookup;
util::SetOnce<VirtualLookup> requiredLookup;
};
struct BinaryOperationAnnotation: ExpressionAnnotation

View File

@ -32,6 +32,7 @@
#include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/Keccak256.h>
#include <boost/algorithm/string/join.hpp>
@ -311,10 +312,26 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
bool ASTJsonConverter::visit(UsingForDirective const& _node)
{
setJsonNode(_node, "UsingForDirective", {
make_pair("libraryName", toJson(_node.libraryName())),
vector<pair<string, Json::Value>> attributes = {
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;
}
@ -505,7 +522,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
_attributes.emplace_back(
make_pair(
"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));

View File

@ -348,10 +348,19 @@ ASTPointer<InheritanceSpecifier> ASTJsonImporter::createInheritanceSpecifier(Jso
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>(
_node,
createIdentifierPath(member(_node, "libraryName")),
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"])
move(functions),
!_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/ASTUtils.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolutil/Algorithms.h>
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)
{
solAssert(_varDecl.isConstant(), "Constant variable expected");

View File

@ -21,9 +21,11 @@
namespace solidity::frontend
{
class VariableDeclaration;
class ASTNode;
class Declaration;
class Expression;
class SourceUnit;
class VariableDeclaration;
/// Find the topmost referenced constant variable declaration when the given variable
/// 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.
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))
{
m_libraryName->accept(_visitor);
listAccept(functionsOrLibrary(), _visitor);
if (m_typeName)
m_typeName->accept(_visitor);
}
@ -205,7 +205,7 @@ void UsingForDirective::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_libraryName->accept(_visitor);
listAccept(functionsOrLibrary(), _visitor);
if (m_typeName)
m_typeName->accept(_visitor);
}

View File

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

View File

@ -369,6 +369,10 @@ public:
/// are returned without modification.
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).
virtual void clearCache() const;
@ -1004,6 +1008,8 @@ public:
Type const* encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
Declaration const* typeDefinition() const override;
BoolResult validForLocation(DataLocation _loc) const override;
bool recursive() const;
@ -1069,6 +1075,8 @@ public:
return _inLibrary ? this : encodingType();
}
Declaration const* typeDefinition() const override;
EnumDefinition const& enumDefinition() const { return m_enum; }
/// @returns the value that the string has in the Enum
unsigned int memberValue(ASTString const& _member) const;
@ -1101,6 +1109,9 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override { return &underlyingType(); }
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
Declaration const* typeDefinition() const override;
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;

View File

@ -403,7 +403,7 @@ void CompilerContext::appendInlineAssembly(
{
if (_insideFunction)
return false;
return contains(_localVariables, _identifier.name.str());
return util::contains(_localVariables, _identifier.name.str());
};
identifierAccess.generateCode = [&](
yul::Identifier const& _identifier,
@ -572,8 +572,7 @@ void CompilerContext::updateSourceLocation()
evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings)
{
// 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};
asmSettings.isCreation = true;
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0};
asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole;

View File

@ -65,7 +65,7 @@ public:
RevertStrings _revertStrings,
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_revertStrings(_revertStrings),
m_reservedMemory{0},

View File

@ -235,7 +235,7 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
m_context.pushSubroutineOffset(m_context.runtimeSub());
// This code replaces the address added by appendDeployTimeAddress().
m_context.appendInlineAssembly(
Whiskers(R"(
util::Whiskers(R"(
{
// If code starts at 11, an mstore(0) writes to the full PUSH20 plus data
// without the need for a shift.
@ -672,7 +672,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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))
if (stackLayout.back() < 0)
@ -842,7 +842,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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));
}
@ -916,7 +916,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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(Instruction::POP);
@ -1045,7 +1045,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
solAssert(m_context.evmVersion().supportsReturndata(), "");
// 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.appendConditionalJumpTo(panicTag);
m_context << Instruction::POP; // remove selector
@ -1077,7 +1077,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
solAssert(m_context.evmVersion().supportsReturndata(), "");
// stack: <selector>
m_context << selectorFromSignature32("Panic(uint256)") << Instruction::EQ;
m_context << util::selectorFromSignature32("Panic(uint256)") << Instruction::EQ;
m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(fallbackTag);

View File

@ -268,7 +268,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
errinfo_sourceLocation(_varDecl.location()) <<
errinfo_comment("Stack too deep.")
util::errinfo_comment("Stack too deep.")
);
m_context << dupInstruction(retSizeOnStack + 1);
m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction);
@ -350,7 +350,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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
for (unsigned i = 0; i < itemSize; ++i)
@ -1258,6 +1258,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes;
TypePointers targetTypes;
ASTNode::listAccept(arguments, *this);
@ -1265,14 +1266,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
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
if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type))
argumentTypes = tupleType->components();
else
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
for (unsigned i = 0; i < arguments.size(); ++i)
@ -1292,12 +1296,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (isPacked)
{
solAssert(!function.padArguments(), "");
utils().packedEncode(argumentTypes, TypePointers());
utils().packedEncode(argumentTypes, targetTypes);
}
else
{
solAssert(function.padArguments(), "");
utils().abiEncode(argumentTypes, TypePointers());
utils().abiEncode(argumentTypes, targetTypes);
}
utils().fetchFreeMemoryPointer();
// 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!");
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);
utils().moveIntoStack(static_cast<unsigned>(insertPos), 1);
@ -2862,7 +2866,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio
else
BOOST_THROW_EXCEPTION(InternalCompilerError()
<< 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)

View File

@ -50,7 +50,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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.");
for (unsigned i = 0; i < m_size; ++i)
@ -64,7 +64,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
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)
for (unsigned i = 0; i < m_size; ++i)
@ -436,7 +436,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< 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
)
{
string const ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
string ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
yul::AssemblyStack asmStack(
m_evmVersion,
@ -113,15 +113,7 @@ pair<string, string> IRGenerator::run(
}
asmStack.optimize();
string warning =
"/*=====================================================*\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())};
return {move(ir), asmStack.print(m_context.soliditySourceProvider())};
}
string IRGenerator::generate(
@ -234,7 +226,7 @@ string IRGenerator::generate(
t("deployedFunctions", m_context.functionCollector().requestedFunctions());
t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated()));
t("metadataName", yul::Object::metadataName());
t("cborMetadata", toHex(_cborMetadata));
t("cborMetadata", util::toHex(_cborMetadata));
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
{
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 it2 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c2);
return it1 < it2;

View File

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

View File

@ -33,6 +33,7 @@
#include <libsmtutil/CHCSmtLib2Interface.h>
#include <liblangutil/CharStreamProvider.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h>
#ifdef HAVE_Z3_DLOPEN
#include <z3_version.h>
@ -1497,7 +1498,7 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
auto const* contract = function->annotation().contract;
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;
@ -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>,
// so the assertion below should still hold.
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;
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.");

View File

@ -48,7 +48,7 @@ map<Predicate const*, set<string>> collectInvariants(
map<string, pair<smtutil::Expression, smtutil::Expression>> equalities;
// 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 == "=")
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);
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);
}
@ -412,7 +412,7 @@ pair<vector<optional<string>>, vector<VariableDeclaration const*>> Predicate::lo
auto first = _args.end() - static_cast<int>(localVars.size());
vector<smtutil::Expression> outValues(first, _args.end());
auto mask = applyMap(
auto mask = util::applyMap(
localVars,
[this](auto _var) {
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 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};
}
@ -496,7 +496,7 @@ optional<string> Predicate::expressionToString(smtutil::Expression const& _expr,
if (_expr.name == "0")
return "0x0";
// 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&)
{

View File

@ -115,6 +115,12 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract)
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)
{
// State variables are handled by the constructor.
@ -313,7 +319,7 @@ bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{
auto const& vars = _assignment.variableNames;
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))
assignedVars.insert(varDecl);
}

View File

@ -136,6 +136,7 @@ protected:
// because the order of expression evaluation is undefined
// 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;
void endVisit(ContractDefinition 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) {
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,

View File

@ -580,7 +580,7 @@ optional<smtutil::Expression> symbolicTypeConversion(frontend::Type const* _from
return smtutil::Expression(size_t(0));
auto bytesVec = util::asBytes(strType->value());
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;

View File

@ -278,7 +278,7 @@ void CompilerStack::setMetadataHash(MetadataHash _metadataHash)
void CompilerStack::selectDebugInfo(DebugInfoSelection _debugInfoSelection)
{
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;
}
@ -573,7 +573,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
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.checkRequestedSourcesAndContracts(allSources);
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())
{
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(
@ -1040,7 +1040,7 @@ Json::Value CompilerStack::interfaceSymbols(string const& _contractName) const
if (!event->isAnonymous())
{
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;
@ -1494,7 +1494,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
continue;
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())
meta["sources"][s.first]["license"] = *licenseString;
if (m_metadataLiteralSources)
@ -1502,7 +1502,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
else
{
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());
}
}
@ -1565,7 +1565,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
meta["settings"]["libraries"] = Json::objectValue;
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"]["userdoc"] = natspecUser(_contract);
@ -1677,7 +1677,7 @@ bytes CompilerStack::createCBORMetadata(Contract const& _contract, bool _forIR)
else
solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash");
if (experimentalMode || _forIR)
if (experimentalMode)
encoder.pushBool("experimental", true);
if (m_metadataFormat == MetadataFormat::WithReleaseVersionTag)
encoder.pushBytes("solc", VersionCompactBytes);

View File

@ -189,7 +189,7 @@ public:
/// Enable EVM Bytecode generation. This is enabled by default.
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; }
/// Enable experimental generation of Ewasm code. If enabled, IR is also generated.
@ -373,8 +373,8 @@ private:
std::shared_ptr<evmasm::Assembly> evmRuntimeAssembly;
evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object).
evmasm::LinkerObject runtimeObject; ///< Runtime object.
std::string yulIR; ///< Experimental Yul IR code.
std::string yulIROptimized; ///< Optimized experimental Yul IR code.
std::string yulIR; ///< Yul IR code.
std::string yulIROptimized; ///< Optimized Yul IR code.
std::string ewasm; ///< Experimental Ewasm text representation
evmasm::LinkerObject ewasmObject; ///< Experimental Ewasm code
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.
Source const& source(std::string const& _sourceName) const;
/// @param _forIR If true, include a flag that indicates that the bytecode comes from the
/// experimental IR codegen.
/// @param _forIR If true, include a flag that indicates that the bytecode comes from IR codegen.
/// @returns the metadata JSON as a compact string for the given contract.
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[r]cL" // Turn into SSA again and simplify
"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

View File

@ -1448,10 +1448,6 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
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();
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/StandardCompiler.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/CharStream.h>
@ -48,31 +53,6 @@ using namespace solidity::frontend;
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)
{
// 1=Error, 2=Warning, 3=Info, 4=Hint
@ -97,9 +77,11 @@ LanguageServer::LanguageServer(Transport& _transport):
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
{"initialized", [](auto, auto) {}},
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
{"textDocument/definition", GotoDefinition(*this) },
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
{"textDocument/implementation", GotoDefinition(*this) },
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
},
m_fileRepository("/" /* basePath */),
@ -107,55 +89,14 @@ LanguageServer::LanguageServer(Transport& _transport):
{
}
optional<SourceLocation> LanguageServer::parsePosition(
string const& _sourceUnitName,
Json::Value const& _position
) const
Json::Value LanguageServer::toRange(SourceLocation const& _location)
{
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
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;
return HandlerBase(*this).toRange(_location);
}
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
Json::Value LanguageServer::toJson(SourceLocation const& _location)
{
if (!_range.isObject())
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;
return HandlerBase(*this).toJson(_location);
}
void LanguageServer::changeConfiguration(Json::Value const& _settings)
@ -253,7 +194,7 @@ bool LanguageServer::run()
string const methodName = (*jsonMessage)["method"].asString();
id = (*jsonMessage)["id"];
if (auto handler = valueOrDefault(m_handlers, methodName))
if (auto handler = util::valueOrDefault(m_handlers, methodName))
handler(id, (*jsonMessage)["params"]);
else
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;
replyArgs["serverInfo"]["name"] = "solc";
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"]["openClose"] = true;
m_client.reply(_id, move(replyArgs));
}
@ -371,11 +314,11 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
string text = jsonContentChange["text"].asString();
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(
change && change->hasText(),
ErrorCode::RequestFailed,
"Invalid source range: " + jsonCompactPrint(jsonContentChange["range"])
"Invalid source range: " + util::jsonCompactPrint(jsonContentChange["range"])
);
string buffer = m_fileRepository.sourceUnits().at(sourceUnitName);
@ -403,3 +346,19 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
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.
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:
/// 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.
@ -66,28 +71,19 @@ private:
void handleTextDocumentDidOpen(Json::Value const& _args);
void handleTextDocumentDidChange(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).
void changeConfiguration(Json::Value const&);
/// Compile everything until after analysis phase.
void compile();
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
std::optional<langutil::SourceLocation> parsePosition(
std::string const& _sourceUnitName,
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;
Json::Value toRange(langutil::SourceLocation const& _location);
Json::Value toJson(langutil::SourceLocation const& _location);
// LSP related member fields
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown };
State m_state = State::Started;

View File

@ -69,7 +69,7 @@ private:
{ \
BOOST_THROW_EXCEPTION( \
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:
nodes.push_back(parseUserDefinedValueTypeDefinition());
break;
case Token::Using:
nodes.push_back(parseUsingDirective());
break;
case Token::Function:
nodes.push_back(parseFunctionDefinition(true));
break;
@ -962,16 +965,37 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
ASTNodeFactory nodeFactory(*this);
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;
expectToken(Token::For);
if (m_scanner->currentToken() == Token::Mul)
advance();
else
typeName = parseTypeName();
bool global = false;
if (m_scanner->currentToken() == Token::Identifier && currentLiteral() == "global")
{
global = true;
advance();
}
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UsingForDirective>(library, typeName);
return nodeFactory.createNode<UsingForDirective>(move(functions), usesBraces, typeName, global);
}
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()

View File

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

View File

@ -27,7 +27,9 @@
#include <libsolutil/CommonData.h>
#include <libsolutil/Numeric.h>
#include <algorithm>
#include <limits>
#include <locale>
#include <string>
#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);
}
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 <liblangutil/Scanner.h>
#include <boost/algorithm/string.hpp>
#include <optional>
using namespace std;
@ -71,8 +72,7 @@ evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
)
{
// 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};
asmSettings.isCreation = true;
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, _evmVersion, 0};
asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole;
@ -194,7 +194,10 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
yulAssert(_object.analysisInfo, "");
for (auto& subNode: _object.subObjects)
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);
unique_ptr<GasMeter> meter;
@ -281,7 +284,7 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
yulAssert(m_parserResult->code, "");
yulAssert(m_parserResult->analysisInfo, "");
evmasm::Assembly assembly;
evmasm::Assembly assembly(true, {});
EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);

View File

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

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