mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
commit
9d92c9fbf1
21
.github/ISSUE_TEMPLATE/general.md
vendored
21
.github/ISSUE_TEMPLATE/general.md
vendored
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
name: General Feedback
|
|
||||||
about: Any general feedback (neither feature request nor bug reports)
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--## Prerequisites
|
|
||||||
|
|
||||||
- First, many thanks for taking part in the community. We really appreciate that.
|
|
||||||
- Read the [contributing guidelines](http://solidity.readthedocs.io/en/latest/contributing.html).
|
|
||||||
- Support questions are better asked in one of the following locations:
|
|
||||||
- [Solidity chat](https://gitter.im/ethereum/solidity)
|
|
||||||
- [Stack Overflow](https://ethereum.stackexchange.com/)
|
|
||||||
- Ensure the issue isn't already reported.
|
|
||||||
|
|
||||||
*Delete the above section and the instructions in the sections below before submitting*
|
|
||||||
-->
|
|
||||||
## Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Please describe the purpose of your ticket.
|
|
||||||
-->
|
|
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,22 +0,0 @@
|
|||||||
<!--### Your checklist for this pull request
|
|
||||||
|
|
||||||
Please review the [guidelines for contributing](http://solidity.readthedocs.io/en/latest/contributing.html) to this repository.
|
|
||||||
|
|
||||||
Please also note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). By participating in this project you agree to abide by its terms.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Please explain the changes you made here.
|
|
||||||
|
|
||||||
Thank you for your help!
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Checklist
|
|
||||||
- [ ] Code compiles correctly
|
|
||||||
- [ ] All tests are passing
|
|
||||||
- [ ] New tests have been created which fail without the change (if possible)
|
|
||||||
- [ ] README / documentation was extended, if necessary
|
|
||||||
- [ ] Changelog entry (if change is visible to the user)
|
|
||||||
- [ ] Used meaningful commit messages
|
|
24
Changelog.md
24
Changelog.md
@ -9,20 +9,37 @@ Compiler Features:
|
|||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
||||||
|
|
||||||
### 0.6.4 (unreleased)
|
### 0.6.5 (unreleased)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
|
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Metadata: Added support for IPFS hashes of large files that need to be split in multiple chunks.
|
||||||
|
|
||||||
|
|
||||||
|
### 0.6.4 (2020-03-10)
|
||||||
|
|
||||||
|
Language Features:
|
||||||
|
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
|
||||||
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
||||||
|
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
||||||
|
* Commandline Interface: Enable output of experimental optimized IR via ``--ir-optimized``.
|
||||||
|
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* isoltest: Added new keyword `wei` to express function value in semantic tests
|
* Inheritance: Fix incorrect error on calling unimplemented base functions.
|
||||||
|
* Reference Resolver: Fix scoping issue following try/catch statements.
|
||||||
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
|
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
|
||||||
|
* SMTChecker: Fix internal errors when analysing tuples.
|
||||||
|
* Yul AST Import: correctly import blocks as statements, switch statements and string literals.
|
||||||
|
|
||||||
### 0.6.3 (2020-02-18)
|
### 0.6.3 (2020-02-18)
|
||||||
|
|
||||||
@ -37,6 +54,7 @@ Compiler Features:
|
|||||||
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
|
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
|
||||||
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
|
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
|
||||||
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
|
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
|
||||||
|
* SMTChecker: CHC support to internal function calls.
|
||||||
|
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
@ -11,7 +11,7 @@ You can interleave Solidity statements with inline assembly in a language close
|
|||||||
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
|
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
|
||||||
which is especially useful when you are enhancing the language by writing libraries.
|
which is especially useful when you are enhancing the language by writing libraries.
|
||||||
|
|
||||||
The language used for inline assembly in Solidity is called `Yul <yul>`_
|
The language used for inline assembly in Solidity is called :ref:`Yul <yul>`
|
||||||
and it is documented in its own section. This section will only cover
|
and it is documented in its own section. This section will only cover
|
||||||
how the inline assembly code can interface with the surrounding Solidity code.
|
how the inline assembly code can interface with the surrounding Solidity code.
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ how the inline assembly code can interface with the surrounding Solidity code.
|
|||||||
|
|
||||||
|
|
||||||
An inline assembly block is marked by ``assembly { ... }``, where the code inside
|
An inline assembly block is marked by ``assembly { ... }``, where the code inside
|
||||||
the curly braces is code in the `Yul <yul>`_ language.
|
the curly braces is code in the :ref:`Yul <yul>` language.
|
||||||
|
|
||||||
The inline assembly code can access local Solidity variables as explained below.
|
The inline assembly code can access local Solidity variables as explained below.
|
||||||
|
|
||||||
|
@ -888,5 +888,9 @@
|
|||||||
"0.6.3": {
|
"0.6.3": {
|
||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2020-02-18"
|
"released": "2020-02-18"
|
||||||
|
},
|
||||||
|
"0.6.4": {
|
||||||
|
"bugs": [],
|
||||||
|
"released": "2020-03-10"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -252,7 +252,7 @@ which only need to be created if there is a dispute.
|
|||||||
/// This complicated expression just tells you how the address
|
/// This complicated expression just tells you how the address
|
||||||
/// can be pre-computed. It is just there for illustration.
|
/// can be pre-computed. It is just there for illustration.
|
||||||
/// You actually only need ``new D{salt: salt}(arg)``.
|
/// You actually only need ``new D{salt: salt}(arg)``.
|
||||||
address predictedAddress = address(bytes20(keccak256(abi.encodePacked(
|
address predictedAddress = address(uint(keccak256(abi.encodePacked(
|
||||||
byte(0xff),
|
byte(0xff),
|
||||||
address(this),
|
address(this),
|
||||||
salt,
|
salt,
|
||||||
|
@ -547,6 +547,7 @@ not mean loss of proving power.
|
|||||||
|
|
||||||
pragma solidity >=0.5.0;
|
pragma solidity >=0.5.0;
|
||||||
pragma experimental SMTChecker;
|
pragma experimental SMTChecker;
|
||||||
|
// This may report a warning if no SMT solver available.
|
||||||
|
|
||||||
contract Recover
|
contract Recover
|
||||||
{
|
{
|
||||||
@ -601,6 +602,7 @@ types.
|
|||||||
pragma solidity >=0.5.0;
|
pragma solidity >=0.5.0;
|
||||||
pragma experimental SMTChecker;
|
pragma experimental SMTChecker;
|
||||||
// This will report a warning
|
// This will report a warning
|
||||||
|
|
||||||
contract Aliasing
|
contract Aliasing
|
||||||
{
|
{
|
||||||
uint[] array;
|
uint[] array;
|
||||||
|
@ -646,7 +646,7 @@ External (or public) functions have the following members:
|
|||||||
Example that shows how to use the members::
|
Example that shows how to use the members::
|
||||||
|
|
||||||
pragma solidity >=0.4.16 <0.8.0;
|
pragma solidity >=0.4.16 <0.8.0;
|
||||||
|
// This will report a warning
|
||||||
|
|
||||||
contract Example {
|
contract Example {
|
||||||
function f() public payable returns (bytes4) {
|
function f() public payable returns (bytes4) {
|
||||||
|
@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(AssemblyItem const& _item)
|
||||||
|
{
|
||||||
|
if (_item.type() != Operation)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return reverts(_item.instruction());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(Instruction _instruction)
|
||||||
|
{
|
||||||
|
switch (_instruction)
|
||||||
|
{
|
||||||
|
case Instruction::INVALID:
|
||||||
|
case Instruction::REVERT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
if (_item.type() != Operation)
|
if (_item.type() != Operation)
|
||||||
|
@ -47,6 +47,8 @@ struct SemanticInformation
|
|||||||
static bool altersControlFlow(AssemblyItem const& _item);
|
static bool altersControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(Instruction _instruction);
|
static bool terminatesControlFlow(Instruction _instruction);
|
||||||
|
static bool reverts(AssemblyItem const& _item);
|
||||||
|
static bool reverts(Instruction _instruction);
|
||||||
/// @returns false if the value put on the stack by _item depends on anything else than
|
/// @returns false if the value put on the stack by _item depends on anything else than
|
||||||
/// the information in the current block header, memory, storage or stack.
|
/// the information in the current block header, memory, storage or stack.
|
||||||
static bool isDeterministic(AssemblyItem const& _item);
|
static bool isDeterministic(AssemblyItem const& _item);
|
||||||
|
@ -166,6 +166,7 @@ namespace solidity::langutil
|
|||||||
K(Indexed, "indexed", 0) \
|
K(Indexed, "indexed", 0) \
|
||||||
K(Interface, "interface", 0) \
|
K(Interface, "interface", 0) \
|
||||||
K(Internal, "internal", 0) \
|
K(Internal, "internal", 0) \
|
||||||
|
K(Immutable, "immutable", 0) \
|
||||||
K(Import, "import", 0) \
|
K(Import, "import", 0) \
|
||||||
K(Is, "is", 0) \
|
K(Is, "is", 0) \
|
||||||
K(Library, "library", 0) \
|
K(Library, "library", 0) \
|
||||||
@ -243,7 +244,6 @@ namespace solidity::langutil
|
|||||||
K(Default, "default", 0) \
|
K(Default, "default", 0) \
|
||||||
K(Define, "define", 0) \
|
K(Define, "define", 0) \
|
||||||
K(Final, "final", 0) \
|
K(Final, "final", 0) \
|
||||||
K(Immutable, "immutable", 0) \
|
|
||||||
K(Implements, "implements", 0) \
|
K(Implements, "implements", 0) \
|
||||||
K(In, "in", 0) \
|
K(In, "in", 0) \
|
||||||
K(Inline, "inline", 0) \
|
K(Inline, "inline", 0) \
|
||||||
|
@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
|
|||||||
{
|
{
|
||||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
auto const& functionFlow = m_cfg.functionFlow(_function);
|
||||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
||||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
|
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
|
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
variableOccurrence->occurrence() ?
|
variableOccurrence->occurrence() ?
|
||||||
variableOccurrence->occurrence()->location() :
|
*variableOccurrence->occurrence() :
|
||||||
variableOccurrence->declaration().location(),
|
variableOccurrence->declaration().location(),
|
||||||
ssl,
|
ssl,
|
||||||
string("This variable is of storage pointer type and can be ") +
|
string("This variable is of storage pointer type and can be ") +
|
||||||
@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
|
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
|
||||||
{
|
{
|
||||||
// collect all nodes reachable from the entry point
|
// collect all nodes reachable from the entry point
|
||||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||||
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
|||||||
}
|
}
|
||||||
).visited;
|
).visited;
|
||||||
|
|
||||||
// traverse all paths backwards from exit and revert
|
// traverse all paths backwards from exit, revert and transaction return
|
||||||
// and extract (valid) source locations of unreachable nodes into sorted set
|
// and extract (valid) source locations of unreachable nodes into sorted set
|
||||||
std::set<SourceLocation> unreachable;
|
std::set<SourceLocation> unreachable;
|
||||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
|
||||||
[&](CFGNode const* _node, auto&& _addChild) {
|
[&](CFGNode const* _node, auto&& _addChild) {
|
||||||
if (!reachable.count(_node) && _node->location.isValid())
|
if (!reachable.count(_node) && _node->location.isValid())
|
||||||
unreachable.insert(_node->location);
|
unreachable.insert(_node->location);
|
||||||
|
@ -36,9 +36,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
||||||
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
||||||
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
|
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
|
||||||
/// that can not be reached from @param _entry.
|
/// that can not be reached from @param _entry.
|
||||||
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const;
|
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
|
||||||
|
|
||||||
CFG const& m_cfg;
|
CFG const& m_cfg;
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
||||||
|
#include <libyul/AsmData.h>
|
||||||
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
|
|||||||
m_nodeContainer(_nodeContainer),
|
m_nodeContainer(_nodeContainer),
|
||||||
m_currentNode(_functionFlow.entry),
|
m_currentNode(_functionFlow.entry),
|
||||||
m_returnNode(_functionFlow.exit),
|
m_returnNode(_functionFlow.exit),
|
||||||
m_revertNode(_functionFlow.revert)
|
m_revertNode(_functionFlow.revert),
|
||||||
|
m_transactionReturnNode(_functionFlow.transactionReturn)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
||||||
CFG::NodeContainer& _nodeContainer,
|
CFG::NodeContainer& _nodeContainer,
|
||||||
FunctionDefinition const& _function
|
FunctionDefinition const& _function
|
||||||
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
|||||||
functionFlow->entry = _nodeContainer.newNode();
|
functionFlow->entry = _nodeContainer.newNode();
|
||||||
functionFlow->exit = _nodeContainer.newNode();
|
functionFlow->exit = _nodeContainer.newNode();
|
||||||
functionFlow->revert = _nodeContainer.newNode();
|
functionFlow->revert = _nodeContainer.newNode();
|
||||||
|
functionFlow->transactionReturn = _nodeContainer.newNode();
|
||||||
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
||||||
builder.appendControlFlow(_function);
|
builder.appendControlFlow(_function);
|
||||||
|
|
||||||
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
|
|||||||
if (_forStatement.condition())
|
if (_forStatement.condition())
|
||||||
appendControlFlow(*_forStatement.condition());
|
appendControlFlow(*_forStatement.condition());
|
||||||
|
|
||||||
auto loopExpression = newLabel();
|
auto postPart = newLabel();
|
||||||
auto nodes = splitFlow<2>();
|
auto nodes = splitFlow<2>();
|
||||||
auto afterFor = nodes[1];
|
auto afterFor = nodes[1];
|
||||||
m_currentNode = nodes[0];
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
{
|
{
|
||||||
BreakContinueScope scope(*this, afterFor, loopExpression);
|
BreakContinueScope scope(*this, afterFor, postPart);
|
||||||
appendControlFlow(_forStatement.body());
|
appendControlFlow(_forStatement.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
placeAndConnectLabel(loopExpression);
|
placeAndConnectLabel(postPart);
|
||||||
|
|
||||||
if (auto expression = _forStatement.loopExpression())
|
if (auto expression = _forStatement.loopExpression())
|
||||||
appendControlFlow(*expression);
|
appendControlFlow(*expression);
|
||||||
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
|
|||||||
appendControlFlow(*returnParameter);
|
appendControlFlow(*returnParameter);
|
||||||
m_returnNode->variableOccurrences.emplace_back(
|
m_returnNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Return,
|
VariableOccurrence::Kind::Return
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
&_return
|
_return.location()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
connect(m_currentNode, m_returnNode);
|
connect(m_currentNode, m_returnNode);
|
||||||
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
|
|||||||
|
|
||||||
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
||||||
{
|
{
|
||||||
solAssert(!!m_currentNode, "");
|
solAssert(!!m_currentNode && !m_inlineAssembly, "");
|
||||||
visitNode(_inlineAssembly);
|
|
||||||
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
|
m_inlineAssembly = &_inlineAssembly;
|
||||||
|
(*this)(_inlineAssembly.operations());
|
||||||
|
m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::visit(yul::Statement const& _statement)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
|
||||||
|
ASTWalker::visit(_statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::If const& _if)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_if.condition);
|
||||||
|
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
(*this)(_if.body);
|
||||||
|
nodes[0] = m_currentNode;
|
||||||
|
mergeFlow(nodes, nodes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_switch.expression);
|
||||||
|
|
||||||
|
auto beforeSwitch = m_currentNode;
|
||||||
|
|
||||||
|
auto nodes = splitFlow(_switch.cases.size());
|
||||||
|
for (size_t i = 0u; i < _switch.cases.size(); ++i)
|
||||||
{
|
{
|
||||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
|
m_currentNode = nodes[i];
|
||||||
|
(*this)(_switch.cases[i].body);
|
||||||
|
nodes[i] = m_currentNode;
|
||||||
|
}
|
||||||
|
mergeFlow(nodes);
|
||||||
|
|
||||||
|
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
|
||||||
|
if (!hasDefault)
|
||||||
|
connect(beforeSwitch, m_currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
|
||||||
|
(*this)(_forLoop.pre);
|
||||||
|
|
||||||
|
auto condition = createLabelHere();
|
||||||
|
|
||||||
|
if (_forLoop.condition)
|
||||||
|
visit(*_forLoop.condition);
|
||||||
|
|
||||||
|
auto loopExpression = newLabel();
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
auto afterFor = nodes[1];
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
|
{
|
||||||
|
BreakContinueScope scope(*this, afterFor, loopExpression);
|
||||||
|
(*this)(_forLoop.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
placeAndConnectLabel(loopExpression);
|
||||||
|
|
||||||
|
(*this)(_forLoop.post);
|
||||||
|
|
||||||
|
connect(m_currentNode, condition);
|
||||||
|
m_currentNode = afterFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Break const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_breakJump, "");
|
||||||
|
connect(m_currentNode, m_breakJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Continue const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_continueJump, "");
|
||||||
|
connect(m_currentNode, m_continueJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
if (externalReferences.count(&_identifier))
|
||||||
|
{
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*variableDeclaration,
|
*declaration,
|
||||||
VariableOccurrence::Kind::InlineAssembly,
|
VariableOccurrence::Kind::Access,
|
||||||
&_inlineAssembly
|
_identifier.location
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_assignment.value);
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
for (auto const& variable: _assignment.variableNames)
|
||||||
|
if (externalReferences.count(&variable))
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
|
||||||
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
|
*declaration,
|
||||||
|
VariableOccurrence::Kind::Assignment,
|
||||||
|
variable.location
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
using namespace yul;
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
yul::ASTWalker::operator()(_functionCall);
|
||||||
|
|
||||||
|
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
||||||
|
if (builtinFunction->controlFlowSideEffects.terminates)
|
||||||
|
{
|
||||||
|
if (builtinFunction->controlFlowSideEffects.reverts)
|
||||||
|
connect(m_currentNode, m_revertNode);
|
||||||
|
else
|
||||||
|
connect(m_currentNode, m_transactionReturnNode);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
// External references cannot be accessed from within functions, so we can ignore their control flow.
|
||||||
|
// TODO: we might still want to track if they always revert or return, though.
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Leave const&)
|
||||||
|
{
|
||||||
|
// This has to be implemented, if we ever decide to visit functions.
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||||
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
|
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Declaration,
|
VariableOccurrence::Kind::Declaration
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle declaration with immediate assignment.
|
// Handle declaration with immediate assignment.
|
||||||
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
_variableDeclaration.value().get()
|
_variableDeclaration.value()->location()
|
||||||
);
|
);
|
||||||
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
||||||
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*var,
|
*var,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
expression
|
expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
|
|||||||
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
||||||
VariableOccurrence::Kind::Assignment :
|
VariableOccurrence::Kind::Assignment :
|
||||||
VariableOccurrence::Kind::Access,
|
VariableOccurrence::Kind::Access,
|
||||||
&_identifier
|
_identifier.location()
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -30,7 +31,7 @@ namespace solidity::frontend {
|
|||||||
* Modifiers are not yet applied to the functions. This is done in a second
|
* Modifiers are not yet applied to the functions. This is done in a second
|
||||||
* step in the CFG class.
|
* step in the CFG class.
|
||||||
*/
|
*/
|
||||||
class ControlFlowBuilder: private ASTConstVisitor
|
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
||||||
@ -39,7 +40,10 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
|
explicit ControlFlowBuilder(
|
||||||
|
CFG::NodeContainer& _nodeContainer,
|
||||||
|
FunctionFlow const& _functionFlow
|
||||||
|
);
|
||||||
|
|
||||||
// Visits for constructing the control flow.
|
// Visits for constructing the control flow.
|
||||||
bool visit(BinaryOperation const& _operation) override;
|
bool visit(BinaryOperation const& _operation) override;
|
||||||
@ -62,6 +66,17 @@ private:
|
|||||||
// Visits for filling variable occurrences.
|
// Visits for filling variable occurrences.
|
||||||
bool visit(FunctionTypeName const& _functionTypeName) override;
|
bool visit(FunctionTypeName const& _functionTypeName) override;
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||||
|
void visit(yul::Statement const& _statement) override;
|
||||||
|
void operator()(yul::If const& _if) override;
|
||||||
|
void operator()(yul::Switch const& _switch) override;
|
||||||
|
void operator()(yul::ForLoop const& _for) override;
|
||||||
|
void operator()(yul::Break const&) override;
|
||||||
|
void operator()(yul::Continue const&) override;
|
||||||
|
void operator()(yul::Identifier const& _identifier) override;
|
||||||
|
void operator()(yul::Assignment const& _assignment) override;
|
||||||
|
void operator()(yul::FunctionCall const& _functionCall) override;
|
||||||
|
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
|
||||||
|
void operator()(yul::Leave const& _leaveStatement) override;
|
||||||
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
||||||
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
@ -70,6 +85,9 @@ protected:
|
|||||||
bool visitNode(ASTNode const&) override;
|
bool visitNode(ASTNode const&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using ASTConstVisitor::visit;
|
||||||
|
using yul::ASTWalker::visit;
|
||||||
|
using yul::ASTWalker::operator();
|
||||||
|
|
||||||
/// Appends the control flow of @a _node to the current control flow.
|
/// Appends the control flow of @a _node to the current control flow.
|
||||||
void appendControlFlow(ASTNode const& _node);
|
void appendControlFlow(ASTNode const& _node);
|
||||||
@ -136,6 +154,7 @@ private:
|
|||||||
CFGNode* m_currentNode = nullptr;
|
CFGNode* m_currentNode = nullptr;
|
||||||
CFGNode* m_returnNode = nullptr;
|
CFGNode* m_returnNode = nullptr;
|
||||||
CFGNode* m_revertNode = nullptr;
|
CFGNode* m_revertNode = nullptr;
|
||||||
|
CFGNode* m_transactionReturnNode = nullptr;
|
||||||
|
|
||||||
/// The current jump destination of break Statements.
|
/// The current jump destination of break Statements.
|
||||||
CFGNode* m_breakJump = nullptr;
|
CFGNode* m_breakJump = nullptr;
|
||||||
@ -145,6 +164,8 @@ private:
|
|||||||
CFGNode* m_placeholderEntry = nullptr;
|
CFGNode* m_placeholderEntry = nullptr;
|
||||||
CFGNode* m_placeholderExit = nullptr;
|
CFGNode* m_placeholderExit = nullptr;
|
||||||
|
|
||||||
|
InlineAssembly const* m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
/// Helper class that replaces the break and continue jump destinations for the
|
/// Helper class that replaces the break and continue jump destinations for the
|
||||||
/// current scope and restores the originals at the end of the scope.
|
/// current scope and restores the originals at the end of the scope.
|
||||||
class BreakContinueScope
|
class BreakContinueScope
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
#include <liblangutil/ErrorReporter.h>
|
#include <liblangutil/ErrorReporter.h>
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
#include <liblangutil/SourceLocation.h>
|
#include <liblangutil/SourceLocation.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -33,8 +34,8 @@ namespace solidity::frontend
|
|||||||
/**
|
/**
|
||||||
* Occurrence of a variable in a block of control flow.
|
* Occurrence of a variable in a block of control flow.
|
||||||
* Stores the declaration of the referenced variable, the
|
* Stores the declaration of the referenced variable, the
|
||||||
* kind of the occurrence and possibly the node at which
|
* kind of the occurrence and possibly the source location
|
||||||
* it occurred.
|
* at which it occurred.
|
||||||
*/
|
*/
|
||||||
class VariableOccurrence
|
class VariableOccurrence
|
||||||
{
|
{
|
||||||
@ -47,7 +48,7 @@ public:
|
|||||||
Assignment,
|
Assignment,
|
||||||
InlineAssembly
|
InlineAssembly
|
||||||
};
|
};
|
||||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence):
|
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
|
||||||
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -57,8 +58,8 @@ public:
|
|||||||
{
|
{
|
||||||
if (m_occurrence && _rhs.m_occurrence)
|
if (m_occurrence && _rhs.m_occurrence)
|
||||||
{
|
{
|
||||||
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
|
if (*m_occurrence < *_rhs.m_occurrence) return true;
|
||||||
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
|
if (*_rhs.m_occurrence < *m_occurrence) return false;
|
||||||
}
|
}
|
||||||
else if (_rhs.m_occurrence)
|
else if (_rhs.m_occurrence)
|
||||||
return true;
|
return true;
|
||||||
@ -74,14 +75,14 @@ public:
|
|||||||
|
|
||||||
VariableDeclaration const& declaration() const { return m_declaration; }
|
VariableDeclaration const& declaration() const { return m_declaration; }
|
||||||
Kind kind() const { return m_occurrenceKind; };
|
Kind kind() const { return m_occurrenceKind; };
|
||||||
ASTNode const* occurrence() const { return m_occurrence; }
|
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
|
||||||
private:
|
private:
|
||||||
/// Declaration of the occurring variable.
|
/// Declaration of the occurring variable.
|
||||||
VariableDeclaration const& m_declaration;
|
VariableDeclaration const& m_declaration;
|
||||||
/// Kind of occurrence.
|
/// Kind of occurrence.
|
||||||
Kind m_occurrenceKind = Kind::Access;
|
Kind m_occurrenceKind = Kind::Access;
|
||||||
/// AST node at which the variable occurred, if available (may be nullptr).
|
/// Source location at which the variable occurred, if available (may be nullptr).
|
||||||
ASTNode const* m_occurrence = nullptr;
|
std::optional<langutil::SourceLocation> m_occurrence;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,6 +120,10 @@ struct FunctionFlow
|
|||||||
/// This node is empty does not have any exits, but may have multiple entries
|
/// This node is empty does not have any exits, but may have multiple entries
|
||||||
/// (e.g. all assert, require, revert and throw statements).
|
/// (e.g. all assert, require, revert and throw statements).
|
||||||
CFGNode* revert = nullptr;
|
CFGNode* revert = nullptr;
|
||||||
|
/// Transaction return node. Destination node for inline assembly "return" calls.
|
||||||
|
/// This node is empty and does not have any exits, but may have multiple entries
|
||||||
|
/// (e.g. all inline assembly return calls).
|
||||||
|
CFGNode* transactionReturn = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CFG: private ASTConstVisitor
|
class CFG: private ASTConstVisitor
|
||||||
@ -140,7 +145,6 @@ public:
|
|||||||
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
/// Node container.
|
/// Node container.
|
||||||
|
@ -67,6 +67,22 @@ void ReferencesResolver::endVisit(Block const& _block)
|
|||||||
m_resolver.setScope(_block.scope());
|
m_resolver.setScope(_block.scope());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ReferencesResolver::visit(TryCatchClause const& _tryCatchClause)
|
||||||
|
{
|
||||||
|
if (!m_resolveInsideCode)
|
||||||
|
return false;
|
||||||
|
m_resolver.setScope(&_tryCatchClause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReferencesResolver::endVisit(TryCatchClause const& _tryCatchClause)
|
||||||
|
{
|
||||||
|
if (!m_resolveInsideCode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_resolver.setScope(_tryCatchClause.scope());
|
||||||
|
}
|
||||||
|
|
||||||
bool ReferencesResolver::visit(ForStatement const& _for)
|
bool ReferencesResolver::visit(ForStatement const& _for)
|
||||||
{
|
{
|
||||||
if (!m_resolveInsideCode)
|
if (!m_resolveInsideCode)
|
||||||
|
@ -70,6 +70,8 @@ private:
|
|||||||
|
|
||||||
bool visit(Block const& _block) override;
|
bool visit(Block const& _block) override;
|
||||||
void endVisit(Block const& _block) override;
|
void endVisit(Block const& _block) override;
|
||||||
|
bool visit(TryCatchClause const& _tryCatchClause) override;
|
||||||
|
void endVisit(TryCatchClause const& _tryCatchClause) override;
|
||||||
bool visit(ForStatement const& _for) override;
|
bool visit(ForStatement const& _for) override;
|
||||||
void endVisit(ForStatement const& _for) override;
|
void endVisit(ForStatement const& _for) override;
|
||||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||||
|
@ -1703,22 +1703,21 @@ void TypeChecker::typeCheckFunctionCall(
|
|||||||
|
|
||||||
if (_functionType->kind() == FunctionType::Kind::Declaration)
|
if (_functionType->kind() == FunctionType::Kind::Declaration)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(
|
if (
|
||||||
_functionCall.location(),
|
m_scope->derivesFrom(*_functionType->declaration().annotation().contract) &&
|
||||||
"Cannot call function via contract type name."
|
!dynamic_cast<FunctionDefinition const&>(_functionType->declaration()).isImplemented()
|
||||||
);
|
)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCall.location(),
|
||||||
|
"Cannot call unimplemented base function."
|
||||||
|
);
|
||||||
|
else
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCall.location(),
|
||||||
|
"Cannot call function via contract type name."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_functionType->kind() == FunctionType::Kind::Internal && _functionType->hasDeclaration())
|
|
||||||
if (auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(&_functionType->declaration()))
|
|
||||||
// functionDefinition->annotation().contract != m_scope ensures that this is a qualified access,
|
|
||||||
// e.g. ``A.f();`` instead of a simple function call like ``f();`` (the latter is valid for unimplemented
|
|
||||||
// functions).
|
|
||||||
if (functionDefinition->annotation().contract != m_scope && !functionDefinition->isImplemented())
|
|
||||||
m_errorReporter.typeError(
|
|
||||||
_functionCall.location(),
|
|
||||||
"Cannot call unimplemented base function."
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for unsupported use of bare static call
|
// Check for unsupported use of bare static call
|
||||||
if (
|
if (
|
||||||
@ -2312,7 +2311,11 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
|||||||
else if (!expressionFunctionType->isPayable())
|
else if (!expressionFunctionType->isPayable())
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
_functionCallOptions.location(),
|
_functionCallOptions.location(),
|
||||||
"Cannot set option \"value\" on a non-payable function type."
|
kind == FunctionType::Kind::Creation ?
|
||||||
|
"Cannot set option \"value\", since the constructor of " +
|
||||||
|
expressionFunctionType->returnParameterTypes().front()->toString() +
|
||||||
|
" is not payable." :
|
||||||
|
"Cannot set option \"value\" on a non-payable function type."
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2522,12 +2525,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
annotation.type = possibleMembers.front().type;
|
annotation.type = possibleMembers.front().type;
|
||||||
|
|
||||||
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
||||||
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
||||||
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
||||||
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
dynamic_cast<FunctionType const*>(exprType) &&
|
||||||
|
!annotation.referencedDeclaration &&
|
||||||
|
(memberName == "value" || memberName == "gas")
|
||||||
|
)
|
||||||
|
m_errorReporter.warning(
|
||||||
|
_memberAccess.location(),
|
||||||
|
"Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
||||||
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
||||||
else if (exprType->category() == Type::Category::Array)
|
else if (exprType->category() == Type::Category::Array)
|
||||||
|
@ -390,7 +390,7 @@ DeclarationAnnotation& Declaration::annotation() const
|
|||||||
bool VariableDeclaration::isLValue() const
|
bool VariableDeclaration::isLValue() const
|
||||||
{
|
{
|
||||||
// Constant declared variables are Read-Only
|
// Constant declared variables are Read-Only
|
||||||
if (m_isConstant)
|
if (isConstant())
|
||||||
return false;
|
return false;
|
||||||
// External function arguments of reference type are Read-Only
|
// External function arguments of reference type are Read-Only
|
||||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
||||||
|
@ -814,6 +814,7 @@ class VariableDeclaration: public Declaration
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Location { Unspecified, Storage, Memory, CallData };
|
enum Location { Unspecified, Storage, Memory, CallData };
|
||||||
|
enum class Constantness { Mutable, Immutable, Constant };
|
||||||
|
|
||||||
VariableDeclaration(
|
VariableDeclaration(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
@ -824,7 +825,7 @@ public:
|
|||||||
Visibility _visibility,
|
Visibility _visibility,
|
||||||
bool _isStateVar = false,
|
bool _isStateVar = false,
|
||||||
bool _isIndexed = false,
|
bool _isIndexed = false,
|
||||||
bool _isConstant = false,
|
Constantness _constantness = Constantness::Mutable,
|
||||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
||||||
Location _referenceLocation = Location::Unspecified
|
Location _referenceLocation = Location::Unspecified
|
||||||
):
|
):
|
||||||
@ -833,7 +834,7 @@ public:
|
|||||||
m_value(_value),
|
m_value(_value),
|
||||||
m_isStateVariable(_isStateVar),
|
m_isStateVariable(_isStateVar),
|
||||||
m_isIndexed(_isIndexed),
|
m_isIndexed(_isIndexed),
|
||||||
m_isConstant(_isConstant),
|
m_constantness(_constantness),
|
||||||
m_overrides(_overrides),
|
m_overrides(_overrides),
|
||||||
m_location(_referenceLocation) {}
|
m_location(_referenceLocation) {}
|
||||||
|
|
||||||
@ -877,7 +878,7 @@ public:
|
|||||||
bool hasReferenceOrMappingType() const;
|
bool hasReferenceOrMappingType() const;
|
||||||
bool isStateVariable() const { return m_isStateVariable; }
|
bool isStateVariable() const { return m_isStateVariable; }
|
||||||
bool isIndexed() const { return m_isIndexed; }
|
bool isIndexed() const { return m_isIndexed; }
|
||||||
bool isConstant() const { return m_isConstant; }
|
bool isConstant() const { return m_constantness == Constantness::Constant; }
|
||||||
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
|
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
|
||||||
Location referenceLocation() const { return m_location; }
|
Location referenceLocation() const { return m_location; }
|
||||||
/// @returns a set of allowed storage locations for the variable.
|
/// @returns a set of allowed storage locations for the variable.
|
||||||
@ -904,7 +905,8 @@ private:
|
|||||||
ASTPointer<Expression> m_value;
|
ASTPointer<Expression> m_value;
|
||||||
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
|
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
|
||||||
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
|
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
|
||||||
bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
|
/// Whether the variable is "constant", "immutable" or non-marked (mutable).
|
||||||
|
Constantness m_constantness = Constantness::Mutable;
|
||||||
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
|
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
|
||||||
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
|
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
|
||||||
};
|
};
|
||||||
|
@ -411,6 +411,12 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
|||||||
{
|
{
|
||||||
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
|
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
|
||||||
|
|
||||||
|
VariableDeclaration::Constantness constantness{};
|
||||||
|
if (memberAsBool(_node, "constant"))
|
||||||
|
constantness = VariableDeclaration::Constantness::Constant;
|
||||||
|
else
|
||||||
|
constantness = VariableDeclaration::Constantness::Mutable;
|
||||||
|
|
||||||
return createASTNode<VariableDeclaration>(
|
return createASTNode<VariableDeclaration>(
|
||||||
_node,
|
_node,
|
||||||
nullOrCast<TypeName>(member(_node, "typeName")),
|
nullOrCast<TypeName>(member(_node, "typeName")),
|
||||||
@ -419,7 +425,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
|||||||
visibility(_node),
|
visibility(_node),
|
||||||
memberAsBool(_node, "stateVariable"),
|
memberAsBool(_node, "stateVariable"),
|
||||||
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
||||||
memberAsBool(_node, "constant"),
|
constantness,
|
||||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||||
location(_node)
|
location(_node)
|
||||||
);
|
);
|
||||||
|
@ -104,6 +104,8 @@ yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node)
|
|||||||
return createContinue(_node);
|
return createContinue(_node);
|
||||||
else if (nodeType == "Leave")
|
else if (nodeType == "Leave")
|
||||||
return createLeave(_node);
|
return createLeave(_node);
|
||||||
|
else if (nodeType == "Block")
|
||||||
|
return createBlock(_node);
|
||||||
else
|
else
|
||||||
astAssert(false, "Invalid nodeType as statement");
|
astAssert(false, "Invalid nodeType as statement");
|
||||||
}
|
}
|
||||||
@ -158,10 +160,9 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
lit.value = YulString{member(_node, "value").asString()};
|
lit.value = YulString{member(_node, "value").asString()};
|
||||||
lit.type= YulString{member(_node, "type").asString()};
|
lit.type= YulString{member(_node, "type").asString()};
|
||||||
|
|
||||||
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
|
||||||
|
|
||||||
if (kind == "number")
|
if (kind == "number")
|
||||||
{
|
{
|
||||||
|
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||||
lit.kind = yul::LiteralKind::Number;
|
lit.kind = yul::LiteralKind::Number;
|
||||||
astAssert(
|
astAssert(
|
||||||
scanner.currentToken() == Token::Number,
|
scanner.currentToken() == Token::Number,
|
||||||
@ -170,6 +171,7 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
}
|
}
|
||||||
else if (kind == "bool")
|
else if (kind == "bool")
|
||||||
{
|
{
|
||||||
|
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||||
lit.kind = yul::LiteralKind::Boolean;
|
lit.kind = yul::LiteralKind::Boolean;
|
||||||
astAssert(
|
astAssert(
|
||||||
scanner.currentToken() == Token::TrueLiteral ||
|
scanner.currentToken() == Token::TrueLiteral ||
|
||||||
@ -180,7 +182,10 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
else if (kind == "string")
|
else if (kind == "string")
|
||||||
{
|
{
|
||||||
lit.kind = yul::LiteralKind::String;
|
lit.kind = yul::LiteralKind::String;
|
||||||
astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!");
|
astAssert(
|
||||||
|
lit.value.str().size() <= 32,
|
||||||
|
"String literal too long (" + to_string(lit.value.str().size()) + " > 32)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(false, "unknown type of literal");
|
solAssert(false, "unknown type of literal");
|
||||||
@ -268,7 +273,11 @@ yul::If AsmJsonImporter::createIf(Json::Value const& _node)
|
|||||||
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
||||||
{
|
{
|
||||||
auto caseStatement = createAsmNode<yul::Case>(_node);
|
auto caseStatement = createAsmNode<yul::Case>(_node);
|
||||||
caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique<yul::Literal>(createLiteral(member(_node, "value")));
|
auto const& value = member(_node, "value");
|
||||||
|
if (value.isString())
|
||||||
|
astAssert(value.asString() == "default", "Expected default case");
|
||||||
|
else
|
||||||
|
caseStatement.value = make_unique<yul::Literal>(createLiteral(value));
|
||||||
caseStatement.body = createBlock(member(_node, "body"));
|
caseStatement.body = createBlock(member(_node, "body"));
|
||||||
return caseStatement;
|
return caseStatement;
|
||||||
}
|
}
|
||||||
@ -276,7 +285,7 @@ yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
|||||||
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
|
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
|
||||||
{
|
{
|
||||||
auto switchStatement = createAsmNode<yul::Switch>(_node);
|
auto switchStatement = createAsmNode<yul::Switch>(_node);
|
||||||
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "value")));
|
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "expression")));
|
||||||
for (auto const& var: member(_node, "cases"))
|
for (auto const& var: member(_node, "cases"))
|
||||||
switchStatement.cases.emplace_back(createCase(var));
|
switchStatement.cases.emplace_back(createCase(var));
|
||||||
return switchStatement;
|
return switchStatement;
|
||||||
|
@ -2924,7 +2924,10 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
|
|||||||
{
|
{
|
||||||
case Kind::External:
|
case Kind::External:
|
||||||
case Kind::DelegateCall:
|
case Kind::DelegateCall:
|
||||||
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
|
slots = {
|
||||||
|
make_tuple("address", TypeProvider::address()),
|
||||||
|
make_tuple("functionIdentifier", TypeProvider::uint(32))
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
case Kind::BareCall:
|
case Kind::BareCall:
|
||||||
case Kind::BareCallCode:
|
case Kind::BareCallCode:
|
||||||
@ -3471,7 +3474,15 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
||||||
members.emplace_back(declaration->name(), declaration->type(), declaration);
|
{
|
||||||
|
if (
|
||||||
|
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(declaration);
|
||||||
|
functionDefinition && !functionDefinition->isImplemented()
|
||||||
|
)
|
||||||
|
members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration);
|
||||||
|
else
|
||||||
|
members.emplace_back(declaration->name(), declaration->type(), declaration);
|
||||||
|
}
|
||||||
else if (
|
else if (
|
||||||
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
||||||
declaration->isVisibleViaContractTypeAccess()
|
declaration->isVisibleViaContractTypeAccess()
|
||||||
|
@ -55,7 +55,7 @@ string ABIFunctions::tupleEncoder(
|
|||||||
functionName += t->identifier() + "_";
|
functionName += t->identifier() + "_";
|
||||||
functionName += options.toFunctionNameSuffix();
|
functionName += options.toFunctionNameSuffix();
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
// Note that the values are in reverse due to the difference in calling semantics.
|
// Note that the values are in reverse due to the difference in calling semantics.
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(headStart <valueParams>) -> tail {
|
function <functionName>(headStart <valueParams>) -> tail {
|
||||||
@ -121,7 +121,7 @@ string ABIFunctions::tupleEncoderPacked(
|
|||||||
functionName += t->identifier() + "_";
|
functionName += t->identifier() + "_";
|
||||||
functionName += options.toFunctionNameSuffix();
|
functionName += options.toFunctionNameSuffix();
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
solAssert(!_givenTypes.empty(), "");
|
solAssert(!_givenTypes.empty(), "");
|
||||||
|
|
||||||
// Note that the values are in reverse due to the difference in calling semantics.
|
// Note that the values are in reverse due to the difference in calling semantics.
|
||||||
@ -173,7 +173,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
if (_fromMemory)
|
if (_fromMemory)
|
||||||
functionName += "_fromMemory";
|
functionName += "_fromMemory";
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
TypePointers decodingTypes;
|
TypePointers decodingTypes;
|
||||||
for (auto const& t: _types)
|
for (auto const& t: _types)
|
||||||
decodingTypes.emplace_back(t->decodingType());
|
decodingTypes.emplace_back(t->decodingType());
|
||||||
@ -240,13 +240,6 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<string, set<string>> ABIFunctions::requestedFunctions()
|
|
||||||
{
|
|
||||||
std::set<string> empty;
|
|
||||||
swap(empty, m_externallyUsedFunctions);
|
|
||||||
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
|
|
||||||
}
|
|
||||||
|
|
||||||
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||||
{
|
{
|
||||||
string suffix;
|
string suffix;
|
||||||
@ -1499,14 +1492,7 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
|
|||||||
|
|
||||||
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
||||||
{
|
{
|
||||||
return m_functionCollector->createFunction(_name, _creator);
|
return m_functionCollector.createFunction(_name, _creator);
|
||||||
}
|
|
||||||
|
|
||||||
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
|
|
||||||
{
|
|
||||||
string name = createFunction(_name, _creator);
|
|
||||||
m_externallyUsedFunctions.insert(name);
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
@ -58,11 +57,11 @@ public:
|
|||||||
explicit ABIFunctions(
|
explicit ABIFunctions(
|
||||||
langutil::EVMVersion _evmVersion,
|
langutil::EVMVersion _evmVersion,
|
||||||
RevertStrings _revertStrings,
|
RevertStrings _revertStrings,
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
MultiUseYulFunctionCollector& _functionCollector
|
||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_functionCollector(std::move(_functionCollector)),
|
m_functionCollector(_functionCollector),
|
||||||
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
|
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -104,12 +103,6 @@ public:
|
|||||||
/// stack slot, it takes exactly that number of values.
|
/// stack slot, it takes exactly that number of values.
|
||||||
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
|
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
|
||||||
|
|
||||||
/// @returns concatenation of all generated functions and a set of the
|
|
||||||
/// externally used functions.
|
|
||||||
/// Clears the internal list, i.e. calling it again will result in an
|
|
||||||
/// empty return value.
|
|
||||||
std::pair<std::string, std::set<std::string>> requestedFunctions();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct EncodingOptions
|
struct EncodingOptions
|
||||||
{
|
{
|
||||||
@ -239,11 +232,6 @@ private:
|
|||||||
/// cases.
|
/// cases.
|
||||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||||
|
|
||||||
/// Helper function that uses @a _creator to create a function and add it to
|
|
||||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
|
||||||
/// cases. Also adds it to the list of externally used functions.
|
|
||||||
std::string createExternallyUsedFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
|
||||||
|
|
||||||
/// @returns the size of the static part of the encoding of the given types.
|
/// @returns the size of the static part of the encoding of the given types.
|
||||||
static size_t headSize(TypePointers const& _targetTypes);
|
static size_t headSize(TypePointers const& _targetTypes);
|
||||||
|
|
||||||
@ -259,8 +247,7 @@ private:
|
|||||||
|
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings const m_revertStrings;
|
RevertStrings const m_revertStrings;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
MultiUseYulFunctionCollector& m_functionCollector;
|
||||||
std::set<std::string> m_externallyUsedFunctions;
|
|
||||||
YulUtilFunctions m_utils;
|
YulUtilFunctions m_utils;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,6 +94,20 @@ void CompilerContext::callLowLevelFunction(
|
|||||||
*this << retTag.tag();
|
*this << retTag.tag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerContext::callYulFunction(
|
||||||
|
string const& _name,
|
||||||
|
unsigned _inArgs,
|
||||||
|
unsigned _outArgs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
m_externallyUsedYulFunctions.insert(_name);
|
||||||
|
auto const retTag = pushNewTag();
|
||||||
|
CompilerUtils(*this).moveIntoStack(_inArgs);
|
||||||
|
appendJumpTo(namedTag(_name));
|
||||||
|
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
|
||||||
|
*this << retTag.tag();
|
||||||
|
}
|
||||||
|
|
||||||
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
|
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
|
||||||
string const& _name,
|
string const& _name,
|
||||||
unsigned _inArgs,
|
unsigned _inArgs,
|
||||||
@ -133,6 +147,13 @@ void CompilerContext::appendMissingLowLevelFunctions()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pair<string, set<string>> CompilerContext::requestedYulFunctions()
|
||||||
|
{
|
||||||
|
set<string> empty;
|
||||||
|
swap(empty, m_externallyUsedYulFunctions);
|
||||||
|
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
|
||||||
|
}
|
||||||
|
|
||||||
void CompilerContext::addVariable(
|
void CompilerContext::addVariable(
|
||||||
VariableDeclaration const& _declaration,
|
VariableDeclaration const& _declaration,
|
||||||
unsigned _offsetToCurrent
|
unsigned _offsetToCurrent
|
||||||
|
@ -65,7 +65,8 @@ public:
|
|||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_runtimeContext(_runtimeContext),
|
m_runtimeContext(_runtimeContext),
|
||||||
m_abiFunctions(m_evmVersion, m_revertStrings)
|
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
|
||||||
|
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
|
||||||
{
|
{
|
||||||
if (m_runtimeContext)
|
if (m_runtimeContext)
|
||||||
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
||||||
@ -131,6 +132,14 @@ public:
|
|||||||
unsigned _outArgs,
|
unsigned _outArgs,
|
||||||
std::function<void(CompilerContext&)> const& _generator
|
std::function<void(CompilerContext&)> const& _generator
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Appends a call to a yul function and registers the function as externally used.
|
||||||
|
void callYulFunction(
|
||||||
|
std::string const& _name,
|
||||||
|
unsigned _inArgs,
|
||||||
|
unsigned _outArgs
|
||||||
|
);
|
||||||
|
|
||||||
/// Returns the tag of the named low-level function and inserts the generator into the
|
/// Returns the tag of the named low-level function and inserts the generator into the
|
||||||
/// list of low-level-functions to be generated, unless it already exists.
|
/// list of low-level-functions to be generated, unless it already exists.
|
||||||
/// Note that the generator should not assume that objects are still alive when it is called,
|
/// Note that the generator should not assume that objects are still alive when it is called,
|
||||||
@ -144,6 +153,12 @@ public:
|
|||||||
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
||||||
void appendMissingLowLevelFunctions();
|
void appendMissingLowLevelFunctions();
|
||||||
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
||||||
|
YulUtilFunctions& utilFunctions() { return m_yulUtilFunctions; }
|
||||||
|
/// @returns concatenation of all generated functions and a set of the
|
||||||
|
/// externally used functions.
|
||||||
|
/// Clears the internal list, i.e. calling it again will result in an
|
||||||
|
/// empty return value.
|
||||||
|
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
|
||||||
|
|
||||||
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
|
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
|
||||||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
||||||
@ -355,8 +370,14 @@ private:
|
|||||||
size_t m_runtimeSub = -1;
|
size_t m_runtimeSub = -1;
|
||||||
/// An index of low-level function labels by name.
|
/// An index of low-level function labels by name.
|
||||||
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
|
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
|
||||||
|
/// Collector for yul functions.
|
||||||
|
MultiUseYulFunctionCollector m_yulFunctionCollector;
|
||||||
|
/// Set of externally used yul functions.
|
||||||
|
std::set<std::string> m_externallyUsedYulFunctions;
|
||||||
/// Container for ABI functions to be generated.
|
/// Container for ABI functions to be generated.
|
||||||
ABIFunctions m_abiFunctions;
|
ABIFunctions m_abiFunctions;
|
||||||
|
/// Container for Yul Util functions to be generated.
|
||||||
|
YulUtilFunctions m_yulUtilFunctions;
|
||||||
/// The queue of low-level functions to generate.
|
/// The queue of low-level functions to generate.
|
||||||
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
||||||
};
|
};
|
||||||
|
@ -121,56 +121,12 @@ void CompilerUtils::returnDataToArray()
|
|||||||
|
|
||||||
void CompilerUtils::accessCalldataTail(Type const& _type)
|
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
m_context << Instruction::SWAP1;
|
||||||
solAssert(_type.isDynamicallyEncoded(), "");
|
m_context.callYulFunction(
|
||||||
|
m_context.utilFunctions().accessCalldataTailFunction(_type),
|
||||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
2,
|
||||||
solAssert(tailSize > 1, "");
|
_type.isDynamicallySized() ? 2 : 1
|
||||||
|
);
|
||||||
// returns the absolute offset of the tail in "base_ref"
|
|
||||||
m_context.appendInlineAssembly(Whiskers(R"({
|
|
||||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
|
||||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
|
|
||||||
base_ref := add(base_ref, rel_offset_of_tail)
|
|
||||||
})")
|
|
||||||
("neededLength", toCompactHexWithPrefix(tailSize))
|
|
||||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
|
|
||||||
.render(), {"base_ref", "ptr_to_tail"});
|
|
||||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
|
||||||
|
|
||||||
if (!_type.isDynamicallySized())
|
|
||||||
{
|
|
||||||
m_context << Instruction::POP;
|
|
||||||
// stack layout: <absolute_offset_of_tail>
|
|
||||||
solAssert(
|
|
||||||
_type.category() == Type::Category::Struct ||
|
|
||||||
_type.category() == Type::Category::Array,
|
|
||||||
"Invalid dynamically encoded base type on tail access."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
|
||||||
solAssert(!!arrayType, "Invalid dynamically sized type.");
|
|
||||||
unsigned int calldataStride = arrayType->calldataStride();
|
|
||||||
solAssert(calldataStride > 0, "");
|
|
||||||
|
|
||||||
// returns the absolute offset of the tail in "base_ref"
|
|
||||||
// and the length of the tail in "length"
|
|
||||||
m_context.appendInlineAssembly(
|
|
||||||
Whiskers(R"({
|
|
||||||
length := calldataload(base_ref)
|
|
||||||
base_ref := add(base_ref, 0x20)
|
|
||||||
if gt(length, 0xffffffffffffffff) { <revertString> }
|
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
|
||||||
})")
|
|
||||||
("calldataStride", toCompactHexWithPrefix(calldataStride))
|
|
||||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
|
|
||||||
.render(),
|
|
||||||
{"base_ref", "length"}
|
|
||||||
);
|
|
||||||
// stack layout: <absolute_offset_of_tail> <length>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned CompilerUtils::loadFromMemory(
|
unsigned CompilerUtils::loadFromMemory(
|
||||||
@ -539,6 +495,10 @@ void CompilerUtils::encodeToMemory(
|
|||||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||||
{
|
{
|
||||||
// copy tail pointer (=mem_end - mem_start) to memory
|
// copy tail pointer (=mem_end - mem_start) to memory
|
||||||
|
solAssert(
|
||||||
|
(2 + dynPointers) <= 16,
|
||||||
|
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
||||||
|
);
|
||||||
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
||||||
m_context << Instruction::SUB;
|
m_context << Instruction::SUB;
|
||||||
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
||||||
@ -595,31 +555,21 @@ void CompilerUtils::abiEncodeV2(
|
|||||||
|
|
||||||
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
||||||
|
|
||||||
auto ret = m_context.pushNewTag();
|
|
||||||
moveIntoStack(sizeOnStack(_givenTypes) + 1);
|
|
||||||
|
|
||||||
string encoderName =
|
string encoderName =
|
||||||
_padToWordBoundaries ?
|
_padToWordBoundaries ?
|
||||||
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
|
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
|
||||||
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
|
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
|
||||||
m_context.appendJumpTo(m_context.namedTag(encoderName));
|
m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
|
||||||
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
|
||||||
m_context << ret.tag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
|
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
|
||||||
{
|
{
|
||||||
// stack: <source_offset> <length> [stack top]
|
// stack: <source_offset> <length> [stack top]
|
||||||
auto ret = m_context.pushNewTag();
|
|
||||||
moveIntoStack(2);
|
|
||||||
// stack: <return tag> <source_offset> <length> [stack top]
|
|
||||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||||
m_context << Instruction::SWAP1;
|
m_context << Instruction::SWAP1;
|
||||||
// stack: <return tag> <end> <start>
|
// stack: <end> <start>
|
||||||
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
|
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
|
||||||
m_context.appendJumpTo(m_context.namedTag(decoderName));
|
m_context.callYulFunction(decoderName, 2, sizeOnStack(_parameterTypes));
|
||||||
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
|
|
||||||
m_context << ret.tag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||||
|
@ -1267,12 +1267,12 @@ void ContractCompiler::appendMissingFunctions()
|
|||||||
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
||||||
}
|
}
|
||||||
m_context.appendMissingLowLevelFunctions();
|
m_context.appendMissingLowLevelFunctions();
|
||||||
auto abiFunctions = m_context.abiFunctions().requestedFunctions();
|
auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
|
||||||
if (!abiFunctions.first.empty())
|
if (!yulFunctions.empty())
|
||||||
m_context.appendInlineAssembly(
|
m_context.appendInlineAssembly(
|
||||||
"{" + move(abiFunctions.first) + "}",
|
"{" + move(yulFunctions) + "}",
|
||||||
{},
|
{},
|
||||||
abiFunctions.second,
|
externallyUsedYulFunctions,
|
||||||
true,
|
true,
|
||||||
m_optimiserSettings
|
m_optimiserSettings
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ using namespace solidity::frontend;
|
|||||||
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||||
{
|
{
|
||||||
string functionName = "combine_external_function_id";
|
string functionName = "combine_external_function_id";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(addr, selector) -> combined {
|
function <functionName>(addr, selector) -> combined {
|
||||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||||
@ -55,7 +55,7 @@ string YulUtilFunctions::combineExternalFunctionIdFunction()
|
|||||||
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||||
{
|
{
|
||||||
string functionName = "split_external_function_id";
|
string functionName = "split_external_function_id";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(combined) -> addr, selector {
|
function <functionName>(combined) -> addr, selector {
|
||||||
combined := <shr64>(combined)
|
combined := <shr64>(combined)
|
||||||
@ -73,7 +73,7 @@ string YulUtilFunctions::splitExternalFunctionIdFunction()
|
|||||||
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||||
{
|
{
|
||||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_fromCalldata)
|
if (_fromCalldata)
|
||||||
{
|
{
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
@ -116,7 +116,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
|||||||
|
|
||||||
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (!_messageType)
|
if (!_messageType)
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(condition) {
|
function <functionName>(condition) {
|
||||||
@ -166,7 +166,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
|||||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||||
{
|
{
|
||||||
string functionName = string("leftAlign_") + _type.identifier();
|
string functionName = string("leftAlign_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> aligned {
|
function <functionName>(value) -> aligned {
|
||||||
<body>
|
<body>
|
||||||
@ -228,7 +228,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
|||||||
solAssert(_numBits < 256, "");
|
solAssert(_numBits < 256, "");
|
||||||
|
|
||||||
string functionName = "shift_left_" + to_string(_numBits);
|
string functionName = "shift_left_" + to_string(_numBits);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> newValue {
|
function <functionName>(value) -> newValue {
|
||||||
@ -251,7 +251,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
|||||||
string YulUtilFunctions::shiftLeftFunctionDynamic()
|
string YulUtilFunctions::shiftLeftFunctionDynamic()
|
||||||
{
|
{
|
||||||
string functionName = "shift_left_dynamic";
|
string functionName = "shift_left_dynamic";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(bits, value) -> newValue {
|
function <functionName>(bits, value) -> newValue {
|
||||||
@ -277,7 +277,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
|
|||||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||||
|
|
||||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> newValue {
|
function <functionName>(value) -> newValue {
|
||||||
@ -303,7 +303,7 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
|
|||||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||||
|
|
||||||
string const functionName = "shift_right_unsigned_dynamic";
|
string const functionName = "shift_right_unsigned_dynamic";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(bits, value) -> newValue {
|
function <functionName>(bits, value) -> newValue {
|
||||||
@ -328,7 +328,7 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
|
|||||||
size_t numBits = _numBytes * 8;
|
size_t numBits = _numBytes * 8;
|
||||||
size_t shiftBits = _shiftBytes * 8;
|
size_t shiftBits = _shiftBytes * 8;
|
||||||
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value, toInsert) -> result {
|
function <functionName>(value, toInsert) -> result {
|
||||||
@ -350,7 +350,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
|||||||
solAssert(_numBytes <= 32, "");
|
solAssert(_numBytes <= 32, "");
|
||||||
size_t numBits = _numBytes * 8;
|
size_t numBits = _numBytes * 8;
|
||||||
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
|
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value, shiftBytes, toInsert) -> result {
|
function <functionName>(value, shiftBytes, toInsert) -> result {
|
||||||
@ -371,7 +371,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
|||||||
string YulUtilFunctions::roundUpFunction()
|
string YulUtilFunctions::roundUpFunction()
|
||||||
{
|
{
|
||||||
string functionName = "round_up_to_mul_of_32";
|
string functionName = "round_up_to_mul_of_32";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> result {
|
function <functionName>(value) -> result {
|
||||||
@ -389,7 +389,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
|||||||
// TODO: Consider to add a special case for unsigned 256-bit integers
|
// TODO: Consider to add a special case for unsigned 256-bit integers
|
||||||
// and use the following instead:
|
// and use the following instead:
|
||||||
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> sum {
|
function <functionName>(x, y) -> sum {
|
||||||
@ -416,7 +416,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_mul_" + _type.identifier();
|
string functionName = "checked_mul_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
// Multiplication by zero could be treated separately and directly return zero.
|
// Multiplication by zero could be treated separately and directly return zero.
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
@ -448,7 +448,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_div_" + _type.identifier();
|
string functionName = "checked_div_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> r {
|
function <functionName>(x, y) -> r {
|
||||||
@ -473,7 +473,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_mod_" + _type.identifier();
|
string functionName = "checked_mod_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> r {
|
function <functionName>(x, y) -> r {
|
||||||
@ -490,7 +490,7 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_sub_" + _type.identifier();
|
string functionName = "checked_sub_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> diff {
|
function <functionName>(x, y) -> diff {
|
||||||
@ -516,7 +516,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_length_" + _type.identifier();
|
string functionName = "array_length_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers w(R"(
|
Whiskers w(R"(
|
||||||
function <functionName>(value) -> length {
|
function <functionName>(value) -> length {
|
||||||
<?dynamic>
|
<?dynamic>
|
||||||
@ -564,7 +564,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
||||||
|
|
||||||
string functionName = "resize_array_" + _type.identifier();
|
string functionName = "resize_array_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, newLen) {
|
function <functionName>(array, newLen) {
|
||||||
if gt(newLen, <maxArrayLength>) {
|
if gt(newLen, <maxArrayLength>) {
|
||||||
@ -604,7 +604,7 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_pop_" + _type.identifier();
|
string functionName = "array_pop_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array) {
|
function <functionName>(array) {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -632,7 +632,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_push_" + _type.identifier();
|
string functionName = "array_push_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, value) {
|
function <functionName>(array, value) {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -659,7 +659,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_push_zero_" + _type.identifier();
|
string functionName = "array_push_zero_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array) -> slot, offset {
|
function <functionName>(array) -> slot, offset {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -684,7 +684,7 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
|
|||||||
|
|
||||||
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(start, end) {
|
function <functionName>(start, end) {
|
||||||
for {} lt(start, end) { start := add(start, <increment>) }
|
for {} lt(start, end) { start := add(start, <increment>) }
|
||||||
@ -715,7 +715,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
|||||||
|
|
||||||
string functionName = "clear_storage_array_" + _type.identifier();
|
string functionName = "clear_storage_array_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot) {
|
function <functionName>(slot) {
|
||||||
<?dynamic>
|
<?dynamic>
|
||||||
@ -745,7 +745,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Type const& baseType = *_type.baseType();
|
Type const& baseType = *_type.baseType();
|
||||||
|
|
||||||
switch (_type.location())
|
switch (_type.location())
|
||||||
@ -798,7 +798,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
|||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||||
string functionName = "array_allocation_size_" + _type.identifier();
|
string functionName = "array_allocation_size_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers w(R"(
|
Whiskers w(R"(
|
||||||
function <functionName>(length) -> size {
|
function <functionName>(length) -> size {
|
||||||
// Make sure we can allocate memory without overflow
|
// Make sure we can allocate memory without overflow
|
||||||
@ -825,7 +825,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_dataslot_" + _type.identifier();
|
string functionName = "array_dataslot_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
// No special processing for calldata arrays, because they are stored as
|
// No special processing for calldata arrays, because they are stored as
|
||||||
// offset of the data area and length on the stack, so the offset already
|
// offset of the data area and length on the stack, so the offset already
|
||||||
// points to the data area.
|
// points to the data area.
|
||||||
@ -858,7 +858,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
||||||
|
|
||||||
string functionName = "storage_array_index_access_" + _type.identifier();
|
string functionName = "storage_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, index) -> slot, offset {
|
function <functionName>(array, index) -> slot, offset {
|
||||||
if iszero(lt(index, <arrayLen>(array))) {
|
if iszero(lt(index, <arrayLen>(array))) {
|
||||||
@ -886,7 +886,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "memory_array_index_access_" + _type.identifier();
|
string functionName = "memory_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(baseRef, index) -> addr {
|
function <functionName>(baseRef, index) -> addr {
|
||||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||||
@ -912,7 +912,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
|
|||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
string functionName = "calldata_array_index_access_" + _type.identifier();
|
string functionName = "calldata_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
||||||
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
|
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
|
||||||
@ -938,17 +938,17 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
|
|||||||
solAssert(_type.isDynamicallyEncoded(), "");
|
solAssert(_type.isDynamicallyEncoded(), "");
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
string functionName = "access_calldata_tail_" + _type.identifier();
|
string functionName = "access_calldata_tail_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
|
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
|
||||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <invalidCalldataTailOffset> }
|
||||||
addr := add(base_ref, rel_offset_of_tail)
|
addr := add(base_ref, rel_offset_of_tail)
|
||||||
<?dynamicallySized>
|
<?dynamicallySized>
|
||||||
length := calldataload(addr)
|
length := calldataload(addr)
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength> }
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
|
||||||
addr := add(addr, 32)
|
addr := add(addr, 32)
|
||||||
|
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail> }
|
||||||
</dynamicallySized>
|
</dynamicallySized>
|
||||||
}
|
}
|
||||||
)")
|
)")
|
||||||
@ -956,6 +956,9 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
|
|||||||
("dynamicallySized", _type.isDynamicallySized())
|
("dynamicallySized", _type.isDynamicallySized())
|
||||||
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
|
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
|
||||||
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
|
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
|
||||||
|
("invalidCalldataTailOffset", revertReasonIfDebug("Invalid calldata tail offset"))
|
||||||
|
("invalidCalldataTailLength", revertReasonIfDebug("Invalid calldata tail length"))
|
||||||
|
("shortCalldataTail", revertReasonIfDebug("Calldata tail too short"))
|
||||||
.render();
|
.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -966,7 +969,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
|||||||
if (_type.dataStoredIn(DataLocation::Storage))
|
if (_type.dataStoredIn(DataLocation::Storage))
|
||||||
solAssert(_type.baseType()->storageBytes() > 16, "");
|
solAssert(_type.baseType()->storageBytes() > 16, "");
|
||||||
string functionName = "array_nextElement_" + _type.identifier();
|
string functionName = "array_nextElement_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(ptr) -> next {
|
function <functionName>(ptr) -> next {
|
||||||
next := add(ptr, <advance>)
|
next := add(ptr, <advance>)
|
||||||
@ -1002,7 +1005,7 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
|
|||||||
solAssert(_keyType.sizeOnStack() <= 1, "");
|
solAssert(_keyType.sizeOnStack() <= 1, "");
|
||||||
|
|
||||||
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
|
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_mappingType.keyType()->isDynamicallySized())
|
if (_mappingType.keyType()->isDynamicallySized())
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot <comma> <key>) -> dataSlot {
|
function <functionName>(slot <comma> <key>) -> dataSlot {
|
||||||
@ -1050,7 +1053,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
|
|||||||
to_string(_offset) +
|
to_string(_offset) +
|
||||||
"_" +
|
"_" +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(_type.sizeOnStack() == 1, "");
|
solAssert(_type.sizeOnStack() == 1, "");
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot) -> value {
|
function <functionName>(slot) -> value {
|
||||||
@ -1071,7 +1074,7 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
|||||||
string(_splitFunctionTypes ? "split_" : "") +
|
string(_splitFunctionTypes ? "split_" : "") +
|
||||||
"_" +
|
"_" +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(_type.sizeOnStack() == 1, "");
|
solAssert(_type.sizeOnStack() == 1, "");
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot, offset) -> value {
|
function <functionName>(slot, offset) -> value {
|
||||||
@ -1101,7 +1104,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
|
|||||||
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
if (_type.isValueType())
|
if (_type.isValueType())
|
||||||
{
|
{
|
||||||
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
||||||
@ -1141,7 +1144,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
|
|||||||
string("write_to_memory_") +
|
string("write_to_memory_") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
||||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
{
|
{
|
||||||
@ -1201,7 +1204,7 @@ string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool
|
|||||||
"extract_from_storage_value_dynamic" +
|
"extract_from_storage_value_dynamic" +
|
||||||
string(_splitFunctionTypes ? "split_" : "") +
|
string(_splitFunctionTypes ? "split_" : "") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot_value, offset) -> value {
|
function <functionName>(slot_value, offset) -> value {
|
||||||
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
|
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
|
||||||
@ -1224,7 +1227,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
|
|||||||
"offset_" +
|
"offset_" +
|
||||||
to_string(_offset) +
|
to_string(_offset) +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot_value) -> value {
|
function <functionName>(slot_value) -> value {
|
||||||
value := <cleanupStorage>(<shr>(slot_value))
|
value := <cleanupStorage>(<shr>(slot_value))
|
||||||
@ -1243,7 +1246,7 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
|
|||||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||||
|
|
||||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> cleaned {
|
function <functionName>(value) -> cleaned {
|
||||||
cleaned := <cleaned>
|
cleaned := <cleaned>
|
||||||
@ -1275,7 +1278,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
|||||||
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
||||||
|
|
||||||
string functionName = "prepare_store_" + _type.identifier();
|
string functionName = "prepare_store_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> ret {
|
function <functionName>(value) -> ret {
|
||||||
ret := <actualPrepare>
|
ret := <actualPrepare>
|
||||||
@ -1293,7 +1296,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
|||||||
string YulUtilFunctions::allocationFunction()
|
string YulUtilFunctions::allocationFunction()
|
||||||
{
|
{
|
||||||
string functionName = "allocateMemory";
|
string functionName = "allocateMemory";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(size) -> memPtr {
|
function <functionName>(size) -> memPtr {
|
||||||
memPtr := mload(<freeMemoryPointer>)
|
memPtr := mload(<freeMemoryPointer>)
|
||||||
@ -1314,7 +1317,7 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(!_type.isByteArray(), "");
|
solUnimplementedAssert(!_type.isByteArray(), "");
|
||||||
|
|
||||||
string functionName = "allocate_memory_array_" + _type.identifier();
|
string functionName = "allocate_memory_array_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(length) -> memPtr {
|
function <functionName>(length) -> memPtr {
|
||||||
memPtr := <alloc>(<allocSize>(length))
|
memPtr := <alloc>(<allocSize>(length))
|
||||||
@ -1333,6 +1336,35 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
|||||||
|
|
||||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||||
{
|
{
|
||||||
|
if (_from.category() == Type::Category::Function)
|
||||||
|
{
|
||||||
|
solAssert(_to.category() == Type::Category::Function, "");
|
||||||
|
FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from);
|
||||||
|
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to);
|
||||||
|
solAssert(
|
||||||
|
fromType.isImplicitlyConvertibleTo(targetType) &&
|
||||||
|
fromType.sizeOnStack() == targetType.sizeOnStack() &&
|
||||||
|
(fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) &&
|
||||||
|
fromType.kind() == targetType.kind(),
|
||||||
|
"Invalid function type conversion requested."
|
||||||
|
);
|
||||||
|
string const functionName =
|
||||||
|
"convert_" +
|
||||||
|
_from.identifier() +
|
||||||
|
"_to_" +
|
||||||
|
_to.identifier();
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
return Whiskers(R"(
|
||||||
|
function <functionName>(addr, functionId) -> outAddr, outFunctionId {
|
||||||
|
outAddr := addr
|
||||||
|
outFunctionId := functionId
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||||
return conversionFunctionSpecial(_from, _to);
|
return conversionFunctionSpecial(_from, _to);
|
||||||
|
|
||||||
@ -1341,7 +1373,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
_from.identifier() +
|
_from.identifier() +
|
||||||
"_to_" +
|
"_to_" +
|
||||||
_to.identifier();
|
_to.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> converted {
|
function <functionName>(value) -> converted {
|
||||||
<body>
|
<body>
|
||||||
@ -1440,22 +1472,34 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
break;
|
break;
|
||||||
case Type::Category::Array:
|
case Type::Category::Array:
|
||||||
{
|
{
|
||||||
bool equal = _from == _to;
|
if (_from == _to)
|
||||||
|
body = "converted := value";
|
||||||
if (!equal)
|
else
|
||||||
{
|
{
|
||||||
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
||||||
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
||||||
|
|
||||||
if (*from.mobileType() == *to.mobileType())
|
switch (to.location())
|
||||||
equal = true;
|
{
|
||||||
|
case DataLocation::Storage:
|
||||||
|
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
|
||||||
|
solAssert(
|
||||||
|
(to.isPointer() || (from.isByteArray() && to.isByteArray())) &&
|
||||||
|
from.location() == DataLocation::Storage,
|
||||||
|
"Invalid conversion to storage type."
|
||||||
|
);
|
||||||
|
body = "converted := value";
|
||||||
|
break;
|
||||||
|
case DataLocation::Memory:
|
||||||
|
// Copy the array to a free position in memory, unless it is already in memory.
|
||||||
|
solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet.");
|
||||||
|
body = "converted := value";
|
||||||
|
break;
|
||||||
|
case DataLocation::CallData:
|
||||||
|
solUnimplemented("Conversion of calldata types not yet implemented.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (equal)
|
|
||||||
body = "converted := value";
|
|
||||||
else
|
|
||||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::Category::Struct:
|
case Type::Category::Struct:
|
||||||
@ -1519,7 +1563,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||||
{
|
{
|
||||||
string functionName = string("cleanup_") + _type.identifier();
|
string functionName = string("cleanup_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> cleaned {
|
function <functionName>(value) -> cleaned {
|
||||||
<body>
|
<body>
|
||||||
@ -1606,7 +1650,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
|
|||||||
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
||||||
{
|
{
|
||||||
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) {
|
function <functionName>(value) {
|
||||||
if iszero(<condition>) { <failure> }
|
if iszero(<condition>) { <failure> }
|
||||||
@ -1667,7 +1711,7 @@ string YulUtilFunctions::packedHashFunction(
|
|||||||
size_t sizeOnStack = 0;
|
size_t sizeOnStack = 0;
|
||||||
for (Type const* t: _givenTypes)
|
for (Type const* t: _givenTypes)
|
||||||
sizeOnStack += t->sizeOnStack();
|
sizeOnStack += t->sizeOnStack();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(<variables>) -> hash {
|
function <functionName>(<variables>) -> hash {
|
||||||
let pos := mload(<freeMemoryPointer>)
|
let pos := mload(<freeMemoryPointer>)
|
||||||
@ -1688,7 +1732,7 @@ string YulUtilFunctions::forwardingRevertFunction()
|
|||||||
{
|
{
|
||||||
bool forward = m_evmVersion.supportsReturndata();
|
bool forward = m_evmVersion.supportsReturndata();
|
||||||
string functionName = "revert_forward_" + to_string(forward);
|
string functionName = "revert_forward_" + to_string(forward);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (forward)
|
if (forward)
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>() {
|
function <functionName>() {
|
||||||
@ -1715,7 +1759,7 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "decrement_" + _type.identifier();
|
string const functionName = "decrement_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
u256 minintval;
|
u256 minintval;
|
||||||
|
|
||||||
// Smallest admissible value to decrement
|
// Smallest admissible value to decrement
|
||||||
@ -1743,7 +1787,7 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "increment_" + _type.identifier();
|
string const functionName = "increment_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
u256 maxintval;
|
u256 maxintval;
|
||||||
|
|
||||||
// Biggest admissible value to increment
|
// Biggest admissible value to increment
|
||||||
@ -1774,7 +1818,7 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(_value) -> ret {
|
function <functionName>(_value) -> ret {
|
||||||
if slt(_value, <minval>) { revert(0,0) }
|
if slt(_value, <minval>) { revert(0,0) }
|
||||||
@ -1794,7 +1838,7 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "zero_value_for_" + _type.identifier();
|
string const functionName = "zero_value_for_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>() -> ret {
|
function <functionName>() -> ret {
|
||||||
<body>
|
<body>
|
||||||
@ -1810,7 +1854,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
|
|||||||
{
|
{
|
||||||
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_type.isValueType())
|
if (_type.isValueType())
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot, offset) {
|
function <functionName>(slot, offset) {
|
||||||
@ -1842,7 +1886,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
|||||||
_from.identifier() +
|
_from.identifier() +
|
||||||
"_to_" +
|
"_to_" +
|
||||||
_to.identifier();
|
_to.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (
|
if (
|
||||||
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
|
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
|
||||||
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
|
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
|
||||||
@ -1950,7 +1994,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
|
|||||||
if (_fromCalldata)
|
if (_fromCalldata)
|
||||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
{
|
{
|
||||||
solAssert(refType->sizeOnStack() == 1, "");
|
solAssert(refType->sizeOnStack() == 1, "");
|
||||||
|
@ -47,11 +47,11 @@ public:
|
|||||||
explicit YulUtilFunctions(
|
explicit YulUtilFunctions(
|
||||||
langutil::EVMVersion _evmVersion,
|
langutil::EVMVersion _evmVersion,
|
||||||
RevertStrings _revertStrings,
|
RevertStrings _revertStrings,
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
MultiUseYulFunctionCollector& _functionCollector
|
||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_functionCollector(std::move(_functionCollector))
|
m_functionCollector(_functionCollector)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// @returns a function that combines the address and selector to a single value
|
/// @returns a function that combines the address and selector to a single value
|
||||||
@ -306,7 +306,7 @@ private:
|
|||||||
|
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings m_revertStrings;
|
RevertStrings m_revertStrings;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
MultiUseYulFunctionCollector& m_functionCollector;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ string IRGenerationContext::newYulVariable()
|
|||||||
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||||
{
|
{
|
||||||
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||||
return m_functions->createFunction(funName, [&]() {
|
return m_functions.createFunction(funName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
||||||
switch fun
|
switch fun
|
||||||
|
@ -56,11 +56,10 @@ public:
|
|||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
m_optimiserSettings(std::move(_optimiserSettings))
|
||||||
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
|
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
|
||||||
|
|
||||||
/// Sets the current inheritance hierarchy from derived to base.
|
/// Sets the current inheritance hierarchy from derived to base.
|
||||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
||||||
@ -108,7 +107,7 @@ private:
|
|||||||
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
|
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
|
||||||
/// Storage offsets of state variables
|
/// Storage offsets of state variables
|
||||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
MultiUseYulFunctionCollector m_functions;
|
||||||
size_t m_varCounter = 0;
|
size_t m_varCounter = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
|||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
for (auto const* fun: contract->definedFunctions())
|
for (auto const* fun: contract->definedFunctions())
|
||||||
generateFunction(*fun);
|
generateFunction(*fun);
|
||||||
t("functions", m_context.functionCollector()->requestedFunctions());
|
t("functions", m_context.functionCollector().requestedFunctions());
|
||||||
|
|
||||||
resetContext(_contract);
|
resetContext(_contract);
|
||||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||||
@ -116,7 +116,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
|||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
for (auto const* fun: contract->definedFunctions())
|
for (auto const* fun: contract->definedFunctions())
|
||||||
generateFunction(*fun);
|
generateFunction(*fun);
|
||||||
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
|
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||||
return t.render();
|
return t.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ string IRGenerator::generate(Block const& _block)
|
|||||||
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
string functionName = m_context.functionName(_function);
|
string functionName = m_context.functionName(_function);
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
Whiskers t(R"(
|
Whiskers t(R"(
|
||||||
function <functionName>(<params>) <returns> {
|
function <functionName>(<params>) <returns> {
|
||||||
<body>
|
<body>
|
||||||
@ -160,7 +160,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
|||||||
solAssert(_varDecl.isStateVariable(), "");
|
solAssert(_varDecl.isStateVariable(), "");
|
||||||
|
|
||||||
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
|
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||||
solAssert(slot_offset.second == 0, "");
|
solAssert(slot_offset.second == 0, "");
|
||||||
FunctionType funType(_varDecl);
|
FunctionType funType(_varDecl);
|
||||||
@ -209,7 +209,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
|||||||
{
|
{
|
||||||
solUnimplementedAssert(type->isValueType(), "");
|
solUnimplementedAssert(type->isValueType(), "");
|
||||||
|
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||||
|
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
@ -383,11 +383,10 @@ string IRGenerator::memoryInit()
|
|||||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
m_context.functionCollector()->requestedFunctions().empty(),
|
m_context.functionCollector().requestedFunctions().empty(),
|
||||||
"Reset context while it still had functions."
|
"Reset context while it still had functions."
|
||||||
);
|
);
|
||||||
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
|
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
|
||||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
|
||||||
|
|
||||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||||
for (auto const& var: ContractType(_contract).stateVariables())
|
for (auto const& var: ContractType(_contract).stateVariables())
|
||||||
|
@ -47,6 +47,7 @@ using namespace std;
|
|||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -800,11 +801,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
case Type::Category::Function:
|
case Type::Category::Function:
|
||||||
if (member == "selector")
|
if (member == "selector")
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(
|
||||||
|
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||||
|
FunctionType::Kind::External, ""
|
||||||
|
);
|
||||||
|
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
|
||||||
}
|
}
|
||||||
else if (member == "address")
|
else if (member == "address")
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(
|
||||||
|
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||||
|
FunctionType::Kind::External, ""
|
||||||
|
);
|
||||||
|
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(
|
solAssert(
|
||||||
@ -1204,7 +1213,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
argumentTypes.emplace_back(&type(*arg));
|
argumentTypes.emplace_back(&type(*arg));
|
||||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||||
}
|
}
|
||||||
string argumentString = ", " + joinHumanReadable(argumentStrings);
|
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
||||||
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||||
|
|
||||||
|
@ -432,12 +432,6 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
|
|||||||
m_context.newValue(*param);
|
m_context.newValue(*param);
|
||||||
m_context.setUnknownValue(*param);
|
m_context.setUnknownValue(*param);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_errorReporter.warning(
|
|
||||||
_funCall.location(),
|
|
||||||
"Assertion checker does not support recursive function calls.",
|
|
||||||
SecondarySourceLocation().append("Starting from function:", funDef->location())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Algorithms.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -75,16 +77,39 @@ void CHC::analyze(SourceUnit const& _source)
|
|||||||
m_context.setAssertionAccumulation(false);
|
m_context.setAssertionAccumulation(false);
|
||||||
m_variableUsage.setFunctionInlining(false);
|
m_variableUsage.setFunctionInlining(false);
|
||||||
|
|
||||||
|
resetSourceAnalysis();
|
||||||
|
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
auto genesisSort = make_shared<smt::FunctionSort>(
|
auto genesisSort = make_shared<smt::FunctionSort>(
|
||||||
vector<smt::SortPointer>(),
|
vector<smt::SortPointer>(),
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
||||||
auto genesis = (*m_genesisPredicate)({});
|
addRule(genesis(), "genesis");
|
||||||
addRule(genesis, genesis.name);
|
|
||||||
|
|
||||||
_source.accept(*this);
|
set<SourceUnit const*, IdCompare> sources;
|
||||||
|
sources.insert(&_source);
|
||||||
|
for (auto const& source: _source.referencedSourceUnits(true))
|
||||||
|
sources.insert(source);
|
||||||
|
for (auto const* source: sources)
|
||||||
|
defineInterfacesAndSummaries(*source);
|
||||||
|
for (auto const* source: sources)
|
||||||
|
source->accept(*this);
|
||||||
|
|
||||||
|
for (auto const& [scope, target]: m_verificationTargets)
|
||||||
|
{
|
||||||
|
auto assertions = transactionAssertions(scope);
|
||||||
|
for (auto const* assertion: assertions)
|
||||||
|
{
|
||||||
|
createErrorBlock();
|
||||||
|
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
|
||||||
|
auto [result, model] = query(error(), assertion->location());
|
||||||
|
// This should be fine but it's a bug in the old compiler
|
||||||
|
(void)model;
|
||||||
|
if (result == smt::CheckResult::UNSATISFIABLE)
|
||||||
|
m_safeAssertions.insert(assertion);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> CHC::unhandledQueries() const
|
vector<string> CHC::unhandledQueries() const
|
||||||
@ -97,26 +122,15 @@ vector<string> CHC::unhandledQueries() const
|
|||||||
|
|
||||||
bool CHC::visit(ContractDefinition const& _contract)
|
bool CHC::visit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_contract))
|
resetContractAnalysis();
|
||||||
return false;
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
initContract(_contract);
|
initContract(_contract);
|
||||||
|
|
||||||
m_stateVariables = _contract.stateVariablesIncludingInherited();
|
m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||||
|
m_stateSorts = stateSorts(_contract);
|
||||||
for (auto const& var: m_stateVariables)
|
|
||||||
// SMT solvers do not support function types as arguments.
|
|
||||||
if (var->type()->category() == Type::Category::Function)
|
|
||||||
m_stateSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
|
||||||
else
|
|
||||||
m_stateSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
|
|
||||||
clearIndices(&_contract);
|
clearIndices(&_contract);
|
||||||
|
|
||||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
|
||||||
m_interfacePredicate = createSymbolicBlock(interfaceSort(), "interface_" + suffix);
|
|
||||||
|
|
||||||
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
@ -125,10 +139,12 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||||
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
|
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
|
||||||
m_constructorPredicate = createSymbolicBlock(constructorSort(), "implicit_constructor_" + to_string(_contract.id()));
|
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
|
||||||
|
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
|
||||||
auto stateExprs = currentStateVariables();
|
auto stateExprs = currentStateVariables();
|
||||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||||
|
|
||||||
SMTEncoder::visit(_contract);
|
SMTEncoder::visit(_contract);
|
||||||
return false;
|
return false;
|
||||||
@ -136,33 +152,33 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
void CHC::endVisit(ContractDefinition const& _contract)
|
void CHC::endVisit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_contract))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto const& var: m_stateVariables)
|
for (auto const& var: m_stateVariables)
|
||||||
{
|
{
|
||||||
solAssert(m_context.knownVariable(*var), "");
|
solAssert(m_context.knownVariable(*var), "");
|
||||||
|
auto const& symbVar = m_context.variable(*var);
|
||||||
|
symbVar->resetIndex();
|
||||||
m_context.setZeroValue(*var);
|
m_context.setZeroValue(*var);
|
||||||
|
symbVar->increaseIndex();
|
||||||
}
|
}
|
||||||
auto genesisPred = (*m_genesisPredicate)({});
|
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
|
||||||
auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables());
|
connectBlocks(genesis(), implicitConstructor);
|
||||||
connectBlocks(genesisPred, implicitConstructor);
|
|
||||||
m_currentBlock = implicitConstructor;
|
m_currentBlock = implicitConstructor;
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
|
||||||
if (auto constructor = _contract.constructor())
|
if (auto constructor = _contract.constructor())
|
||||||
constructor->accept(*this);
|
constructor->accept(*this);
|
||||||
else
|
else
|
||||||
inlineConstructorHierarchy(_contract);
|
inlineConstructorHierarchy(_contract);
|
||||||
|
|
||||||
connectBlocks(m_currentBlock, interface());
|
auto summary = predicate(*m_constructorSummaryPredicate, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables());
|
||||||
|
connectBlocks(m_currentBlock, summary);
|
||||||
|
|
||||||
for (unsigned i = 0; i < m_verificationTargets.size(); ++i)
|
clearIndices(m_currentContract, nullptr);
|
||||||
{
|
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||||
auto const& target = m_verificationTargets.at(i);
|
setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
|
||||||
auto errorAppl = error(i + 1);
|
|
||||||
if (query(errorAppl, target->location()))
|
addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue());
|
||||||
m_safeAssertions.insert(target);
|
connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
|
||||||
}
|
|
||||||
|
|
||||||
SMTEncoder::endVisit(_contract);
|
SMTEncoder::endVisit(_contract);
|
||||||
}
|
}
|
||||||
@ -182,7 +198,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented");
|
solAssert(!m_currentFunction, "Function inlining should not happen in CHC.");
|
||||||
m_currentFunction = &_function;
|
m_currentFunction = &_function;
|
||||||
|
|
||||||
initFunction(_function);
|
initFunction(_function);
|
||||||
@ -193,7 +209,17 @@ bool CHC::visit(FunctionDefinition const& _function)
|
|||||||
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
|
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
|
||||||
auto bodyPred = predicate(*bodyBlock);
|
auto bodyPred = predicate(*bodyBlock);
|
||||||
|
|
||||||
connectBlocks(m_currentBlock, functionPred);
|
if (_function.isConstructor())
|
||||||
|
connectBlocks(m_currentBlock, functionPred);
|
||||||
|
else
|
||||||
|
connectBlocks(genesis(), functionPred);
|
||||||
|
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
for (auto const* var: m_stateVariables)
|
||||||
|
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
|
||||||
|
|
||||||
connectBlocks(functionPred, bodyPred);
|
connectBlocks(functionPred, bodyPred);
|
||||||
|
|
||||||
setCurrentBlock(*bodyBlock);
|
setCurrentBlock(*bodyBlock);
|
||||||
@ -225,18 +251,30 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
|||||||
// This is done in endVisit(ContractDefinition).
|
// This is done in endVisit(ContractDefinition).
|
||||||
if (_function.isConstructor())
|
if (_function.isConstructor())
|
||||||
{
|
{
|
||||||
auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id()));
|
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables()));
|
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
|
||||||
|
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables()));
|
||||||
|
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
clearIndices(m_currentContract, m_currentFunction);
|
||||||
auto stateExprs = currentStateVariables();
|
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||||
setCurrentBlock(*constructorExit, &stateExprs);
|
setCurrentBlock(*constructorExit, &stateExprs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connectBlocks(m_currentBlock, interface());
|
auto assertionError = m_error.currentValue();
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
auto sum = summary(_function);
|
||||||
auto stateExprs = currentStateVariables();
|
connectBlocks(m_currentBlock, sum);
|
||||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
|
||||||
|
auto iface = interface();
|
||||||
|
|
||||||
|
auto stateExprs = initialStateVariables();
|
||||||
|
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||||
|
|
||||||
|
if (_function.isPublic())
|
||||||
|
{
|
||||||
|
addVerificationTarget(&_function, m_currentBlock, sum, assertionError);
|
||||||
|
connectBlocks(m_currentBlock, iface, sum && (assertionError == 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_currentFunction = nullptr;
|
m_currentFunction = nullptr;
|
||||||
}
|
}
|
||||||
@ -421,6 +459,8 @@ void CHC::endVisit(FunctionCall const& _funCall)
|
|||||||
SMTEncoder::endVisit(_funCall);
|
SMTEncoder::endVisit(_funCall);
|
||||||
break;
|
break;
|
||||||
case FunctionType::Kind::Internal:
|
case FunctionType::Kind::Internal:
|
||||||
|
internalFunctionCall(_funCall);
|
||||||
|
break;
|
||||||
case FunctionType::Kind::External:
|
case FunctionType::Kind::External:
|
||||||
case FunctionType::Kind::DelegateCall:
|
case FunctionType::Kind::DelegateCall:
|
||||||
case FunctionType::Kind::BareCall:
|
case FunctionType::Kind::BareCall:
|
||||||
@ -468,12 +508,56 @@ void CHC::visitAssert(FunctionCall const& _funCall)
|
|||||||
solAssert(args.size() == 1, "");
|
solAssert(args.size() == 1, "");
|
||||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||||
|
|
||||||
createErrorBlock();
|
solAssert(m_currentContract, "");
|
||||||
|
solAssert(m_currentFunction, "");
|
||||||
|
if (m_currentFunction->isConstructor())
|
||||||
|
m_functionAssertions[m_currentContract].insert(&_funCall);
|
||||||
|
else
|
||||||
|
m_functionAssertions[m_currentFunction].insert(&_funCall);
|
||||||
|
|
||||||
smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue());
|
auto previousError = m_error.currentValue();
|
||||||
connectBlocks(m_currentBlock, error(), currentPathConditions() && assertNeg);
|
m_error.increaseIndex();
|
||||||
|
|
||||||
m_verificationTargets.push_back(&_funCall);
|
connectBlocks(
|
||||||
|
m_currentBlock,
|
||||||
|
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
|
||||||
|
currentPathConditions() && !m_context.expression(*args.front())->currentValue() && (m_error.currentValue() == _funCall.id())
|
||||||
|
);
|
||||||
|
|
||||||
|
m_context.addAssertion(m_error.currentValue() == previousError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHC::internalFunctionCall(FunctionCall const& _funCall)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
|
||||||
|
auto const* function = functionCallToDefinition(_funCall);
|
||||||
|
if (function)
|
||||||
|
{
|
||||||
|
if (m_currentFunction && !m_currentFunction->isConstructor())
|
||||||
|
m_callGraph[m_currentFunction].insert(function);
|
||||||
|
else
|
||||||
|
m_callGraph[m_currentContract].insert(function);
|
||||||
|
auto const* contract = function->annotation().contract;
|
||||||
|
|
||||||
|
// Libraries can have constants as their "state" variables,
|
||||||
|
// so we need to ensure they were constructed correctly.
|
||||||
|
if (contract->isLibrary())
|
||||||
|
m_context.addAssertion(interface(*contract));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto previousError = m_error.currentValue();
|
||||||
|
|
||||||
|
m_context.addAssertion(predicate(_funCall));
|
||||||
|
|
||||||
|
connectBlocks(
|
||||||
|
m_currentBlock,
|
||||||
|
(m_currentFunction && !m_currentFunction->isConstructor()) ? summary(*m_currentFunction) : summary(*m_currentContract),
|
||||||
|
(m_error.currentValue() > 0)
|
||||||
|
);
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
m_error.increaseIndex();
|
||||||
|
m_context.addAssertion(m_error.currentValue() == previousError);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::unknownFunctionCall(FunctionCall const&)
|
void CHC::unknownFunctionCall(FunctionCall const&)
|
||||||
@ -488,15 +572,23 @@ void CHC::unknownFunctionCall(FunctionCall const&)
|
|||||||
m_unknownFunctionCallSeen = true;
|
m_unknownFunctionCallSeen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::reset()
|
void CHC::resetSourceAnalysis()
|
||||||
|
{
|
||||||
|
m_verificationTargets.clear();
|
||||||
|
m_safeAssertions.clear();
|
||||||
|
m_functionAssertions.clear();
|
||||||
|
m_callGraph.clear();
|
||||||
|
m_summaries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHC::resetContractAnalysis()
|
||||||
{
|
{
|
||||||
m_stateSorts.clear();
|
m_stateSorts.clear();
|
||||||
m_stateVariables.clear();
|
m_stateVariables.clear();
|
||||||
m_verificationTargets.clear();
|
|
||||||
m_safeAssertions.clear();
|
|
||||||
m_unknownFunctionCallSeen = false;
|
m_unknownFunctionCallSeen = false;
|
||||||
m_breakDest = nullptr;
|
m_breakDest = nullptr;
|
||||||
m_continueDest = nullptr;
|
m_continueDest = nullptr;
|
||||||
|
m_error.resetIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::eraseKnowledge()
|
void CHC::eraseKnowledge()
|
||||||
@ -521,25 +613,9 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CHC::shouldVisit(ContractDefinition const& _contract) const
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
_contract.isLibrary() ||
|
|
||||||
_contract.isInterface()
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
||||||
{
|
{
|
||||||
if (
|
return _function.isImplemented();
|
||||||
_function.isPublic() &&
|
|
||||||
_function.isImplemented()
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::setCurrentBlock(
|
void CHC::setCurrentBlock(
|
||||||
@ -547,7 +623,8 @@ void CHC::setCurrentBlock(
|
|||||||
vector<smt::Expression> const* _arguments
|
vector<smt::Expression> const* _arguments
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
m_context.popSolver();
|
if (m_context.solverStackHeigh() > 0)
|
||||||
|
m_context.popSolver();
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
clearIndices(m_currentContract, m_currentFunction);
|
||||||
m_context.pushSolver();
|
m_context.pushSolver();
|
||||||
@ -557,10 +634,42 @@ void CHC::setCurrentBlock(
|
|||||||
m_currentBlock = predicate(_block);
|
m_currentBlock = predicate(_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const* _txRoot)
|
||||||
|
{
|
||||||
|
set<Expression const*, IdCompare> assertions;
|
||||||
|
solidity::util::BreadthFirstSearch<ASTNode const*>{{_txRoot}}.run([&](auto const* function, auto&& _addChild) {
|
||||||
|
assertions.insert(m_functionAssertions[function].begin(), m_functionAssertions[function].end());
|
||||||
|
for (auto const* called: m_callGraph[function])
|
||||||
|
_addChild(called);
|
||||||
|
});
|
||||||
|
return assertions;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<VariableDeclaration const*> stateVars;
|
||||||
|
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
|
||||||
|
for (auto var: contract->stateVariables())
|
||||||
|
stateVars.push_back(var);
|
||||||
|
return stateVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<smt::SortPointer> stateSorts;
|
||||||
|
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||||
|
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
return stateSorts;
|
||||||
|
}
|
||||||
|
|
||||||
smt::SortPointer CHC::constructorSort()
|
smt::SortPointer CHC::constructorSort()
|
||||||
{
|
{
|
||||||
// TODO this will change once we support function calls.
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
return interfaceSort();
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
vector<smt::SortPointer>{intSort} + m_stateSorts,
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::SortPointer CHC::interfaceSort()
|
smt::SortPointer CHC::interfaceSort()
|
||||||
@ -572,20 +681,38 @@ smt::SortPointer CHC::interfaceSort()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
stateSorts(_contract),
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function in the symbolic CFG requires:
|
||||||
|
/// - Index of failed assertion. 0 means no assertion failed.
|
||||||
|
/// - 2 sets of state variables:
|
||||||
|
/// - State variables at the beginning of the current function, immutable
|
||||||
|
/// - Current state variables
|
||||||
|
/// At the beginning of the function these must equal set 1
|
||||||
|
/// - 2 sets of input variables:
|
||||||
|
/// - Input variables at the beginning of the current function, immutable
|
||||||
|
/// - Current input variables
|
||||||
|
/// At the beginning of the function these must equal set 1
|
||||||
|
/// - 1 set of output variables
|
||||||
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
vector<smt::SortPointer> varSorts;
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
for (auto const& var: _function.parameters() + _function.returnParameters())
|
vector<smt::SortPointer> inputSorts;
|
||||||
{
|
for (auto const& var: _function.parameters())
|
||||||
// SMT solvers do not support function types as arguments.
|
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
if (var->type()->category() == Type::Category::Function)
|
vector<smt::SortPointer> outputSorts;
|
||||||
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
for (auto const& var: _function.returnParameters())
|
||||||
else
|
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
varSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
}
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
m_stateSorts + varSorts,
|
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -601,19 +728,31 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
|
|||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
vector<smt::SortPointer> varSorts;
|
vector<smt::SortPointer> varSorts;
|
||||||
for (auto const& var: m_currentFunction->localVariables())
|
for (auto const& var: m_currentFunction->localVariables())
|
||||||
{
|
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
// SMT solvers do not support function types as arguments.
|
|
||||||
if (var->type()->category() == Type::Category::Function)
|
|
||||||
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
|
||||||
else
|
|
||||||
varSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
}
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
fSort->domain + varSorts,
|
fSort->domain + varSorts,
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||||
|
auto sorts = stateSorts(_contract);
|
||||||
|
|
||||||
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
|
vector<smt::SortPointer> inputSorts, outputSorts;
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
for (auto const& var: _function.returnParameters())
|
||||||
|
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
|
||||||
{
|
{
|
||||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||||
@ -625,12 +764,33 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPoin
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||||
|
{
|
||||||
|
for (auto const& node: _source.nodes())
|
||||||
|
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
|
||||||
|
for (auto const* base: contract->annotation().linearizedBaseContracts)
|
||||||
|
{
|
||||||
|
string suffix = base->name() + "_" + to_string(base->id());
|
||||||
|
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
||||||
|
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
||||||
|
if (!m_context.knownVariable(*var))
|
||||||
|
createVariable(*var);
|
||||||
|
for (auto const* function: base->definedFunctions())
|
||||||
|
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
smt::Expression CHC::interface()
|
smt::Expression CHC::interface()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
vector<smt::Expression> paramExprs;
|
||||||
for (auto const& var: m_stateVariables)
|
for (auto const& var: m_stateVariables)
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
return (*m_interfacePredicate)(paramExprs);
|
return (*m_interfaces.at(m_currentContract))(paramExprs);
|
||||||
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::interface(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
return (*m_interfaces.at(&_contract))(stateVariablesAtIndex(0, _contract));
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::Expression CHC::error()
|
smt::Expression CHC::error()
|
||||||
@ -643,6 +803,27 @@ smt::Expression CHC::error(unsigned _idx)
|
|||||||
return m_errorPredicate->functionValueAtIndex(_idx)({});
|
return m_errorPredicate->functionValueAtIndex(_idx)({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::summary(ContractDefinition const&)
|
||||||
|
{
|
||||||
|
return (*m_constructorSummaryPredicate)(
|
||||||
|
vector<smt::Expression>{m_error.currentValue()} +
|
||||||
|
currentStateVariables()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::summary(FunctionDefinition const& _function)
|
||||||
|
{
|
||||||
|
vector<smt::Expression> args{m_error.currentValue()};
|
||||||
|
auto contract = _function.annotation().contract;
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
args.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||||
|
for (auto const& var: _function.returnParameters())
|
||||||
|
args.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
return (*m_summaries.at(m_currentContract).at(&_function))(args);
|
||||||
|
}
|
||||||
|
|
||||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||||
{
|
{
|
||||||
return createSymbolicBlock(sort(_node),
|
return createSymbolicBlock(sort(_node),
|
||||||
@ -653,6 +834,15 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node,
|
|||||||
predicateName(_node));
|
predicateName(_node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
return createSymbolicBlock(summarySort(_function, _contract),
|
||||||
|
"summary_" +
|
||||||
|
uniquePrefix() +
|
||||||
|
"_" +
|
||||||
|
predicateName(&_function, &_contract));
|
||||||
|
}
|
||||||
|
|
||||||
void CHC::createErrorBlock()
|
void CHC::createErrorBlock()
|
||||||
{
|
{
|
||||||
solAssert(m_errorPredicate, "");
|
solAssert(m_errorPredicate, "");
|
||||||
@ -669,6 +859,28 @@ void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to
|
|||||||
addRule(edge, _from.name + "_to_" + _to.name);
|
addRule(edge, _from.name + "_to_" + _to.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::initialStateVariables()
|
||||||
|
{
|
||||||
|
return stateVariablesAtIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
vector<smt::Expression> exprs;
|
||||||
|
for (auto const& var: m_stateVariables)
|
||||||
|
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||||
|
return exprs;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<smt::Expression> exprs;
|
||||||
|
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||||
|
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||||
|
return exprs;
|
||||||
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentStateVariables()
|
vector<smt::Expression> CHC::currentStateVariables()
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
@ -680,11 +892,22 @@ vector<smt::Expression> CHC::currentStateVariables()
|
|||||||
|
|
||||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
vector<smt::Expression> initInputExprs;
|
||||||
if (m_currentFunction)
|
vector<smt::Expression> mutableInputExprs;
|
||||||
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
|
for (auto const& var: m_currentFunction->parameters())
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
{
|
||||||
return currentStateVariables() + paramExprs;
|
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||||
|
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
}
|
||||||
|
vector<smt::Expression> returnExprs;
|
||||||
|
for (auto const& var: m_currentFunction->returnParameters())
|
||||||
|
returnExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
return vector<smt::Expression>{m_error.currentValue()} +
|
||||||
|
initialStateVariables() +
|
||||||
|
initInputExprs +
|
||||||
|
currentStateVariables() +
|
||||||
|
mutableInputExprs +
|
||||||
|
returnExprs;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentBlockVariables()
|
vector<smt::Expression> CHC::currentBlockVariables()
|
||||||
@ -696,7 +919,7 @@ vector<smt::Expression> CHC::currentBlockVariables()
|
|||||||
return currentFunctionVariables() + paramExprs;
|
return currentFunctionVariables() + paramExprs;
|
||||||
}
|
}
|
||||||
|
|
||||||
string CHC::predicateName(ASTNode const* _node)
|
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
|
||||||
{
|
{
|
||||||
string prefix;
|
string prefix;
|
||||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||||
@ -705,7 +928,12 @@ string CHC::predicateName(ASTNode const* _node)
|
|||||||
if (!funDef->name().empty())
|
if (!funDef->name().empty())
|
||||||
prefix += "_" + funDef->name() + "_";
|
prefix += "_" + funDef->name() + "_";
|
||||||
}
|
}
|
||||||
return prefix + to_string(_node->id());
|
else if (m_currentFunction && !m_currentFunction->name().empty())
|
||||||
|
prefix += m_currentFunction->name();
|
||||||
|
|
||||||
|
auto contract = _contract ? _contract : m_currentContract;
|
||||||
|
solAssert(contract, "");
|
||||||
|
return prefix + "_" + to_string(_node->id()) + "_" + to_string(contract->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
|
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
|
||||||
@ -721,12 +949,40 @@ smt::Expression CHC::predicate(
|
|||||||
return _block(_arguments);
|
return _block(_arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::predicate(FunctionCall const& _funCall)
|
||||||
|
{
|
||||||
|
auto const* function = functionCallToDefinition(_funCall);
|
||||||
|
if (!function)
|
||||||
|
return smt::Expression(true);
|
||||||
|
|
||||||
|
m_error.increaseIndex();
|
||||||
|
vector<smt::Expression> args{m_error.currentValue()};
|
||||||
|
auto const* contract = function->annotation().contract;
|
||||||
|
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
|
||||||
|
args += symbolicArguments(_funCall);
|
||||||
|
for (auto const& var: m_stateVariables)
|
||||||
|
m_context.variable(*var)->increaseIndex();
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||||
|
|
||||||
|
auto const& returnParams = function->returnParameters();
|
||||||
|
for (auto param: returnParams)
|
||||||
|
if (m_context.knownVariable(*param))
|
||||||
|
m_context.variable(*param)->increaseIndex();
|
||||||
|
else
|
||||||
|
createVariable(*param);
|
||||||
|
for (auto const& var: function->returnParameters())
|
||||||
|
args.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
|
||||||
|
return (*m_summaries.at(contract).at(function))(args);
|
||||||
|
}
|
||||||
|
|
||||||
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
|
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
|
||||||
{
|
{
|
||||||
m_interface->addRule(_rule, _ruleName);
|
m_interface->addRule(_rule, _ruleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
pair<smt::CheckResult, vector<string>> CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
smt::CheckResult result;
|
smt::CheckResult result;
|
||||||
vector<string> values;
|
vector<string> values;
|
||||||
@ -736,7 +992,7 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
|
|||||||
case smt::CheckResult::SATISFIABLE:
|
case smt::CheckResult::SATISFIABLE:
|
||||||
break;
|
break;
|
||||||
case smt::CheckResult::UNSATISFIABLE:
|
case smt::CheckResult::UNSATISFIABLE:
|
||||||
return true;
|
break;
|
||||||
case smt::CheckResult::UNKNOWN:
|
case smt::CheckResult::UNKNOWN:
|
||||||
break;
|
break;
|
||||||
case smt::CheckResult::CONFLICTING:
|
case smt::CheckResult::CONFLICTING:
|
||||||
@ -746,7 +1002,12 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
|
|||||||
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return {result, values};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHC::addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId)
|
||||||
|
{
|
||||||
|
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{VerificationTarget::Type::Assert, _from, _constraints}, _errorId});
|
||||||
}
|
}
|
||||||
|
|
||||||
string CHC::uniquePrefix()
|
string CHC::uniquePrefix()
|
||||||
|
@ -76,25 +76,42 @@ private:
|
|||||||
void endVisit(Continue const& _node) override;
|
void endVisit(Continue const& _node) override;
|
||||||
|
|
||||||
void visitAssert(FunctionCall const& _funCall);
|
void visitAssert(FunctionCall const& _funCall);
|
||||||
|
void internalFunctionCall(FunctionCall const& _funCall);
|
||||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
|
struct IdCompare
|
||||||
|
{
|
||||||
|
bool operator()(ASTNode const* lhs, ASTNode const* rhs) const
|
||||||
|
{
|
||||||
|
return lhs->id() < rhs->id();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Helpers.
|
/// Helpers.
|
||||||
//@{
|
//@{
|
||||||
void reset();
|
void resetSourceAnalysis();
|
||||||
|
void resetContractAnalysis();
|
||||||
void eraseKnowledge();
|
void eraseKnowledge();
|
||||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
|
||||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||||
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
|
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
|
||||||
|
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
||||||
|
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Sort helpers.
|
/// Sort helpers.
|
||||||
//@{
|
//@{
|
||||||
|
static std::vector<smt::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||||
smt::SortPointer constructorSort();
|
smt::SortPointer constructorSort();
|
||||||
smt::SortPointer interfaceSort();
|
smt::SortPointer interfaceSort();
|
||||||
|
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||||
smt::SortPointer sort(ASTNode const* _block);
|
smt::SortPointer sort(ASTNode const* _block);
|
||||||
|
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||||
|
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
|
||||||
|
/// where the sort changes because the set of state variables might change.
|
||||||
|
static smt::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Predicate helpers.
|
/// Predicate helpers.
|
||||||
@ -102,14 +119,24 @@ private:
|
|||||||
/// @returns a new block of given _sort and _name.
|
/// @returns a new block of given _sort and _name.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
|
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
|
||||||
|
|
||||||
|
/// Creates summary predicates for all functions of all contracts
|
||||||
|
/// in a given _source.
|
||||||
|
void defineInterfacesAndSummaries(SourceUnit const& _source);
|
||||||
|
|
||||||
|
/// Genesis predicate.
|
||||||
|
smt::Expression genesis() { return (*m_genesisPredicate)({}); }
|
||||||
/// Interface predicate over current variables.
|
/// Interface predicate over current variables.
|
||||||
smt::Expression interface();
|
smt::Expression interface();
|
||||||
|
smt::Expression interface(ContractDefinition const& _contract);
|
||||||
/// Error predicate over current variables.
|
/// Error predicate over current variables.
|
||||||
smt::Expression error();
|
smt::Expression error();
|
||||||
smt::Expression error(unsigned _idx);
|
smt::Expression error(unsigned _idx);
|
||||||
|
|
||||||
/// Creates a block for the given _node.
|
/// Creates a block for the given _node.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
||||||
|
/// Creates a call block for the given function _function from contract _contract.
|
||||||
|
/// The contract is needed here because of inheritance.
|
||||||
|
std::unique_ptr<smt::SymbolicFunctionVariable> createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||||
|
|
||||||
/// Creates a new error block to be used by an assertion.
|
/// Creates a new error block to be used by an assertion.
|
||||||
/// Also registers the predicate.
|
/// Also registers the predicate.
|
||||||
@ -117,6 +144,11 @@ private:
|
|||||||
|
|
||||||
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
|
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
|
||||||
|
|
||||||
|
/// @returns the symbolic values of the state variables at the beginning
|
||||||
|
/// of the current transaction.
|
||||||
|
std::vector<smt::Expression> initialStateVariables();
|
||||||
|
std::vector<smt::Expression> stateVariablesAtIndex(int _index);
|
||||||
|
std::vector<smt::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
|
||||||
/// @returns the current symbolic values of the current state variables.
|
/// @returns the current symbolic values of the current state variables.
|
||||||
std::vector<smt::Expression> currentStateVariables();
|
std::vector<smt::Expression> currentStateVariables();
|
||||||
|
|
||||||
@ -128,19 +160,28 @@ private:
|
|||||||
std::vector<smt::Expression> currentBlockVariables();
|
std::vector<smt::Expression> currentBlockVariables();
|
||||||
|
|
||||||
/// @returns the predicate name for a given node.
|
/// @returns the predicate name for a given node.
|
||||||
std::string predicateName(ASTNode const* _node);
|
std::string predicateName(ASTNode const* _node, ContractDefinition const* _contract = nullptr);
|
||||||
/// @returns a predicate application over the current scoped variables.
|
/// @returns a predicate application over the current scoped variables.
|
||||||
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
||||||
/// @returns a predicate application over @param _arguments.
|
/// @returns a predicate application over @param _arguments.
|
||||||
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
|
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
|
||||||
|
/// @returns the summary predicate for the called function.
|
||||||
|
smt::Expression predicate(FunctionCall const& _funCall);
|
||||||
|
/// @returns a predicate that defines a constructor summary.
|
||||||
|
smt::Expression summary(ContractDefinition const& _contract);
|
||||||
|
/// @returns a predicate that defines a function summary.
|
||||||
|
smt::Expression summary(FunctionDefinition const& _function);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Solver related.
|
/// Solver related.
|
||||||
//@{
|
//@{
|
||||||
/// Adds Horn rule to the solver.
|
/// Adds Horn rule to the solver.
|
||||||
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
||||||
/// @returns true if query is unsatisfiable (safe).
|
/// @returns <true, empty> if query is unsatisfiable (safe).
|
||||||
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
/// @returns <false, model> otherwise.
|
||||||
|
std::pair<smt::CheckResult, std::vector<std::string>> query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
||||||
|
|
||||||
|
void addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Misc.
|
/// Misc.
|
||||||
@ -157,15 +198,29 @@ private:
|
|||||||
|
|
||||||
/// Implicit constructor predicate.
|
/// Implicit constructor predicate.
|
||||||
/// Explicit constructors are handled as functions.
|
/// Explicit constructors are handled as functions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorPredicate;
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_implicitConstructorPredicate;
|
||||||
|
|
||||||
|
/// Constructor summary predicate, exists after the constructor
|
||||||
|
/// (implicit or explicit) and before the interface.
|
||||||
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorSummaryPredicate;
|
||||||
|
|
||||||
/// Artificial Interface predicate.
|
/// Artificial Interface predicate.
|
||||||
/// Single entry block for all functions.
|
/// Single entry block for all functions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_interfacePredicate;
|
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
|
||||||
|
|
||||||
/// Artificial Error predicate.
|
/// Artificial Error predicate.
|
||||||
/// Single error block for all assertions.
|
/// Single error block for all assertions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
||||||
|
|
||||||
|
/// Function predicates.
|
||||||
|
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>>> m_summaries;
|
||||||
|
|
||||||
|
smt::SymbolicIntVariable m_error{
|
||||||
|
TypeProvider::uint256(),
|
||||||
|
TypeProvider::uint256(),
|
||||||
|
"error",
|
||||||
|
m_context
|
||||||
|
};
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Variables.
|
/// Variables.
|
||||||
@ -180,7 +235,12 @@ private:
|
|||||||
|
|
||||||
/// Verification targets.
|
/// Verification targets.
|
||||||
//@{
|
//@{
|
||||||
std::vector<Expression const*> m_verificationTargets;
|
struct CHCVerificationTarget: VerificationTarget
|
||||||
|
{
|
||||||
|
smt::Expression errorId;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<ASTNode const*, CHCVerificationTarget, IdCompare> m_verificationTargets;
|
||||||
|
|
||||||
/// Assertions proven safe.
|
/// Assertions proven safe.
|
||||||
std::set<Expression const*> m_safeAssertions;
|
std::set<Expression const*> m_safeAssertions;
|
||||||
@ -190,6 +250,10 @@ private:
|
|||||||
//@{
|
//@{
|
||||||
FunctionDefinition const* m_currentFunction = nullptr;
|
FunctionDefinition const* m_currentFunction = nullptr;
|
||||||
|
|
||||||
|
std::map<ASTNode const*, std::set<ASTNode const*, IdCompare>, IdCompare> m_callGraph;
|
||||||
|
|
||||||
|
std::map<ASTNode const*, std::set<Expression const*>, IdCompare> m_functionAssertions;
|
||||||
|
|
||||||
/// The current block.
|
/// The current block.
|
||||||
smt::Expression m_currentBlock = smt::Expression(true);
|
smt::Expression m_currentBlock = smt::Expression(true);
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ public:
|
|||||||
void pushSolver();
|
void pushSolver();
|
||||||
void popSolver();
|
void popSolver();
|
||||||
void addAssertion(Expression const& _e);
|
void addAssertion(Expression const& _e);
|
||||||
|
unsigned solverStackHeigh() { return m_assertions.size(); } const
|
||||||
SolverInterface* solver()
|
SolverInterface* solver()
|
||||||
{
|
{
|
||||||
solAssert(m_solver, "");
|
solAssert(m_solver, "");
|
||||||
|
@ -29,9 +29,9 @@ ModelChecker::ModelChecker(
|
|||||||
ReadCallback::Callback const& _smtCallback,
|
ReadCallback::Callback const& _smtCallback,
|
||||||
smt::SMTSolverChoice _enabledSolvers
|
smt::SMTSolverChoice _enabledSolvers
|
||||||
):
|
):
|
||||||
|
m_context(),
|
||||||
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
||||||
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers)
|
||||||
m_context()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,14 +62,14 @@ public:
|
|||||||
static smt::SMTSolverChoice availableSolvers();
|
static smt::SMTSolverChoice availableSolvers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Stores the context of the encoding.
|
||||||
|
smt::EncodingContext m_context;
|
||||||
|
|
||||||
/// Bounded Model Checker engine.
|
/// Bounded Model Checker engine.
|
||||||
BMC m_bmc;
|
BMC m_bmc;
|
||||||
|
|
||||||
/// Constrained Horn Clauses engine.
|
/// Constrained Horn Clauses engine.
|
||||||
CHC m_chc;
|
CHC m_chc;
|
||||||
|
|
||||||
/// Stores the context of the encoding.
|
|
||||||
smt::EncodingContext m_context;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -407,19 +407,26 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
|
|||||||
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
|
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
|
||||||
solAssert(symbTuple, "");
|
solAssert(symbTuple, "");
|
||||||
auto const& symbComponents = symbTuple->components();
|
auto const& symbComponents = symbTuple->components();
|
||||||
auto const& tupleComponents = _tuple.components();
|
auto const* tupleComponents = &_tuple.components();
|
||||||
solAssert(symbComponents.size() == _tuple.components().size(), "");
|
while (tupleComponents->size() == 1)
|
||||||
|
{
|
||||||
|
auto innerTuple = dynamic_pointer_cast<TupleExpression>(tupleComponents->front());
|
||||||
|
solAssert(innerTuple, "");
|
||||||
|
tupleComponents = &innerTuple->components();
|
||||||
|
}
|
||||||
|
solAssert(symbComponents.size() == tupleComponents->size(), "");
|
||||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||||
{
|
{
|
||||||
auto sComponent = symbComponents.at(i);
|
auto sComponent = symbComponents.at(i);
|
||||||
auto tComponent = tupleComponents.at(i);
|
auto tComponent = tupleComponents->at(i);
|
||||||
if (sComponent && tComponent)
|
if (sComponent && tComponent)
|
||||||
{
|
{
|
||||||
if (auto varDecl = identifierToVariable(*tComponent))
|
if (auto varDecl = identifierToVariable(*tComponent))
|
||||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
solAssert(m_context.knownExpression(*tComponent), "");
|
if (!m_context.knownExpression(*tComponent))
|
||||||
|
createExpr(*tComponent);
|
||||||
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -666,7 +673,6 @@ void SMTEncoder::visitAssert(FunctionCall const& _funCall)
|
|||||||
auto const& args = _funCall.arguments();
|
auto const& args = _funCall.arguments();
|
||||||
solAssert(args.size() == 1, "");
|
solAssert(args.size() == 1, "");
|
||||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||||
addPathImpliedExpression(expr(*args.front()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SMTEncoder::visitRequire(FunctionCall const& _funCall)
|
void SMTEncoder::visitRequire(FunctionCall const& _funCall)
|
||||||
|
@ -58,11 +58,11 @@ private:
|
|||||||
z3::sort z3Sort(smt::Sort const& _sort);
|
z3::sort z3Sort(smt::Sort const& _sort);
|
||||||
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
|
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
|
||||||
|
|
||||||
std::map<std::string, z3::expr> m_constants;
|
|
||||||
std::map<std::string, z3::func_decl> m_functions;
|
|
||||||
|
|
||||||
z3::context m_context;
|
z3::context m_context;
|
||||||
z3::solver m_solver;
|
z3::solver m_solver;
|
||||||
|
|
||||||
|
std::map<std::string, z3::expr> m_constants;
|
||||||
|
std::map<std::string, z3::func_decl> m_functions;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -899,8 +899,7 @@ h256 const& CompilerStack::Source::swarmHash() const
|
|||||||
string const& CompilerStack::Source::ipfsUrl() const
|
string const& CompilerStack::Source::ipfsUrl() const
|
||||||
{
|
{
|
||||||
if (ipfsUrlCached.empty())
|
if (ipfsUrlCached.empty())
|
||||||
if (scanner->source().size() < 1024 * 256)
|
ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
|
||||||
ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
|
|
||||||
return ipfsUrlCached;
|
return ipfsUrlCached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1373,10 +1372,7 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
|
|||||||
MetadataCBOREncoder encoder;
|
MetadataCBOREncoder encoder;
|
||||||
|
|
||||||
if (m_metadataHash == MetadataHash::IPFS)
|
if (m_metadataHash == MetadataHash::IPFS)
|
||||||
{
|
|
||||||
solAssert(_metadata.length() < 1024 * 256, "Metadata too large.");
|
|
||||||
encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
|
encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
|
||||||
}
|
|
||||||
else if (m_metadataHash == MetadataHash::Bzzr1)
|
else if (m_metadataHash == MetadataHash::Bzzr1)
|
||||||
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
|
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
|
||||||
else
|
else
|
||||||
|
@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
);
|
);
|
||||||
|
|
||||||
bool isIndexed = false;
|
bool isIndexed = false;
|
||||||
bool isDeclaredConst = false;
|
VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
|
||||||
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
||||||
Visibility visibility(Visibility::Default);
|
Visibility visibility(Visibility::Default);
|
||||||
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
||||||
@ -731,7 +731,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
if (_options.allowIndexed && token == Token::Indexed)
|
if (_options.allowIndexed && token == Token::Indexed)
|
||||||
isIndexed = true;
|
isIndexed = true;
|
||||||
else if (token == Token::Constant)
|
else if (token == Token::Constant)
|
||||||
isDeclaredConst = true;
|
constantness = VariableDeclaration::Constantness::Constant;
|
||||||
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
||||||
{
|
{
|
||||||
if (location != VariableDeclaration::Location::Unspecified)
|
if (location != VariableDeclaration::Location::Unspecified)
|
||||||
@ -790,7 +790,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
visibility,
|
visibility,
|
||||||
_options.isStateVariable,
|
_options.isStateVariable,
|
||||||
isIndexed,
|
isIndexed,
|
||||||
isDeclaredConst,
|
constantness,
|
||||||
overrides,
|
overrides,
|
||||||
location
|
location
|
||||||
);
|
);
|
||||||
|
@ -40,6 +40,21 @@ bytes varintEncoding(size_t _n)
|
|||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bytes encodeByteArray(bytes const& _data)
|
||||||
|
{
|
||||||
|
return bytes{0x0a} + varintEncoding(_data.size()) + _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes encodeHash(bytes const& _data)
|
||||||
|
{
|
||||||
|
return bytes{0x12, 0x20} + picosha2::hash256(_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes encodeLinkData(bytes const& _data)
|
||||||
|
{
|
||||||
|
return bytes{0x12} + varintEncoding(_data.size()) + _data;
|
||||||
|
}
|
||||||
|
|
||||||
string base58Encode(bytes const& _data)
|
string base58Encode(bytes const& _data)
|
||||||
{
|
{
|
||||||
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
|
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
|
||||||
@ -53,36 +68,132 @@ string base58Encode(bytes const& _data)
|
|||||||
reverse(output.begin(), output.end());
|
reverse(output.begin(), output.end());
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Chunk
|
||||||
|
{
|
||||||
|
Chunk() = default;
|
||||||
|
Chunk(bytes _hash, size_t _size, size_t _blockSize):
|
||||||
|
hash(std::move(_hash)),
|
||||||
|
size(_size),
|
||||||
|
blockSize(_blockSize)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bytes hash = {};
|
||||||
|
size_t size = 0;
|
||||||
|
size_t blockSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Chunks = vector<Chunk>;
|
||||||
|
|
||||||
|
Chunk combineLinks(Chunks& _links)
|
||||||
|
{
|
||||||
|
bytes data = {};
|
||||||
|
bytes lengths = {};
|
||||||
|
Chunk chunk = {};
|
||||||
|
for (Chunk& link: _links)
|
||||||
|
{
|
||||||
|
chunk.size += link.size;
|
||||||
|
chunk.blockSize += link.blockSize;
|
||||||
|
|
||||||
|
data += encodeLinkData(
|
||||||
|
bytes {0x0a} +
|
||||||
|
varintEncoding(link.hash.size()) +
|
||||||
|
std::move(link.hash) +
|
||||||
|
bytes{0x12, 0x00, 0x18} +
|
||||||
|
varintEncoding(link.blockSize)
|
||||||
|
);
|
||||||
|
|
||||||
|
lengths += bytes{0x20} + varintEncoding(link.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes blockData = data + encodeByteArray(bytes{0x08, 0x02, 0x18} + varintEncoding(chunk.size) + lengths);
|
||||||
|
|
||||||
|
chunk.blockSize += blockData.size();
|
||||||
|
chunk.hash = encodeHash(blockData);
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunks buildNextLevel(Chunks& _currentLevel)
|
||||||
|
{
|
||||||
|
size_t const maxChildNum = 174;
|
||||||
|
|
||||||
|
Chunks nextLevel;
|
||||||
|
Chunks links;
|
||||||
|
|
||||||
|
for (Chunk& chunk: _currentLevel)
|
||||||
|
{
|
||||||
|
links.emplace_back(std::move(chunk.hash), chunk.size, chunk.blockSize);
|
||||||
|
if (links.size() == maxChildNum)
|
||||||
|
{
|
||||||
|
nextLevel.emplace_back(combineLinks(links));
|
||||||
|
links = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!links.empty())
|
||||||
|
nextLevel.emplace_back(combineLinks(links));
|
||||||
|
|
||||||
|
return nextLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a tree starting from the bottom level where nodes are data nodes.
|
||||||
|
/// Data nodes should be calculated and passed as the only level in chunk levels
|
||||||
|
/// Each next level is calculated as following:
|
||||||
|
/// - Pick up to maxChildNum (174) nodes until a whole level is added, group them and pass to the node in the next level
|
||||||
|
/// - Do this until the current level has only one node, return the hash in that node
|
||||||
|
bytes groupChunksBottomUp(Chunks _currentLevel)
|
||||||
|
{
|
||||||
|
// when we reach root it will be the only node in that level
|
||||||
|
while (_currentLevel.size() != 1)
|
||||||
|
_currentLevel = buildNextLevel(_currentLevel);
|
||||||
|
|
||||||
|
// top level's only node stores the hash for file
|
||||||
|
return _currentLevel.front().hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes solidity::util::ipfsHash(string _data)
|
bytes solidity::util::ipfsHash(string _data)
|
||||||
{
|
{
|
||||||
assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented.");
|
size_t const maxChunkSize = 1024 * 256;
|
||||||
|
size_t chunkCount = _data.length() / maxChunkSize + (_data.length() % maxChunkSize > 0 ? 1 : 0);
|
||||||
|
chunkCount = chunkCount == 0 ? 1 : chunkCount;
|
||||||
|
|
||||||
bytes lengthAsVarint = varintEncoding(_data.size());
|
Chunks allChunks;
|
||||||
|
|
||||||
bytes protobufEncodedData;
|
for (unsigned long chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
|
||||||
// Type: File
|
|
||||||
protobufEncodedData += bytes{0x08, 0x02};
|
|
||||||
if (!_data.empty())
|
|
||||||
{
|
{
|
||||||
// Data (length delimited bytes)
|
bytes chunkBytes = asBytes(
|
||||||
protobufEncodedData += bytes{0x12};
|
_data.substr(chunkIndex * maxChunkSize, min(maxChunkSize, _data.length() - chunkIndex * maxChunkSize))
|
||||||
protobufEncodedData += lengthAsVarint;
|
);
|
||||||
protobufEncodedData += asBytes(std::move(_data));
|
|
||||||
|
bytes lengthAsVarint = varintEncoding(chunkBytes.size());
|
||||||
|
|
||||||
|
bytes protobufEncodedData;
|
||||||
|
// Type: File
|
||||||
|
protobufEncodedData += bytes{0x08, 0x02};
|
||||||
|
if (!chunkBytes.empty())
|
||||||
|
{
|
||||||
|
// Data (length delimited bytes)
|
||||||
|
protobufEncodedData += bytes{0x12};
|
||||||
|
protobufEncodedData += lengthAsVarint;
|
||||||
|
protobufEncodedData += chunkBytes;
|
||||||
|
}
|
||||||
|
// filesize: length as varint
|
||||||
|
protobufEncodedData += bytes{0x18} + lengthAsVarint;
|
||||||
|
|
||||||
|
// PBDag:
|
||||||
|
// Data: (length delimited bytes)
|
||||||
|
bytes blockData = encodeByteArray(protobufEncodedData);
|
||||||
|
|
||||||
|
// Multihash: sha2-256, 256 bits
|
||||||
|
allChunks.emplace_back(
|
||||||
|
encodeHash(blockData),
|
||||||
|
chunkBytes.size(),
|
||||||
|
blockData.size()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// filesize: length as varint
|
|
||||||
protobufEncodedData += bytes{0x18} + lengthAsVarint;
|
|
||||||
|
|
||||||
// PBDag:
|
return groupChunksBottomUp(std::move(allChunks));
|
||||||
// Data: (length delimited bytes)
|
|
||||||
size_t protobufLength = protobufEncodedData.size();
|
|
||||||
bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData);
|
|
||||||
// TODO Handle "large" files with multiple blocks
|
|
||||||
|
|
||||||
// Multihash: sha2-256, 256 bits
|
|
||||||
bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData));
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string solidity::util::ipfsHashBase58(string _data)
|
string solidity::util::ipfsHashBase58(string _data)
|
||||||
|
38
libyul/ControlFlowSideEffects.h
Normal file
38
libyul/ControlFlowSideEffects.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace solidity::yul
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effects of code related to control flow.
|
||||||
|
*/
|
||||||
|
struct ControlFlowSideEffects
|
||||||
|
{
|
||||||
|
/// If true, this code terminates the control flow.
|
||||||
|
/// State may or may not be reverted as indicated by the ``reverts`` flag.
|
||||||
|
bool terminates = false;
|
||||||
|
/// If true, this code reverts all state changes in the transaction.
|
||||||
|
/// Whenever this is true, ``terminates`` has to be true as well.
|
||||||
|
bool reverts = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <libyul/YulString.h>
|
#include <libyul/YulString.h>
|
||||||
#include <libyul/SideEffects.h>
|
#include <libyul/SideEffects.h>
|
||||||
|
#include <libyul/ControlFlowSideEffects.h>
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ struct BuiltinFunction
|
|||||||
std::vector<Type> parameters;
|
std::vector<Type> parameters;
|
||||||
std::vector<Type> returns;
|
std::vector<Type> returns;
|
||||||
SideEffects sideEffects;
|
SideEffects sideEffects;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects;
|
||||||
/// If true, this is the msize instruction.
|
/// If true, this is the msize instruction.
|
||||||
bool isMSize = false;
|
bool isMSize = false;
|
||||||
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
||||||
|
@ -52,6 +52,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
|||||||
f.parameters.resize(info.args);
|
f.parameters.resize(info.args);
|
||||||
f.returns.resize(info.ret);
|
f.returns.resize(info.ret);
|
||||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||||
|
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||||
|
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||||
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
f.instruction = _instruction;
|
f.instruction = _instruction;
|
||||||
|
@ -99,6 +99,8 @@ WasmDialect::WasmDialect()
|
|||||||
addFunction("unreachable", {}, {}, false);
|
addFunction("unreachable", {}, {}, false);
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
|
||||||
|
|
||||||
addFunction("datasize", {i64}, {i64}, true, true);
|
addFunction("datasize", {i64}, {i64}, true, true);
|
||||||
addFunction("dataoffset", {i64}, {i64}, true, true);
|
addFunction("dataoffset", {i64}, {i64}, true, true);
|
||||||
@ -147,7 +149,13 @@ void WasmDialect::addEthereumExternals()
|
|||||||
static string const i64{"i64"};
|
static string const i64{"i64"};
|
||||||
static string const i32{"i32"};
|
static string const i32{"i32"};
|
||||||
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
||||||
struct External { string name; vector<string> parameters; vector<string> returns; };
|
struct External
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
vector<string> parameters;
|
||||||
|
vector<string> returns;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects = ControlFlowSideEffects{};
|
||||||
|
};
|
||||||
static vector<External> externals{
|
static vector<External> externals{
|
||||||
{"getAddress", {i32ptr}, {}},
|
{"getAddress", {i32ptr}, {}},
|
||||||
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
||||||
@ -175,11 +183,11 @@ void WasmDialect::addEthereumExternals()
|
|||||||
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||||
{"getBlockNumber", {}, {i64}},
|
{"getBlockNumber", {}, {i64}},
|
||||||
{"getTxOrigin", {i32ptr}, {}},
|
{"getTxOrigin", {i32ptr}, {}},
|
||||||
{"finish", {i32ptr, i32}, {}},
|
{"finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
|
||||||
{"revert", {i32ptr, i32}, {}},
|
{"revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
|
||||||
{"getReturnDataSize", {}, {i32}},
|
{"getReturnDataSize", {}, {i32}},
|
||||||
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||||
{"selfDestruct", {i32ptr}, {}},
|
{"selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
|
||||||
{"getBlockTimestamp", {}, {i64}}
|
{"getBlockTimestamp", {}, {i64}}
|
||||||
};
|
};
|
||||||
for (External const& ext: externals)
|
for (External const& ext: externals)
|
||||||
@ -193,6 +201,7 @@ void WasmDialect::addEthereumExternals()
|
|||||||
f.returns.emplace_back(YulString(p));
|
f.returns.emplace_back(YulString(p));
|
||||||
// TODO some of them are side effect free.
|
// TODO some of them are side effect free.
|
||||||
f.sideEffects = SideEffects::worst();
|
f.sideEffects = SideEffects::worst();
|
||||||
|
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||||
f.isMSize = false;
|
f.isMSize = false;
|
||||||
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
|
@ -10,6 +10,7 @@ SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
|
|||||||
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
|
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
|
||||||
|
|
||||||
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
|
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
|
||||||
|
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
|
||||||
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
|
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
|
||||||
|
|
||||||
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
|
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
|
||||||
@ -75,7 +76,7 @@ echo "Looking at $NSOURCES .sol files..."
|
|||||||
WORKINGDIR=$PWD
|
WORKINGDIR=$PWD
|
||||||
|
|
||||||
# for solfile in $(find $DEV_DIR -name *.sol)
|
# for solfile in $(find $DEV_DIR -name *.sol)
|
||||||
for solfile in $(find $SYNTAXTESTS_DIR -name *.sol)
|
for solfile in $(find $SYNTAXTESTS_DIR $ASTJSONTESTS_DIR -name *.sol)
|
||||||
do
|
do
|
||||||
echo -n "."
|
echo -n "."
|
||||||
# create a temporary sub-directory
|
# create a temporary sub-directory
|
||||||
|
@ -127,6 +127,7 @@ static string const g_strInterface = "interface";
|
|||||||
static string const g_strYul = "yul";
|
static string const g_strYul = "yul";
|
||||||
static string const g_strYulDialect = "yul-dialect";
|
static string const g_strYulDialect = "yul-dialect";
|
||||||
static string const g_strIR = "ir";
|
static string const g_strIR = "ir";
|
||||||
|
static string const g_strIROptimized = "ir-optimized";
|
||||||
static string const g_strIPFS = "ipfs";
|
static string const g_strIPFS = "ipfs";
|
||||||
static string const g_strLicense = "license";
|
static string const g_strLicense = "license";
|
||||||
static string const g_strLibraries = "libraries";
|
static string const g_strLibraries = "libraries";
|
||||||
@ -190,6 +191,7 @@ static string const g_argImportAst = g_strImportAst;
|
|||||||
static string const g_argInputFile = g_strInputFile;
|
static string const g_argInputFile = g_strInputFile;
|
||||||
static string const g_argYul = g_strYul;
|
static string const g_argYul = g_strYul;
|
||||||
static string const g_argIR = g_strIR;
|
static string const g_argIR = g_strIR;
|
||||||
|
static string const g_argIROptimized = g_strIROptimized;
|
||||||
static string const g_argEwasm = g_strEwasm;
|
static string const g_argEwasm = g_strEwasm;
|
||||||
static string const g_argLibraries = g_strLibraries;
|
static string const g_argLibraries = g_strLibraries;
|
||||||
static string const g_argLink = g_strLink;
|
static string const g_argLink = g_strLink;
|
||||||
@ -336,36 +338,50 @@ void CommandLineInterface::handleOpcode(string const& _contract)
|
|||||||
|
|
||||||
void CommandLineInterface::handleIR(string const& _contractName)
|
void CommandLineInterface::handleIR(string const& _contractName)
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argIR))
|
if (!m_args.count(g_argIR))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argOutputDir))
|
sout() << "IR:" << endl;
|
||||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
|
sout() << m_compiler->yulIR(_contractName) << endl;
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
sout() << "IR:" << endl;
|
|
||||||
sout() << m_compiler->yulIR(_contractName) << endl;
|
void CommandLineInterface::handleIROptimized(string const& _contractName)
|
||||||
}
|
{
|
||||||
|
if (!m_args.count(g_argIROptimized))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sout() << "Optimized IR:" << endl;
|
||||||
|
sout() << m_compiler->yulIROptimized(_contractName) << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandLineInterface::handleEwasm(string const& _contractName)
|
void CommandLineInterface::handleEwasm(string const& _contractName)
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argEwasm))
|
if (!m_args.count(g_argEwasm))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argOutputDir))
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
|
||||||
{
|
createFile(
|
||||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
|
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
||||||
createFile(
|
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
||||||
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
);
|
||||||
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
}
|
||||||
);
|
else
|
||||||
}
|
{
|
||||||
else
|
sout() << "Ewasm text:" << endl;
|
||||||
{
|
sout() << m_compiler->ewasm(_contractName) << endl;
|
||||||
sout() << "Ewasm text:" << endl;
|
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
|
||||||
sout() << m_compiler->ewasm(_contractName) << endl;
|
|
||||||
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,6 +828,7 @@ Allowed options)",
|
|||||||
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
|
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
|
||||||
(g_argAbi.c_str(), "ABI specification of the contracts.")
|
(g_argAbi.c_str(), "ABI specification of the contracts.")
|
||||||
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
||||||
|
(g_argIROptimized.c_str(), "Optimized intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
||||||
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
|
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
|
||||||
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
|
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
|
||||||
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
|
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
|
||||||
@ -1123,7 +1140,7 @@ bool CommandLineInterface::processInput()
|
|||||||
m_compiler->setRevertStringBehaviour(m_revertStrings);
|
m_compiler->setRevertStringBehaviour(m_revertStrings);
|
||||||
// TODO: Perhaps we should not compile unless requested
|
// TODO: Perhaps we should not compile unless requested
|
||||||
|
|
||||||
m_compiler->enableIRGeneration(m_args.count(g_argIR));
|
m_compiler->enableIRGeneration(m_args.count(g_argIR) || m_args.count(g_argIROptimized));
|
||||||
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
|
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
|
||||||
|
|
||||||
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||||
@ -1631,6 +1648,7 @@ void CommandLineInterface::outputCompilationResults()
|
|||||||
|
|
||||||
handleBytecode(contract);
|
handleBytecode(contract);
|
||||||
handleIR(contract);
|
handleIR(contract);
|
||||||
|
handleIROptimized(contract);
|
||||||
handleEwasm(contract);
|
handleEwasm(contract);
|
||||||
handleSignatureHashes(contract);
|
handleSignatureHashes(contract);
|
||||||
handleMetadata(contract);
|
handleMetadata(contract);
|
||||||
|
@ -65,6 +65,7 @@ private:
|
|||||||
void handleBinary(std::string const& _contract);
|
void handleBinary(std::string const& _contract);
|
||||||
void handleOpcode(std::string const& _contract);
|
void handleOpcode(std::string const& _contract);
|
||||||
void handleIR(std::string const& _contract);
|
void handleIR(std::string const& _contract);
|
||||||
|
void handleIROptimized(std::string const& _contract);
|
||||||
void handleEwasm(std::string const& _contract);
|
void handleEwasm(std::string const& _contract);
|
||||||
void handleBytecode(std::string const& _contract);
|
void handleBytecode(std::string const& _contract);
|
||||||
void handleSignatureHashes(std::string const& _contract);
|
void handleSignatureHashes(std::string const& _contract);
|
||||||
|
@ -53,12 +53,16 @@ ExecutionFramework::ExecutionFramework(langutil::EVMVersion _evmVersion):
|
|||||||
m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
|
m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
|
||||||
else if (solidity::test::CommonOptions::get().optimize)
|
else if (solidity::test::CommonOptions::get().optimize)
|
||||||
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
|
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
|
||||||
m_evmHost->reset();
|
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExecutionFramework::reset()
|
||||||
|
{
|
||||||
|
m_evmHost->reset();
|
||||||
for (size_t i = 0; i < 10; i++)
|
for (size_t i = 0; i < 10; i++)
|
||||||
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
|
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
|
||||||
EVMHost::convertToEVMC(u256(1) << 100);
|
EVMHost::convertToEVMC(u256(1) << 100);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(
|
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(
|
||||||
|
@ -251,6 +251,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void reset();
|
||||||
|
|
||||||
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
|
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
|
||||||
void sendEther(Address const& _to, u256 const& _value);
|
void sendEther(Address const& _to, u256 const& _value);
|
||||||
size_t currentTimestamp();
|
size_t currentTimestamp();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <test/Common.h>
|
||||||
#include <test/TestCase.h>
|
#include <test/TestCase.h>
|
||||||
|
|
||||||
#include <libsolutil/StringUtils.h>
|
#include <libsolutil/StringUtils.h>
|
||||||
@ -52,14 +53,18 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
|
|||||||
!boost::starts_with(_filename.string(), ".");
|
!boost::starts_with(_filename.string(), ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestCase::validateSettings(langutil::EVMVersion)
|
void TestCase::validateSettings()
|
||||||
{
|
{
|
||||||
if (!m_settings.empty())
|
if (!m_settings.empty())
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"Unknown setting(s): " +
|
"Unknown setting(s): " +
|
||||||
util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
|
util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
|
||||||
);
|
);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
bool TestCase::shouldRun()
|
||||||
|
{
|
||||||
|
return m_shouldRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
|
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
|
||||||
@ -157,20 +162,19 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
|
|||||||
++_it;
|
++_it;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion)
|
void EVMVersionRestrictedTestCase::validateSettings()
|
||||||
{
|
{
|
||||||
if (!m_settings.count("EVMVersion"))
|
if (!m_settings.count("EVMVersion"))
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
string versionString = m_settings["EVMVersion"];
|
string versionString = m_settings["EVMVersion"];
|
||||||
m_validatedSettings["EVMVersion"] = versionString;
|
m_validatedSettings["EVMVersion"] = versionString;
|
||||||
m_settings.erase("EVMVersion");
|
m_settings.erase("EVMVersion");
|
||||||
|
|
||||||
if (!TestCase::validateSettings(_evmVersion))
|
TestCase::validateSettings();
|
||||||
return false;
|
|
||||||
|
|
||||||
if (versionString.empty())
|
if (versionString.empty())
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
string comparator;
|
string comparator;
|
||||||
size_t versionBegin = 0;
|
size_t versionBegin = 0;
|
||||||
@ -188,18 +192,23 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer
|
|||||||
if (!version)
|
if (!version)
|
||||||
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
|
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
|
||||||
|
|
||||||
|
langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
|
||||||
|
bool comparisonResult;
|
||||||
if (comparator == ">")
|
if (comparator == ">")
|
||||||
return _evmVersion > version;
|
comparisonResult = evmVersion > version;
|
||||||
else if (comparator == ">=")
|
else if (comparator == ">=")
|
||||||
return _evmVersion >= version;
|
comparisonResult = evmVersion >= version;
|
||||||
else if (comparator == "<")
|
else if (comparator == "<")
|
||||||
return _evmVersion < version;
|
comparisonResult = evmVersion < version;
|
||||||
else if (comparator == "<=")
|
else if (comparator == "<=")
|
||||||
return _evmVersion <= version;
|
comparisonResult = evmVersion <= version;
|
||||||
else if (comparator == "=")
|
else if (comparator == "=")
|
||||||
return _evmVersion == version;
|
comparisonResult = evmVersion == version;
|
||||||
else if (comparator == "!")
|
else if (comparator == "!")
|
||||||
return !(_evmVersion == version);
|
comparisonResult = !(evmVersion == version);
|
||||||
else
|
else
|
||||||
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
|
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
|
||||||
|
|
||||||
|
if (!comparisonResult)
|
||||||
|
m_shouldRun = false;
|
||||||
}
|
}
|
||||||
|
@ -70,10 +70,12 @@ public:
|
|||||||
|
|
||||||
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
|
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
|
||||||
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
|
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
|
||||||
|
virtual void validateSettings();
|
||||||
|
|
||||||
/// Returns true, if the test case is supported in the current environment and false
|
/// Returns true, if the test case is supported in the current environment and false
|
||||||
/// otherwise which causes this test to be skipped.
|
/// otherwise which causes this test to be skipped.
|
||||||
/// This might check e.g. for restrictions on the EVM version.
|
/// This might check e.g. for restrictions on the EVM version.
|
||||||
virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/);
|
bool shouldRun();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
|
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
|
||||||
@ -102,13 +104,14 @@ protected:
|
|||||||
std::map<std::string, std::string> m_settings;
|
std::map<std::string, std::string> m_settings;
|
||||||
/// Updated settings after validation.
|
/// Updated settings after validation.
|
||||||
std::map<std::string, std::string> m_validatedSettings;
|
std::map<std::string, std::string> m_validatedSettings;
|
||||||
|
|
||||||
|
bool m_shouldRun = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EVMVersionRestrictedTestCase: public TestCase
|
class EVMVersionRestrictedTestCase: public TestCase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise.
|
void validateSettings() override;
|
||||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,8 @@ int registerTests(
|
|||||||
{
|
{
|
||||||
stringstream errorStream;
|
stringstream errorStream;
|
||||||
auto testCase = _testCaseCreator(config);
|
auto testCase = _testCaseCreator(config);
|
||||||
if (testCase->validateSettings(solidity::test::CommonOptions::get().evmVersion()))
|
testCase->validateSettings();
|
||||||
|
if (testCase->shouldRun())
|
||||||
switch (testCase->run(errorStream))
|
switch (testCase->run(errorStream))
|
||||||
{
|
{
|
||||||
case TestCase::TestResult::Success:
|
case TestCase::TestResult::Success:
|
||||||
|
@ -77,6 +77,11 @@ function compileFull()
|
|||||||
expect_output=1
|
expect_output=1
|
||||||
shift;
|
shift;
|
||||||
fi
|
fi
|
||||||
|
if [[ $1 = '-o' ]]
|
||||||
|
then
|
||||||
|
expect_output=2
|
||||||
|
shift;
|
||||||
|
fi
|
||||||
|
|
||||||
local files="$*"
|
local files="$*"
|
||||||
local output
|
local output
|
||||||
@ -93,7 +98,7 @@ function compileFull()
|
|||||||
if [[ \
|
if [[ \
|
||||||
"$exit_code" -ne "$expected_exit_code" || \
|
"$exit_code" -ne "$expected_exit_code" || \
|
||||||
( $expect_output -eq 0 && -n "$errors" ) || \
|
( $expect_output -eq 0 && -n "$errors" ) || \
|
||||||
( $expect_output -ne 0 && -z "$errors" ) \
|
( $expect_output -eq 1 && -z "$errors" ) \
|
||||||
]]
|
]]
|
||||||
then
|
then
|
||||||
printError "Unexpected compilation result:"
|
printError "Unexpected compilation result:"
|
||||||
@ -350,6 +355,10 @@ SOLTMPDIR=$(mktemp -d)
|
|||||||
then
|
then
|
||||||
opts="$opts -w"
|
opts="$opts -w"
|
||||||
fi
|
fi
|
||||||
|
if grep "This may report a warning" "$f" >/dev/null
|
||||||
|
then
|
||||||
|
opts="$opts -o"
|
||||||
|
fi
|
||||||
compileFull $opts "$SOLTMPDIR/$f"
|
compileFull $opts "$SOLTMPDIR/$f"
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
|
95
test/libsolidity/ASTJSON/assembly/empty_block.json
Normal file
95
test/libsolidity/ASTJSON/assembly/empty_block.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"absolutePath": "a",
|
||||||
|
"exportedSymbols":
|
||||||
|
{
|
||||||
|
"C":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"nodeType": "SourceUnit",
|
||||||
|
"nodes":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts": [],
|
||||||
|
"contractDependencies": [],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"documentation": null,
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"id": 6,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"name": "C",
|
||||||
|
"nodeType": "ContractDefinition",
|
||||||
|
"nodes":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"body":
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"nodeType": "Block",
|
||||||
|
"src": "42:31:1",
|
||||||
|
"statements":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"AST":
|
||||||
|
{
|
||||||
|
"nodeType": "YulBlock",
|
||||||
|
"src": "61:6:1",
|
||||||
|
"statements":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"nodeType": "YulBlock",
|
||||||
|
"src": "63:2:1",
|
||||||
|
"statements": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"evmVersion": %EVMVERSION%,
|
||||||
|
"externalReferences": [],
|
||||||
|
"id": 3,
|
||||||
|
"nodeType": "InlineAssembly",
|
||||||
|
"src": "52:15:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"documentation": null,
|
||||||
|
"functionSelector": "e2179b8e",
|
||||||
|
"id": 5,
|
||||||
|
"implemented": true,
|
||||||
|
"kind": "function",
|
||||||
|
"modifiers": [],
|
||||||
|
"name": "g",
|
||||||
|
"nodeType": "FunctionDefinition",
|
||||||
|
"overrides": null,
|
||||||
|
"parameters":
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"nodeType": "ParameterList",
|
||||||
|
"parameters": [],
|
||||||
|
"src": "27:2:1"
|
||||||
|
},
|
||||||
|
"returnParameters":
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"nodeType": "ParameterList",
|
||||||
|
"parameters": [],
|
||||||
|
"src": "42:0:1"
|
||||||
|
},
|
||||||
|
"scope": 6,
|
||||||
|
"src": "17:56:1",
|
||||||
|
"stateMutability": "view",
|
||||||
|
"virtual": false,
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scope": 7,
|
||||||
|
"src": "0:75:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"src": "0:76:1"
|
||||||
|
}
|
7
test/libsolidity/ASTJSON/assembly/empty_block.sol
Normal file
7
test/libsolidity/ASTJSON/assembly/empty_block.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contract C {
|
||||||
|
function g() view public {
|
||||||
|
assembly { {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
123
test/libsolidity/ASTJSON/assembly/empty_block_legacy.json
Normal file
123
test/libsolidity/ASTJSON/assembly/empty_block_legacy.json
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"absolutePath": "a",
|
||||||
|
"exportedSymbols":
|
||||||
|
{
|
||||||
|
"C":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"contractDependencies":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"documentation": null,
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"name": "C",
|
||||||
|
"scope": 7
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"documentation": null,
|
||||||
|
"functionSelector": "e2179b8e",
|
||||||
|
"implemented": true,
|
||||||
|
"isConstructor": false,
|
||||||
|
"kind": "function",
|
||||||
|
"modifiers":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"name": "g",
|
||||||
|
"overrides": null,
|
||||||
|
"scope": 6,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"virtual": false,
|
||||||
|
"visibility": "public"
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"parameters":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 1,
|
||||||
|
"name": "ParameterList",
|
||||||
|
"src": "27:2:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"parameters":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 2,
|
||||||
|
"name": "ParameterList",
|
||||||
|
"src": "42:0:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"evmVersion": %EVMVERSION%,
|
||||||
|
"externalReferences":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"operations": "{ { } }"
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 3,
|
||||||
|
"name": "InlineAssembly",
|
||||||
|
"src": "52:15:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 4,
|
||||||
|
"name": "Block",
|
||||||
|
"src": "42:31:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 5,
|
||||||
|
"name": "FunctionDefinition",
|
||||||
|
"src": "17:56:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 6,
|
||||||
|
"name": "ContractDefinition",
|
||||||
|
"src": "0:75:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 7,
|
||||||
|
"name": "SourceUnit",
|
||||||
|
"src": "0:76:1"
|
||||||
|
}
|
116
test/libsolidity/ASTJSON/assembly/switch_default.json
Normal file
116
test/libsolidity/ASTJSON/assembly/switch_default.json
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"absolutePath": "a",
|
||||||
|
"exportedSymbols":
|
||||||
|
{
|
||||||
|
"C":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"nodeType": "SourceUnit",
|
||||||
|
"nodes":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts": [],
|
||||||
|
"contractDependencies": [],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"documentation": null,
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"id": 6,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"name": "C",
|
||||||
|
"nodeType": "ContractDefinition",
|
||||||
|
"nodes":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"body":
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"nodeType": "Block",
|
||||||
|
"src": "42:48:1",
|
||||||
|
"statements":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"AST":
|
||||||
|
{
|
||||||
|
"nodeType": "YulBlock",
|
||||||
|
"src": "61:23:1",
|
||||||
|
"statements":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"cases":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"body":
|
||||||
|
{
|
||||||
|
"nodeType": "YulBlock",
|
||||||
|
"src": "80:2:1",
|
||||||
|
"statements": []
|
||||||
|
},
|
||||||
|
"nodeType": "YulCase",
|
||||||
|
"src": "72:10:1",
|
||||||
|
"value": "default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expression":
|
||||||
|
{
|
||||||
|
"kind": "number",
|
||||||
|
"nodeType": "YulLiteral",
|
||||||
|
"src": "70:1:1",
|
||||||
|
"type": "",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
"nodeType": "YulSwitch",
|
||||||
|
"src": "63:19:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"evmVersion": %EVMVERSION%,
|
||||||
|
"externalReferences": [],
|
||||||
|
"id": 3,
|
||||||
|
"nodeType": "InlineAssembly",
|
||||||
|
"src": "52:32:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"documentation": null,
|
||||||
|
"functionSelector": "e2179b8e",
|
||||||
|
"id": 5,
|
||||||
|
"implemented": true,
|
||||||
|
"kind": "function",
|
||||||
|
"modifiers": [],
|
||||||
|
"name": "g",
|
||||||
|
"nodeType": "FunctionDefinition",
|
||||||
|
"overrides": null,
|
||||||
|
"parameters":
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"nodeType": "ParameterList",
|
||||||
|
"parameters": [],
|
||||||
|
"src": "27:2:1"
|
||||||
|
},
|
||||||
|
"returnParameters":
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"nodeType": "ParameterList",
|
||||||
|
"parameters": [],
|
||||||
|
"src": "42:0:1"
|
||||||
|
},
|
||||||
|
"scope": 6,
|
||||||
|
"src": "17:73:1",
|
||||||
|
"stateMutability": "view",
|
||||||
|
"virtual": false,
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scope": 7,
|
||||||
|
"src": "0:92:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"src": "0:93:1"
|
||||||
|
}
|
7
test/libsolidity/ASTJSON/assembly/switch_default.sol
Normal file
7
test/libsolidity/ASTJSON/assembly/switch_default.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contract C {
|
||||||
|
function g() view public {
|
||||||
|
assembly { switch 0 default {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
123
test/libsolidity/ASTJSON/assembly/switch_default_legacy.json
Normal file
123
test/libsolidity/ASTJSON/assembly/switch_default_legacy.json
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"absolutePath": "a",
|
||||||
|
"exportedSymbols":
|
||||||
|
{
|
||||||
|
"C":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"contractDependencies":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"documentation": null,
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"name": "C",
|
||||||
|
"scope": 7
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"documentation": null,
|
||||||
|
"functionSelector": "e2179b8e",
|
||||||
|
"implemented": true,
|
||||||
|
"isConstructor": false,
|
||||||
|
"kind": "function",
|
||||||
|
"modifiers":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"name": "g",
|
||||||
|
"overrides": null,
|
||||||
|
"scope": 6,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"virtual": false,
|
||||||
|
"visibility": "public"
|
||||||
|
},
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"parameters":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 1,
|
||||||
|
"name": "ParameterList",
|
||||||
|
"src": "27:2:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"parameters":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 2,
|
||||||
|
"name": "ParameterList",
|
||||||
|
"src": "42:0:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attributes":
|
||||||
|
{
|
||||||
|
"evmVersion": %EVMVERSION%,
|
||||||
|
"externalReferences":
|
||||||
|
[
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"operations": "{\n switch 0\n default { }\n}"
|
||||||
|
},
|
||||||
|
"children": [],
|
||||||
|
"id": 3,
|
||||||
|
"name": "InlineAssembly",
|
||||||
|
"src": "52:32:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 4,
|
||||||
|
"name": "Block",
|
||||||
|
"src": "42:48:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 5,
|
||||||
|
"name": "FunctionDefinition",
|
||||||
|
"src": "17:73:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 6,
|
||||||
|
"name": "ContractDefinition",
|
||||||
|
"src": "0:92:1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 7,
|
||||||
|
"name": "SourceUnit",
|
||||||
|
"src": "0:93:1"
|
||||||
|
}
|
@ -17,14 +17,18 @@
|
|||||||
|
|
||||||
#include <test/libsolidity/SMTCheckerJSONTest.h>
|
#include <test/libsolidity/SMTCheckerJSONTest.h>
|
||||||
#include <test/Common.h>
|
#include <test/Common.h>
|
||||||
|
|
||||||
|
#include <libsolidity/formal/ModelChecker.h>
|
||||||
#include <libsolidity/interface/StandardCompiler.h>
|
#include <libsolidity/interface/StandardCompiler.h>
|
||||||
#include <libsolutil/CommonIO.h>
|
#include <libsolutil/CommonIO.h>
|
||||||
#include <libsolutil/JSON.h>
|
#include <libsolutil/JSON.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/join.hpp>
|
#include <boost/algorithm/string/join.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <boost/throw_exception.hpp>
|
#include <boost/throw_exception.hpp>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@ -50,6 +54,9 @@ SMTCheckerJSONTest::SMTCheckerJSONTest(string const& _filename, langutil::EVMVer
|
|||||||
!m_smtResponses.isObject()
|
!m_smtResponses.isObject()
|
||||||
)
|
)
|
||||||
BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file."));
|
BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file."));
|
||||||
|
|
||||||
|
if (ModelChecker::availableSolvers().none())
|
||||||
|
m_shouldRun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
||||||
|
@ -44,6 +44,15 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _ev
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
m_enabledSolvers = smt::SMTSolverChoice::All();
|
m_enabledSolvers = smt::SMTSolverChoice::All();
|
||||||
|
|
||||||
|
auto available = ModelChecker::availableSolvers();
|
||||||
|
if (!available.z3)
|
||||||
|
m_enabledSolvers.z3 = false;
|
||||||
|
if (!available.cvc4)
|
||||||
|
m_enabledSolvers.cvc4 = false;
|
||||||
|
|
||||||
|
if (m_enabledSolvers.none())
|
||||||
|
m_shouldRun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
||||||
@ -55,17 +64,3 @@ TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePr
|
|||||||
|
|
||||||
return printExpectationAndError(_stream, _linePrefix, _formatted) ? TestResult::Success : TestResult::Failure;
|
return printExpectationAndError(_stream, _linePrefix, _formatted) ? TestResult::Success : TestResult::Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SMTCheckerTest::validateSettings(langutil::EVMVersion _evmVersion)
|
|
||||||
{
|
|
||||||
auto available = ModelChecker::availableSolvers();
|
|
||||||
if (!available.z3)
|
|
||||||
m_enabledSolvers.z3 = false;
|
|
||||||
if (!available.cvc4)
|
|
||||||
m_enabledSolvers.cvc4 = false;
|
|
||||||
|
|
||||||
if (m_enabledSolvers.none())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return SyntaxTest::validateSettings(_evmVersion);
|
|
||||||
}
|
|
||||||
|
@ -37,8 +37,6 @@ public:
|
|||||||
|
|
||||||
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
||||||
|
|
||||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// This is set via option SMTSolvers in the test.
|
/// This is set via option SMTSolvers in the test.
|
||||||
/// The possible options are `all`, `z3`, `cvc4`, `none`,
|
/// The possible options are `all`, `z3`, `cvc4`, `none`,
|
||||||
|
@ -71,6 +71,9 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
|
|||||||
m_settings.erase("ABIEncoderV1Only");
|
m_settings.erase("ABIEncoderV1Only");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_runWithABIEncoderV1Only && solidity::test::CommonOptions::get().useABIEncoderV2)
|
||||||
|
m_shouldRun = false;
|
||||||
|
|
||||||
if (m_settings.count("revertStrings"))
|
if (m_settings.count("revertStrings"))
|
||||||
{
|
{
|
||||||
auto revertStrings = revertStringsFromString(m_settings["revertStrings"]);
|
auto revertStrings = revertStringsFromString(m_settings["revertStrings"]);
|
||||||
@ -90,17 +93,11 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
|
|||||||
soltestAssert(!m_tests.empty(), "No tests specified in " + _filename);
|
soltestAssert(!m_tests.empty(), "No tests specified in " + _filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SemanticTest::validateSettings(langutil::EVMVersion _evmVersion)
|
|
||||||
{
|
|
||||||
if (m_runWithABIEncoderV1Only && solidity::test::CommonOptions::get().useABIEncoderV2)
|
|
||||||
return false;
|
|
||||||
return EVMVersionRestrictedTestCase::validateSettings(_evmVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
||||||
{
|
{
|
||||||
for(bool compileViaYul: set<bool>{!m_runWithoutYul, m_runWithYul})
|
for(bool compileViaYul: set<bool>{!m_runWithoutYul, m_runWithYul})
|
||||||
{
|
{
|
||||||
|
reset();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
m_compileViaYul = compileViaYul;
|
m_compileViaYul = compileViaYul;
|
||||||
|
@ -44,8 +44,6 @@ public:
|
|||||||
|
|
||||||
explicit SemanticTest(std::string const& _filename, langutil::EVMVersion _evmVersion);
|
explicit SemanticTest(std::string const& _filename, langutil::EVMVersion _evmVersion);
|
||||||
|
|
||||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
|
||||||
|
|
||||||
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
||||||
void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool _formatted = false) const override;
|
void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool _formatted = false) const override;
|
||||||
void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix = "") const override;
|
void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix = "") const override;
|
||||||
|
@ -540,7 +540,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
|
|||||||
"default",
|
"default",
|
||||||
"define",
|
"define",
|
||||||
"final",
|
"final",
|
||||||
"immutable",
|
|
||||||
"implements",
|
"implements",
|
||||||
"in",
|
"in",
|
||||||
"inline",
|
"inline",
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f(string memory s) public pure returns (bytes memory t) {
|
||||||
|
t = bytes(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(string): 32, 5, "Hello" -> 32, 5, "Hello"
|
@ -0,0 +1,14 @@
|
|||||||
|
abstract contract I
|
||||||
|
{
|
||||||
|
function a() internal view virtual returns(uint256);
|
||||||
|
}
|
||||||
|
abstract contract V is I
|
||||||
|
{
|
||||||
|
function b() public view returns(uint256) { return a(); }
|
||||||
|
}
|
||||||
|
contract C is V
|
||||||
|
{
|
||||||
|
function a() internal view override returns (uint256) { return 42;}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// b() -> 42
|
@ -0,0 +1,9 @@
|
|||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(uint256[][] calldata x) external { x[0]; }
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=byzantium
|
||||||
|
// revertStrings: debug
|
||||||
|
// ----
|
||||||
|
// f(uint256[][]): 0x20, 1, 0x20, 2, 0x42 -> FAILURE, hex"08c379a0", 0x20, 23, "Calldata tail too short"
|
@ -4,7 +4,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
function f(bool x) public returns (uint) {
|
function f(bool x) public returns (uint) {
|
||||||
// Set the gas to make this work on pre-byzantium VMs
|
// Set the gas to make this work on pre-byzantium VMs
|
||||||
try this.g.gas(8000)(x) {
|
try this.g{gas: 8000}(x) {
|
||||||
return 1;
|
return 1;
|
||||||
} catch {
|
} catch {
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -4,7 +4,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
function f(bool x) public returns (uint) {
|
function f(bool x) public returns (uint) {
|
||||||
// Set the gas to make this work on pre-byzantium VMs
|
// Set the gas to make this work on pre-byzantium VMs
|
||||||
try this.g.gas(8000)(x) {
|
try this.g{gas: 8000}(x) {
|
||||||
return 1;
|
return 1;
|
||||||
} catch {
|
} catch {
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint x) public pure returns (uint) {
|
||||||
|
return 2 * x;
|
||||||
|
}
|
||||||
|
function g() public view returns (function (uint) external returns (uint)) {
|
||||||
|
return this.f;
|
||||||
|
}
|
||||||
|
function h(uint x) public returns (uint) {
|
||||||
|
return this.g()(x) + 1;
|
||||||
|
}
|
||||||
|
function t() external view returns (
|
||||||
|
function(uint) external returns (uint) a,
|
||||||
|
function(uint) external view returns (uint) b) {
|
||||||
|
a = this.f;
|
||||||
|
b = this.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(uint256): 2 -> 4
|
||||||
|
// h(uint256): 2 -> 5
|
||||||
|
// t() -> 0xFDD67305928FCAC8D213D1E47BFA6165CD0B87BB3DE648B0000000000000000, 0xFDD67305928FCAC8D213D1E47BFA6165CD0B87BB3DE648B0000000000000000
|
17
test/libsolidity/semanticTests/viaYul/function_address.sol
Normal file
17
test/libsolidity/semanticTests/viaYul/function_address.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
function f() external returns (address) {
|
||||||
|
return this.f.address;
|
||||||
|
}
|
||||||
|
function g() external returns (bool) {
|
||||||
|
return this.f.address == address(this);
|
||||||
|
}
|
||||||
|
function h(function() external a) public returns (address) {
|
||||||
|
return a.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f() -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b
|
||||||
|
// g() -> true
|
||||||
|
// h(function): left(0x1122334400112233445566778899AABBCCDDEEFF42424242) -> 0x1122334400112233445566778899AABBCCDDEEFF
|
13
test/libsolidity/semanticTests/viaYul/function_selector.sol
Normal file
13
test/libsolidity/semanticTests/viaYul/function_selector.sol
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
contract C {
|
||||||
|
function f() external returns (bytes4) {
|
||||||
|
return this.f.selector;
|
||||||
|
}
|
||||||
|
function h(function() external a) public returns (bytes4) {
|
||||||
|
return a.selector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f() -> left(0x26121ff0)
|
||||||
|
// h(function): left(0x1122334400112233445566778899AABBCCDDEEFF42424242) -> left(0x42424242)
|
@ -6,23 +6,17 @@ contract c {
|
|||||||
x = x + 1;
|
x = x + 1;
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
function g(bool a) public returns (bool) {
|
function g() public returns (bool) {
|
||||||
bool b;
|
bool b;
|
||||||
if (a) {
|
|
||||||
x = 0;
|
|
||||||
b = (f() == 0) && (f() == 0);
|
|
||||||
assert(x == 1);
|
|
||||||
assert(!b);
|
|
||||||
} else {
|
|
||||||
x = 100;
|
x = 100;
|
||||||
b = (f() > 0) && (f() > 0);
|
b = f() > 0;
|
||||||
assert(x == 102);
|
assert(x == 102);
|
||||||
// Should fail.
|
// Should fail.
|
||||||
assert(!b);
|
assert(!b);
|
||||||
}
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (362-372): Assertion violation happens here
|
// Warning: (202-218): Assertion violation happens here
|
||||||
|
// Warning: (242-252): Assertion violation happens here
|
||||||
|
@ -19,4 +19,6 @@ contract A is B {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
|
// Warning: (217-222): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (265-270): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (253-271): Assertion violation happens here
|
// Warning: (253-271): Assertion violation happens here
|
||||||
|
@ -26,4 +26,6 @@ contract A is B2, B1 {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (342-347): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (330-348): Assertion violation happens here
|
// Warning: (330-348): Assertion violation happens here
|
||||||
|
@ -26,4 +26,6 @@ contract A is B2, B1 {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (342-347): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (330-348): Assertion violation happens here
|
// Warning: (330-348): Assertion violation happens here
|
||||||
|
@ -31,4 +31,7 @@ contract A is B2, B1 {
|
|||||||
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (362-378): Assertion violation happens here
|
// Warning: (362-378): Assertion violation happens here
|
||||||
|
@ -26,4 +26,5 @@ contract A is B {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here
|
// Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
// Warning: (356-370): Assertion violation happens here
|
// Warning: (356-370): Assertion violation happens here
|
||||||
|
@ -14,3 +14,4 @@ contract A is C {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (148-162): Assertion violation happens here
|
// Warning: (148-162): Assertion violation happens here
|
||||||
|
// Warning: (166-182): Assertion violation happens here
|
||||||
|
@ -9,5 +9,3 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (99-107): Assertion checker does not support recursive function calls.
|
|
||||||
// Warning: (141-144): Assertion checker does not support recursive function calls.
|
|
||||||
|
@ -2,40 +2,22 @@ pragma experimental SMTChecker;
|
|||||||
|
|
||||||
contract C
|
contract C
|
||||||
{
|
{
|
||||||
uint x;
|
|
||||||
uint y;
|
uint y;
|
||||||
uint z;
|
|
||||||
|
|
||||||
function f() public {
|
function f() public {
|
||||||
if (x == 1)
|
if (y != 1)
|
||||||
x = 2;
|
g();
|
||||||
else
|
|
||||||
x = 1;
|
|
||||||
g();
|
|
||||||
assert(y == 1);
|
assert(y == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function g() public {
|
function g() internal {
|
||||||
y = 1;
|
y = 1;
|
||||||
h();
|
h();
|
||||||
assert(z == 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function h() public {
|
function h() internal {
|
||||||
z = 1;
|
|
||||||
x = 1;
|
|
||||||
f();
|
f();
|
||||||
// This fails for the following calls to the contract:
|
assert(y == 1);
|
||||||
// h()
|
|
||||||
// g() h()
|
|
||||||
// It does not fail for f() g() h() because in that case
|
|
||||||
// h() will not inline f() since it already is in the callstack.
|
|
||||||
assert(x == 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (271-274): Assertion checker does not support recursive function calls.
|
|
||||||
// Warning: (140-143): Assertion checker does not support recursive function calls.
|
|
||||||
// Warning: (483-497): Assertion violation happens here
|
|
||||||
// Warning: (201-204): Assertion checker does not support recursive function calls.
|
|
||||||
// Warning: (483-497): Assertion violation happens here
|
|
||||||
|
@ -14,4 +14,3 @@ contract C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
// Warning: (111-114): Assertion checker does not support recursive function calls.
|
|
||||||
|
@ -22,5 +22,4 @@ contract C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (206-209): Assertion checker does not support recursive function calls.
|
// Warning: (130-144): Error trying to invoke SMT solver.
|
||||||
// Warning: (111-114): Assertion checker does not support recursive function calls.
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract C{
|
||||||
|
uint x;
|
||||||
|
constructor(uint y) public {
|
||||||
|
assert(x == 0);
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
function f() public {
|
||||||
|
assert(x == 1);
|
||||||
|
++x;
|
||||||
|
g();
|
||||||
|
assert(x == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() internal {
|
||||||
|
assert(x == 2);
|
||||||
|
--x;
|
||||||
|
assert(x == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (70-76): Unused function parameter. Remove or comment out the variable name to silence this warning.
|
||||||
|
// Warning: (163-166): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (245-248): Underflow (resulting value less than 0) happens here
|
@ -0,0 +1,32 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract C{
|
||||||
|
uint x;
|
||||||
|
constructor(uint y) public {
|
||||||
|
assert(x == 1);
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
function f() public {
|
||||||
|
assert(x == 2);
|
||||||
|
++x;
|
||||||
|
g();
|
||||||
|
assert(x == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() internal {
|
||||||
|
assert(x == 3);
|
||||||
|
--x;
|
||||||
|
assert(x == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (70-76): Unused function parameter. Remove or comment out the variable name to silence this warning.
|
||||||
|
// Warning: (145-159): Assertion violation happens here
|
||||||
|
// Warning: (163-166): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (227-241): Assertion violation happens here
|
||||||
|
// Warning: (252-266): Assertion violation happens here
|
||||||
|
// Warning: (177-191): Assertion violation happens here
|
||||||
|
// Warning: (227-241): Assertion violation happens here
|
||||||
|
// Warning: (245-248): Underflow (resulting value less than 0) happens here
|
||||||
|
// Warning: (252-266): Assertion violation happens here
|
||||||
|
// Warning: (89-103): Assertion violation happens here
|
@ -0,0 +1,21 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract A {
|
||||||
|
uint x;
|
||||||
|
function f() internal {
|
||||||
|
assert(x == 1);
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract C is A {
|
||||||
|
constructor() public {
|
||||||
|
assert(x == 0);
|
||||||
|
++x;
|
||||||
|
f();
|
||||||
|
assert(x == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (100-103): Underflow (resulting value less than 0) happens here
|
||||||
|
// Warning: (100-103): Underflow (resulting value less than 0) happens here
|
@ -0,0 +1,26 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract A {
|
||||||
|
uint x;
|
||||||
|
function f() internal {
|
||||||
|
assert(x == 2);
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract C is A {
|
||||||
|
constructor() public {
|
||||||
|
assert(x == 1);
|
||||||
|
++x;
|
||||||
|
f();
|
||||||
|
assert(x == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (82-96): Assertion violation happens here
|
||||||
|
// Warning: (100-103): Underflow (resulting value less than 0) happens here
|
||||||
|
// Warning: (82-96): Assertion violation happens here
|
||||||
|
// Warning: (100-103): Underflow (resulting value less than 0) happens here
|
||||||
|
// Warning: (155-169): Assertion violation happens here
|
||||||
|
// Warning: (82-96): Assertion violation happens here
|
||||||
|
// Warning: (187-201): Assertion violation happens here
|
@ -0,0 +1,27 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract C{
|
||||||
|
uint x;
|
||||||
|
constructor(uint y) public {
|
||||||
|
assert(x == 0);
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
function f() public {
|
||||||
|
assert(x == 1);
|
||||||
|
++x;
|
||||||
|
++x;
|
||||||
|
g();
|
||||||
|
g();
|
||||||
|
assert(x == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() internal {
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (70-76): Unused function parameter. Remove or comment out the variable name to silence this warning.
|
||||||
|
// Warning: (163-166): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (170-173): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (241-244): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (241-244): Underflow (resulting value less than 0) happens here
|
@ -0,0 +1,30 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract C{
|
||||||
|
uint x;
|
||||||
|
constructor(uint y) public {
|
||||||
|
assert(x == 1);
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
function f() public {
|
||||||
|
assert(x == 2);
|
||||||
|
++x;
|
||||||
|
++x;
|
||||||
|
g();
|
||||||
|
g();
|
||||||
|
assert(x == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() internal {
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (70-76): Unused function parameter. Remove or comment out the variable name to silence this warning.
|
||||||
|
// Warning: (145-159): Assertion violation happens here
|
||||||
|
// Warning: (163-166): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (170-173): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (241-244): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (191-205): Assertion violation happens here
|
||||||
|
// Warning: (241-244): Underflow (resulting value less than 0) happens here
|
||||||
|
// Warning: (89-103): Assertion violation happens here
|
@ -0,0 +1,18 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function g(uint y) public {
|
||||||
|
uint z = L.f(y);
|
||||||
|
assert(z == y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
library L {
|
||||||
|
function f(uint x) internal returns (uint) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
// Warning: (131-190): Function state mutability can be restricted to pure
|
||||||
|
// Warning: (86-87): Assertion checker does not yet implement type type(library L)
|
@ -0,0 +1,26 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
library l1 {
|
||||||
|
|
||||||
|
uint private constant TON = 1000;
|
||||||
|
function f1() public pure {
|
||||||
|
assert(TON == 1000);
|
||||||
|
assert(TON == 2000);
|
||||||
|
}
|
||||||
|
function f2(uint x, uint y) internal pure returns (uint) {
|
||||||
|
return x + y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function f(uint x) public pure {
|
||||||
|
uint z = l1.f2(x, 1);
|
||||||
|
assert(z == x + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (136-155): Assertion violation happens here
|
||||||
|
// Warning: (229-234): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (300-302): Assertion checker does not yet implement type type(library l1)
|
||||||
|
// Warning: (229-234): Overflow (resulting value larger than 2**256 - 1) happens here
|
||||||
|
// Warning: (327-332): Overflow (resulting value larger than 2**256 - 1) happens here
|
@ -0,0 +1,9 @@
|
|||||||
|
pragma experimental SMTChecker;
|
||||||
|
|
||||||
|
library l1 {
|
||||||
|
|
||||||
|
uint private constant TON = 1000;
|
||||||
|
function f1() public pure {
|
||||||
|
assert(TON == 1000);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user