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:
|
||||
|
||||
|
||||
### 0.6.4 (unreleased)
|
||||
### 0.6.5 (unreleased)
|
||||
|
||||
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: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
||||
* Commandline Interface: Enable output of experimental optimized IR via ``--ir-optimized``.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
* 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)
|
||||
|
||||
@ -37,6 +54,7 @@ Compiler Features:
|
||||
* 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.
|
||||
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
|
||||
* SMTChecker: CHC support to internal function calls.
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
|
@ -888,5 +888,9 @@
|
||||
"0.6.3": {
|
||||
"bugs": [],
|
||||
"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
|
||||
/// can be pre-computed. It is just there for illustration.
|
||||
/// 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),
|
||||
address(this),
|
||||
salt,
|
||||
|
@ -547,6 +547,7 @@ not mean loss of proving power.
|
||||
|
||||
pragma solidity >=0.5.0;
|
||||
pragma experimental SMTChecker;
|
||||
// This may report a warning if no SMT solver available.
|
||||
|
||||
contract Recover
|
||||
{
|
||||
@ -601,6 +602,7 @@ types.
|
||||
pragma solidity >=0.5.0;
|
||||
pragma experimental SMTChecker;
|
||||
// This will report a warning
|
||||
|
||||
contract Aliasing
|
||||
{
|
||||
uint[] array;
|
||||
|
@ -646,7 +646,7 @@ External (or public) functions have the following members:
|
||||
Example that shows how to use the members::
|
||||
|
||||
pragma solidity >=0.4.16 <0.8.0;
|
||||
|
||||
// This will report a warning
|
||||
|
||||
contract Example {
|
||||
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)
|
||||
{
|
||||
if (_item.type() != Operation)
|
||||
|
@ -47,6 +47,8 @@ struct SemanticInformation
|
||||
static bool altersControlFlow(AssemblyItem const& _item);
|
||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||
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
|
||||
/// the information in the current block header, memory, storage or stack.
|
||||
static bool isDeterministic(AssemblyItem const& _item);
|
||||
|
@ -166,6 +166,7 @@ namespace solidity::langutil
|
||||
K(Indexed, "indexed", 0) \
|
||||
K(Interface, "interface", 0) \
|
||||
K(Internal, "internal", 0) \
|
||||
K(Immutable, "immutable", 0) \
|
||||
K(Import, "import", 0) \
|
||||
K(Is, "is", 0) \
|
||||
K(Library, "library", 0) \
|
||||
@ -243,7 +244,6 @@ namespace solidity::langutil
|
||||
K(Default, "default", 0) \
|
||||
K(Define, "define", 0) \
|
||||
K(Final, "final", 0) \
|
||||
K(Immutable, "immutable", 0) \
|
||||
K(Implements, "implements", 0) \
|
||||
K(In, "in", 0) \
|
||||
K(Inline, "inline", 0) \
|
||||
|
@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
|
||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
||||
|
||||
m_errorReporter.typeError(
|
||||
variableOccurrence->occurrence() ?
|
||||
variableOccurrence->occurrence()->location() :
|
||||
*variableOccurrence->occurrence() :
|
||||
variableOccurrence->declaration().location(),
|
||||
ssl,
|
||||
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
|
||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
||||
}
|
||||
).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
|
||||
std::set<SourceLocation> unreachable;
|
||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
|
||||
[&](CFGNode const* _node, auto&& _addChild) {
|
||||
if (!reachable.count(_node) && _node->location.isValid())
|
||||
unreachable.insert(_node->location);
|
||||
|
@ -36,9 +36,9 @@ public:
|
||||
private:
|
||||
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
||||
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.
|
||||
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;
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
using namespace solidity;
|
||||
using namespace solidity::langutil;
|
||||
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
|
||||
m_nodeContainer(_nodeContainer),
|
||||
m_currentNode(_functionFlow.entry),
|
||||
m_returnNode(_functionFlow.exit),
|
||||
m_revertNode(_functionFlow.revert)
|
||||
m_revertNode(_functionFlow.revert),
|
||||
m_transactionReturnNode(_functionFlow.transactionReturn)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
||||
CFG::NodeContainer& _nodeContainer,
|
||||
FunctionDefinition const& _function
|
||||
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
||||
functionFlow->entry = _nodeContainer.newNode();
|
||||
functionFlow->exit = _nodeContainer.newNode();
|
||||
functionFlow->revert = _nodeContainer.newNode();
|
||||
functionFlow->transactionReturn = _nodeContainer.newNode();
|
||||
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
||||
builder.appendControlFlow(_function);
|
||||
|
||||
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
|
||||
if (_forStatement.condition())
|
||||
appendControlFlow(*_forStatement.condition());
|
||||
|
||||
auto loopExpression = newLabel();
|
||||
auto postPart = newLabel();
|
||||
auto nodes = splitFlow<2>();
|
||||
auto afterFor = nodes[1];
|
||||
m_currentNode = nodes[0];
|
||||
|
||||
{
|
||||
BreakContinueScope scope(*this, afterFor, loopExpression);
|
||||
BreakContinueScope scope(*this, afterFor, postPart);
|
||||
appendControlFlow(_forStatement.body());
|
||||
}
|
||||
|
||||
placeAndConnectLabel(loopExpression);
|
||||
placeAndConnectLabel(postPart);
|
||||
|
||||
if (auto expression = _forStatement.loopExpression())
|
||||
appendControlFlow(*expression);
|
||||
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
|
||||
appendControlFlow(*returnParameter);
|
||||
m_returnNode->variableOccurrences.emplace_back(
|
||||
*returnParameter,
|
||||
VariableOccurrence::Kind::Return,
|
||||
nullptr
|
||||
VariableOccurrence::Kind::Return
|
||||
);
|
||||
|
||||
}
|
||||
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
*returnParameter,
|
||||
VariableOccurrence::Kind::Assignment,
|
||||
&_return
|
||||
_return.location()
|
||||
);
|
||||
}
|
||||
connect(m_currentNode, m_returnNode);
|
||||
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
|
||||
|
||||
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
solAssert(!!m_currentNode, "");
|
||||
visitNode(_inlineAssembly);
|
||||
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
|
||||
solAssert(!!m_currentNode && !m_inlineAssembly, "");
|
||||
|
||||
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(
|
||||
*variableDeclaration,
|
||||
VariableOccurrence::Kind::InlineAssembly,
|
||||
&_inlineAssembly
|
||||
*declaration,
|
||||
VariableOccurrence::Kind::Access,
|
||||
_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)
|
||||
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
_variableDeclaration,
|
||||
VariableOccurrence::Kind::Declaration,
|
||||
nullptr
|
||||
VariableOccurrence::Kind::Declaration
|
||||
);
|
||||
|
||||
// Handle declaration with immediate assignment.
|
||||
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
_variableDeclaration,
|
||||
VariableOccurrence::Kind::Assignment,
|
||||
_variableDeclaration.value().get()
|
||||
_variableDeclaration.value()->location()
|
||||
);
|
||||
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
||||
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
_variableDeclaration,
|
||||
VariableOccurrence::Kind::Assignment,
|
||||
nullptr
|
||||
VariableOccurrence::Kind::Assignment
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
*var,
|
||||
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 ?
|
||||
VariableOccurrence::Kind::Assignment :
|
||||
VariableOccurrence::Kind::Access,
|
||||
&_identifier
|
||||
_identifier.location()
|
||||
);
|
||||
|
||||
return true;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
@ -30,7 +31,7 @@ namespace solidity::frontend {
|
||||
* Modifiers are not yet applied to the functions. This is done in a second
|
||||
* step in the CFG class.
|
||||
*/
|
||||
class ControlFlowBuilder: private ASTConstVisitor
|
||||
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
||||
@ -39,7 +40,10 @@ public:
|
||||
);
|
||||
|
||||
private:
|
||||
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
|
||||
explicit ControlFlowBuilder(
|
||||
CFG::NodeContainer& _nodeContainer,
|
||||
FunctionFlow const& _functionFlow
|
||||
);
|
||||
|
||||
// Visits for constructing the control flow.
|
||||
bool visit(BinaryOperation const& _operation) override;
|
||||
@ -62,6 +66,17 @@ private:
|
||||
// Visits for filling variable occurrences.
|
||||
bool visit(FunctionTypeName const& _functionTypeName) 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(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
@ -70,6 +85,9 @@ protected:
|
||||
bool visitNode(ASTNode const&) override;
|
||||
|
||||
private:
|
||||
using ASTConstVisitor::visit;
|
||||
using yul::ASTWalker::visit;
|
||||
using yul::ASTWalker::operator();
|
||||
|
||||
/// Appends the control flow of @a _node to the current control flow.
|
||||
void appendControlFlow(ASTNode const& _node);
|
||||
@ -136,6 +154,7 @@ private:
|
||||
CFGNode* m_currentNode = nullptr;
|
||||
CFGNode* m_returnNode = nullptr;
|
||||
CFGNode* m_revertNode = nullptr;
|
||||
CFGNode* m_transactionReturnNode = nullptr;
|
||||
|
||||
/// The current jump destination of break Statements.
|
||||
CFGNode* m_breakJump = nullptr;
|
||||
@ -145,6 +164,8 @@ private:
|
||||
CFGNode* m_placeholderEntry = nullptr;
|
||||
CFGNode* m_placeholderExit = nullptr;
|
||||
|
||||
InlineAssembly const* m_inlineAssembly = nullptr;
|
||||
|
||||
/// Helper class that replaces the break and continue jump destinations for the
|
||||
/// current scope and restores the originals at the end of the scope.
|
||||
class BreakContinueScope
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
#include <map>
|
||||
@ -33,8 +34,8 @@ namespace solidity::frontend
|
||||
/**
|
||||
* Occurrence of a variable in a block of control flow.
|
||||
* Stores the declaration of the referenced variable, the
|
||||
* kind of the occurrence and possibly the node at which
|
||||
* it occurred.
|
||||
* kind of the occurrence and possibly the source location
|
||||
* at which it occurred.
|
||||
*/
|
||||
class VariableOccurrence
|
||||
{
|
||||
@ -47,7 +48,7 @@ public:
|
||||
Assignment,
|
||||
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)
|
||||
{
|
||||
}
|
||||
@ -57,8 +58,8 @@ public:
|
||||
{
|
||||
if (m_occurrence && _rhs.m_occurrence)
|
||||
{
|
||||
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
|
||||
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
|
||||
if (*m_occurrence < *_rhs.m_occurrence) return true;
|
||||
if (*_rhs.m_occurrence < *m_occurrence) return false;
|
||||
}
|
||||
else if (_rhs.m_occurrence)
|
||||
return true;
|
||||
@ -74,14 +75,14 @@ public:
|
||||
|
||||
VariableDeclaration const& declaration() const { return m_declaration; }
|
||||
Kind kind() const { return m_occurrenceKind; };
|
||||
ASTNode const* occurrence() const { return m_occurrence; }
|
||||
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
|
||||
private:
|
||||
/// Declaration of the occurring variable.
|
||||
VariableDeclaration const& m_declaration;
|
||||
/// Kind of occurrence.
|
||||
Kind m_occurrenceKind = Kind::Access;
|
||||
/// AST node at which the variable occurred, if available (may be nullptr).
|
||||
ASTNode const* m_occurrence = nullptr;
|
||||
/// Source location at which the variable occurred, if available (may be 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
|
||||
/// (e.g. all assert, require, revert and throw statements).
|
||||
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
|
||||
@ -140,7 +145,6 @@ public:
|
||||
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
||||
};
|
||||
private:
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
|
||||
/// Node container.
|
||||
|
@ -67,6 +67,22 @@ void ReferencesResolver::endVisit(Block const& _block)
|
||||
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)
|
||||
{
|
||||
if (!m_resolveInsideCode)
|
||||
|
@ -70,6 +70,8 @@ private:
|
||||
|
||||
bool visit(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;
|
||||
void endVisit(ForStatement const& _for) override;
|
||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||
|
@ -1703,22 +1703,21 @@ void TypeChecker::typeCheckFunctionCall(
|
||||
|
||||
if (_functionType->kind() == FunctionType::Kind::Declaration)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
_functionCall.location(),
|
||||
"Cannot call function via contract type name."
|
||||
);
|
||||
if (
|
||||
m_scope->derivesFrom(*_functionType->declaration().annotation().contract) &&
|
||||
!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;
|
||||
}
|
||||
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
|
||||
if (
|
||||
@ -2312,7 +2311,11 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
||||
else if (!expressionFunctionType->isPayable())
|
||||
m_errorReporter.typeError(
|
||||
_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
|
||||
{
|
||||
@ -2522,12 +2525,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
annotation.type = possibleMembers.front().type;
|
||||
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
||||
{
|
||||
solAssert(
|
||||
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
||||
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
||||
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))
|
||||
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
||||
else if (exprType->category() == Type::Category::Array)
|
||||
|
@ -390,7 +390,7 @@ DeclarationAnnotation& Declaration::annotation() const
|
||||
bool VariableDeclaration::isLValue() const
|
||||
{
|
||||
// Constant declared variables are Read-Only
|
||||
if (m_isConstant)
|
||||
if (isConstant())
|
||||
return false;
|
||||
// External function arguments of reference type are Read-Only
|
||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
||||
|
@ -814,6 +814,7 @@ class VariableDeclaration: public Declaration
|
||||
{
|
||||
public:
|
||||
enum Location { Unspecified, Storage, Memory, CallData };
|
||||
enum class Constantness { Mutable, Immutable, Constant };
|
||||
|
||||
VariableDeclaration(
|
||||
int64_t _id,
|
||||
@ -824,7 +825,7 @@ public:
|
||||
Visibility _visibility,
|
||||
bool _isStateVar = false,
|
||||
bool _isIndexed = false,
|
||||
bool _isConstant = false,
|
||||
Constantness _constantness = Constantness::Mutable,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
||||
Location _referenceLocation = Location::Unspecified
|
||||
):
|
||||
@ -833,7 +834,7 @@ public:
|
||||
m_value(_value),
|
||||
m_isStateVariable(_isStateVar),
|
||||
m_isIndexed(_isIndexed),
|
||||
m_isConstant(_isConstant),
|
||||
m_constantness(_constantness),
|
||||
m_overrides(_overrides),
|
||||
m_location(_referenceLocation) {}
|
||||
|
||||
@ -877,7 +878,7 @@ public:
|
||||
bool hasReferenceOrMappingType() const;
|
||||
bool isStateVariable() const { return m_isStateVariable; }
|
||||
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; }
|
||||
Location referenceLocation() const { return m_location; }
|
||||
/// @returns a set of allowed storage locations for the variable.
|
||||
@ -904,7 +905,8 @@ private:
|
||||
ASTPointer<Expression> m_value;
|
||||
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_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
|
||||
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!");
|
||||
|
||||
VariableDeclaration::Constantness constantness{};
|
||||
if (memberAsBool(_node, "constant"))
|
||||
constantness = VariableDeclaration::Constantness::Constant;
|
||||
else
|
||||
constantness = VariableDeclaration::Constantness::Mutable;
|
||||
|
||||
return createASTNode<VariableDeclaration>(
|
||||
_node,
|
||||
nullOrCast<TypeName>(member(_node, "typeName")),
|
||||
@ -419,7 +425,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
||||
visibility(_node),
|
||||
memberAsBool(_node, "stateVariable"),
|
||||
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
||||
memberAsBool(_node, "constant"),
|
||||
constantness,
|
||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||
location(_node)
|
||||
);
|
||||
|
@ -104,6 +104,8 @@ yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node)
|
||||
return createContinue(_node);
|
||||
else if (nodeType == "Leave")
|
||||
return createLeave(_node);
|
||||
else if (nodeType == "Block")
|
||||
return createBlock(_node);
|
||||
else
|
||||
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.type= YulString{member(_node, "type").asString()};
|
||||
|
||||
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||
|
||||
if (kind == "number")
|
||||
{
|
||||
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||
lit.kind = yul::LiteralKind::Number;
|
||||
astAssert(
|
||||
scanner.currentToken() == Token::Number,
|
||||
@ -170,6 +171,7 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
||||
}
|
||||
else if (kind == "bool")
|
||||
{
|
||||
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||
lit.kind = yul::LiteralKind::Boolean;
|
||||
astAssert(
|
||||
scanner.currentToken() == Token::TrueLiteral ||
|
||||
@ -180,7 +182,10 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
||||
else if (kind == "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
|
||||
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)
|
||||
{
|
||||
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"));
|
||||
return caseStatement;
|
||||
}
|
||||
@ -276,7 +285,7 @@ yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
||||
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _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"))
|
||||
switchStatement.cases.emplace_back(createCase(var));
|
||||
return switchStatement;
|
||||
|
@ -2924,7 +2924,10 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
|
||||
{
|
||||
case Kind::External:
|
||||
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;
|
||||
case Kind::BareCall:
|
||||
case Kind::BareCallCode:
|
||||
@ -3471,7 +3474,15 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
|
||||
continue;
|
||||
|
||||
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 (
|
||||
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
||||
declaration->isVisibleViaContractTypeAccess()
|
||||
|
@ -55,7 +55,7 @@ string ABIFunctions::tupleEncoder(
|
||||
functionName += t->identifier() + "_";
|
||||
functionName += options.toFunctionNameSuffix();
|
||||
|
||||
return createExternallyUsedFunction(functionName, [&]() {
|
||||
return createFunction(functionName, [&]() {
|
||||
// Note that the values are in reverse due to the difference in calling semantics.
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(headStart <valueParams>) -> tail {
|
||||
@ -121,7 +121,7 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
functionName += t->identifier() + "_";
|
||||
functionName += options.toFunctionNameSuffix();
|
||||
|
||||
return createExternallyUsedFunction(functionName, [&]() {
|
||||
return createFunction(functionName, [&]() {
|
||||
solAssert(!_givenTypes.empty(), "");
|
||||
|
||||
// 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)
|
||||
functionName += "_fromMemory";
|
||||
|
||||
return createExternallyUsedFunction(functionName, [&]() {
|
||||
return createFunction(functionName, [&]() {
|
||||
TypePointers decodingTypes;
|
||||
for (auto const& t: _types)
|
||||
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 suffix;
|
||||
@ -1499,14 +1492,7 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
|
||||
|
||||
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _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;
|
||||
return m_functionCollector.createFunction(_name, _creator);
|
||||
}
|
||||
|
||||
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||
|
@ -31,7 +31,6 @@
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::frontend
|
||||
@ -58,11 +57,11 @@ public:
|
||||
explicit ABIFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||
MultiUseYulFunctionCollector& _functionCollector
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_functionCollector(std::move(_functionCollector)),
|
||||
m_functionCollector(_functionCollector),
|
||||
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
|
||||
{}
|
||||
|
||||
@ -104,12 +103,6 @@ public:
|
||||
/// stack slot, it takes exactly that number of values.
|
||||
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:
|
||||
struct EncodingOptions
|
||||
{
|
||||
@ -239,11 +232,6 @@ private:
|
||||
/// cases.
|
||||
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.
|
||||
static size_t headSize(TypePointers const& _targetTypes);
|
||||
|
||||
@ -259,8 +247,7 @@ private:
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings const m_revertStrings;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
MultiUseYulFunctionCollector& m_functionCollector;
|
||||
YulUtilFunctions m_utils;
|
||||
};
|
||||
|
||||
|
@ -94,6 +94,20 @@ void CompilerContext::callLowLevelFunction(
|
||||
*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(
|
||||
string const& _name,
|
||||
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(
|
||||
VariableDeclaration const& _declaration,
|
||||
unsigned _offsetToCurrent
|
||||
|
@ -65,7 +65,8 @@ public:
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
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)
|
||||
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
||||
@ -131,6 +132,14 @@ public:
|
||||
unsigned _outArgs,
|
||||
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
|
||||
/// 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,
|
||||
@ -144,6 +153,12 @@ public:
|
||||
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
||||
void appendMissingLowLevelFunctions();
|
||||
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;
|
||||
/// 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;
|
||||
/// An index of low-level function labels by name.
|
||||
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.
|
||||
ABIFunctions m_abiFunctions;
|
||||
/// Container for Yul Util functions to be generated.
|
||||
YulUtilFunctions m_yulUtilFunctions;
|
||||
/// The queue of low-level functions to generate.
|
||||
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)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(_type.isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
||||
solAssert(tailSize > 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>
|
||||
}
|
||||
m_context << Instruction::SWAP1;
|
||||
m_context.callYulFunction(
|
||||
m_context.utilFunctions().accessCalldataTailFunction(_type),
|
||||
2,
|
||||
_type.isDynamicallySized() ? 2 : 1
|
||||
);
|
||||
}
|
||||
|
||||
unsigned CompilerUtils::loadFromMemory(
|
||||
@ -539,6 +495,10 @@ void CompilerUtils::encodeToMemory(
|
||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||
{
|
||||
// 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 << Instruction::SUB;
|
||||
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
||||
@ -595,31 +555,21 @@ void CompilerUtils::abiEncodeV2(
|
||||
|
||||
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
||||
|
||||
auto ret = m_context.pushNewTag();
|
||||
moveIntoStack(sizeOnStack(_givenTypes) + 1);
|
||||
|
||||
string encoderName =
|
||||
_padToWordBoundaries ?
|
||||
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
|
||||
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
|
||||
m_context.appendJumpTo(m_context.namedTag(encoderName));
|
||||
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
||||
m_context << ret.tag();
|
||||
m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
|
||||
}
|
||||
|
||||
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
|
||||
{
|
||||
// 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::SWAP1;
|
||||
// stack: <return tag> <end> <start>
|
||||
// stack: <end> <start>
|
||||
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
|
||||
m_context.appendJumpTo(m_context.namedTag(decoderName));
|
||||
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
|
||||
m_context << ret.tag();
|
||||
m_context.callYulFunction(decoderName, 2, sizeOnStack(_parameterTypes));
|
||||
}
|
||||
|
||||
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
|
@ -1267,12 +1267,12 @@ void ContractCompiler::appendMissingFunctions()
|
||||
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
||||
}
|
||||
m_context.appendMissingLowLevelFunctions();
|
||||
auto abiFunctions = m_context.abiFunctions().requestedFunctions();
|
||||
if (!abiFunctions.first.empty())
|
||||
auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
|
||||
if (!yulFunctions.empty())
|
||||
m_context.appendInlineAssembly(
|
||||
"{" + move(abiFunctions.first) + "}",
|
||||
"{" + move(yulFunctions) + "}",
|
||||
{},
|
||||
abiFunctions.second,
|
||||
externallyUsedYulFunctions,
|
||||
true,
|
||||
m_optimiserSettings
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ using namespace solidity::frontend;
|
||||
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "combine_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr, selector) -> combined {
|
||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||
@ -55,7 +55,7 @@ string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "split_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(combined) -> addr, selector {
|
||||
combined := <shr64>(combined)
|
||||
@ -73,7 +73,7 @@ string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
{
|
||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (_fromCalldata)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
@ -116,7 +116,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
||||
|
||||
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (!_messageType)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(condition) {
|
||||
@ -166,7 +166,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> aligned {
|
||||
<body>
|
||||
@ -228,7 +228,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
string functionName = "shift_left_" + to_string(_numBits);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
@ -251,7 +251,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
||||
string YulUtilFunctions::shiftLeftFunctionDynamic()
|
||||
{
|
||||
string functionName = "shift_left_dynamic";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
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!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
@ -303,7 +303,7 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string const functionName = "shift_right_unsigned_dynamic";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
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 shiftBits = _shiftBytes * 8;
|
||||
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value, toInsert) -> result {
|
||||
@ -350,7 +350,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
||||
solAssert(_numBytes <= 32, "");
|
||||
size_t numBits = _numBytes * 8;
|
||||
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value, shiftBytes, toInsert) -> result {
|
||||
@ -371,7 +371,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
||||
string YulUtilFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
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
|
||||
// and use the following instead:
|
||||
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> sum {
|
||||
@ -416,7 +416,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
||||
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||
{
|
||||
string functionName = "checked_mul_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
// Multiplication by zero could be treated separately and directly return zero.
|
||||
Whiskers(R"(
|
||||
@ -448,7 +448,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||
{
|
||||
string functionName = "checked_div_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> r {
|
||||
@ -473,7 +473,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
||||
{
|
||||
string functionName = "checked_mod_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> r {
|
||||
@ -490,7 +490,7 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
||||
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||
{
|
||||
string functionName = "checked_sub_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> diff {
|
||||
@ -516,7 +516,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<?dynamic>
|
||||
@ -564,7 +564,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
||||
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
||||
|
||||
string functionName = "resize_array_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, newLen) {
|
||||
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.");
|
||||
|
||||
string functionName = "array_pop_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(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.");
|
||||
|
||||
string functionName = "array_push_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, value) {
|
||||
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.");
|
||||
|
||||
string functionName = "array_push_zero_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array) -> slot, offset {
|
||||
let oldLen := <fetchLength>(array)
|
||||
@ -684,7 +684,7 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
|
||||
|
||||
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(start, end) {
|
||||
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();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) {
|
||||
<?dynamic>
|
||||
@ -745,7 +745,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
||||
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Type const& baseType = *_type.baseType();
|
||||
|
||||
switch (_type.location())
|
||||
@ -798,7 +798,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
string functionName = "array_allocation_size_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// 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 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
|
||||
// offset of the data area and length on the stack, so the offset already
|
||||
// points to the data area.
|
||||
@ -858,7 +858,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
||||
|
||||
string functionName = "storage_array_index_access_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, index) -> slot, offset {
|
||||
if iszero(lt(index, <arrayLen>(array))) {
|
||||
@ -886,7 +886,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "memory_array_index_access_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, index) -> addr {
|
||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||
@ -912,7 +912,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
string functionName = "calldata_array_index_access_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
||||
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.dataStoredIn(DataLocation::CallData), "");
|
||||
string functionName = "access_calldata_tail_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
|
||||
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)
|
||||
<?dynamicallySized>
|
||||
length := calldataload(addr)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength> }
|
||||
addr := add(addr, 32)
|
||||
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail> }
|
||||
</dynamicallySized>
|
||||
}
|
||||
)")
|
||||
@ -956,6 +956,9 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
|
||||
("dynamicallySized", _type.isDynamicallySized())
|
||||
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
|
||||
("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();
|
||||
});
|
||||
}
|
||||
@ -966,7 +969,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
if (_type.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(_type.baseType()->storageBytes() > 16, "");
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(ptr) -> next {
|
||||
next := add(ptr, <advance>)
|
||||
@ -1002,7 +1005,7 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
|
||||
solAssert(_keyType.sizeOnStack() <= 1, "");
|
||||
|
||||
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (_mappingType.keyType()->isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot <comma> <key>) -> dataSlot {
|
||||
@ -1050,7 +1053,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
|
||||
to_string(_offset) +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
@ -1071,7 +1074,7 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot, offset) -> value {
|
||||
@ -1101,7 +1104,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
|
||||
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
||||
_type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
if (_type.isValueType())
|
||||
{
|
||||
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
||||
@ -1141,7 +1144,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
|
||||
string("write_to_memory_") +
|
||||
_type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
solAssert(!dynamic_cast<StringLiteralType 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" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value, offset) -> value {
|
||||
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
|
||||
@ -1224,7 +1227,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value) -> value {
|
||||
value := <cleanupStorage>(<shr>(slot_value))
|
||||
@ -1243,7 +1246,7 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
cleaned := <cleaned>
|
||||
@ -1275,7 +1278,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
||||
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
||||
|
||||
string functionName = "prepare_store_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
ret := <actualPrepare>
|
||||
@ -1293,7 +1296,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
||||
string YulUtilFunctions::allocationFunction()
|
||||
{
|
||||
string functionName = "allocateMemory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(size) -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
@ -1314,7 +1317,7 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
||||
solUnimplementedAssert(!_type.isByteArray(), "");
|
||||
|
||||
string functionName = "allocate_memory_array_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(length) -> memPtr {
|
||||
memPtr := <alloc>(<allocSize>(length))
|
||||
@ -1333,6 +1336,35 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
||||
|
||||
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)
|
||||
return conversionFunctionSpecial(_from, _to);
|
||||
|
||||
@ -1341,7 +1373,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> converted {
|
||||
<body>
|
||||
@ -1440,22 +1472,34 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
{
|
||||
bool equal = _from == _to;
|
||||
|
||||
if (!equal)
|
||||
if (_from == _to)
|
||||
body = "converted := value";
|
||||
else
|
||||
{
|
||||
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
||||
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
||||
|
||||
if (*from.mobileType() == *to.mobileType())
|
||||
equal = true;
|
||||
switch (to.location())
|
||||
{
|
||||
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;
|
||||
}
|
||||
case Type::Category::Struct:
|
||||
@ -1519,7 +1563,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("cleanup_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
@ -1606,7 +1650,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
||||
{
|
||||
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) {
|
||||
if iszero(<condition>) { <failure> }
|
||||
@ -1667,7 +1711,7 @@ string YulUtilFunctions::packedHashFunction(
|
||||
size_t sizeOnStack = 0;
|
||||
for (Type const* t: _givenTypes)
|
||||
sizeOnStack += t->sizeOnStack();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(<variables>) -> hash {
|
||||
let pos := mload(<freeMemoryPointer>)
|
||||
@ -1688,7 +1732,7 @@ string YulUtilFunctions::forwardingRevertFunction()
|
||||
{
|
||||
bool forward = m_evmVersion.supportsReturndata();
|
||||
string functionName = "revert_forward_" + to_string(forward);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (forward)
|
||||
return Whiskers(R"(
|
||||
function <functionName>() {
|
||||
@ -1715,7 +1759,7 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
||||
|
||||
string const functionName = "decrement_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
u256 minintval;
|
||||
|
||||
// Smallest admissible value to decrement
|
||||
@ -1743,7 +1787,7 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
|
||||
|
||||
string const functionName = "increment_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
u256 maxintval;
|
||||
|
||||
// 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;
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(_value) -> ret {
|
||||
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();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>() -> ret {
|
||||
<body>
|
||||
@ -1810,7 +1854,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
|
||||
{
|
||||
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (_type.isValueType())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot, offset) {
|
||||
@ -1842,7 +1886,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
if (
|
||||
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
|
||||
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
|
||||
@ -1950,7 +1994,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
|
||||
if (_fromCalldata)
|
||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(refType->sizeOnStack() == 1, "");
|
||||
|
@ -47,11 +47,11 @@ public:
|
||||
explicit YulUtilFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
||||
MultiUseYulFunctionCollector& _functionCollector
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_functionCollector(std::move(_functionCollector))
|
||||
m_functionCollector(_functionCollector)
|
||||
{}
|
||||
|
||||
/// @returns a function that combines the address and selector to a single value
|
||||
@ -306,7 +306,7 @@ private:
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
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 funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||
return m_functions->createFunction(funName, [&]() {
|
||||
return m_functions.createFunction(funName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
||||
switch fun
|
||||
|
@ -56,11 +56,10 @@ public:
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
|
||||
m_optimiserSettings(std::move(_optimiserSettings))
|
||||
{}
|
||||
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
|
||||
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
|
||||
|
||||
/// Sets the current inheritance hierarchy from derived to base.
|
||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
||||
@ -108,7 +107,7 @@ private:
|
||||
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
|
||||
/// Storage offsets of state variables
|
||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
||||
MultiUseYulFunctionCollector m_functions;
|
||||
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* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
t("functions", m_context.functionCollector()->requestedFunctions());
|
||||
t("functions", m_context.functionCollector().requestedFunctions());
|
||||
|
||||
resetContext(_contract);
|
||||
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* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
|
||||
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||
return t.render();
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ string IRGenerator::generate(Block const& _block)
|
||||
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
string functionName = m_context.functionName(_function);
|
||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
||||
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||
Whiskers t(R"(
|
||||
function <functionName>(<params>) <returns> {
|
||||
<body>
|
||||
@ -160,7 +160,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
solAssert(_varDecl.isStateVariable(), "");
|
||||
|
||||
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);
|
||||
solAssert(slot_offset.second == 0, "");
|
||||
FunctionType funType(_varDecl);
|
||||
@ -209,7 +209,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solUnimplementedAssert(type->isValueType(), "");
|
||||
|
||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
||||
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||
|
||||
return Whiskers(R"(
|
||||
@ -383,11 +383,10 @@ string IRGenerator::memoryInit()
|
||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(
|
||||
m_context.functionCollector()->requestedFunctions().empty(),
|
||||
m_context.functionCollector().requestedFunctions().empty(),
|
||||
"Reset context while it still had functions."
|
||||
);
|
||||
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);
|
||||
for (auto const& var: ContractType(_contract).stateVariables())
|
||||
|
@ -47,6 +47,7 @@ using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::frontend;
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -800,11 +801,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
case Type::Category::Function:
|
||||
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")
|
||||
{
|
||||
solUnimplementedAssert(false, "");
|
||||
solUnimplementedAssert(
|
||||
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||
FunctionType::Kind::External, ""
|
||||
);
|
||||
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
@ -1204,7 +1213,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
argumentTypes.emplace_back(&type(*arg));
|
||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||
}
|
||||
string argumentString = ", " + joinHumanReadable(argumentStrings);
|
||||
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
||||
|
||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||
|
||||
|
@ -432,12 +432,6 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
|
||||
m_context.newValue(*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
|
||||
{
|
||||
|
@ -27,6 +27,8 @@
|
||||
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::langutil;
|
||||
@ -75,16 +77,39 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
m_context.setAssertionAccumulation(false);
|
||||
m_variableUsage.setFunctionInlining(false);
|
||||
|
||||
resetSourceAnalysis();
|
||||
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
auto genesisSort = make_shared<smt::FunctionSort>(
|
||||
vector<smt::SortPointer>(),
|
||||
boolSort
|
||||
);
|
||||
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
||||
auto genesis = (*m_genesisPredicate)({});
|
||||
addRule(genesis, genesis.name);
|
||||
addRule(genesis(), "genesis");
|
||||
|
||||
_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
|
||||
@ -97,26 +122,15 @@ vector<string> CHC::unhandledQueries() const
|
||||
|
||||
bool CHC::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
if (!shouldVisit(_contract))
|
||||
return false;
|
||||
|
||||
reset();
|
||||
resetContractAnalysis();
|
||||
|
||||
initContract(_contract);
|
||||
|
||||
m_stateVariables = _contract.stateVariablesIncludingInherited();
|
||||
|
||||
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()));
|
||||
m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
m_stateSorts = stateSorts(_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.
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
@ -125,10 +139,12 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
boolSort
|
||||
);
|
||||
|
||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||
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();
|
||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||
|
||||
SMTEncoder::visit(_contract);
|
||||
return false;
|
||||
@ -136,33 +152,33 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
if (!shouldVisit(_contract))
|
||||
return;
|
||||
|
||||
for (auto const& var: m_stateVariables)
|
||||
{
|
||||
solAssert(m_context.knownVariable(*var), "");
|
||||
auto const& symbVar = m_context.variable(*var);
|
||||
symbVar->resetIndex();
|
||||
m_context.setZeroValue(*var);
|
||||
symbVar->increaseIndex();
|
||||
}
|
||||
auto genesisPred = (*m_genesisPredicate)({});
|
||||
auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables());
|
||||
connectBlocks(genesisPred, implicitConstructor);
|
||||
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
|
||||
connectBlocks(genesis(), implicitConstructor);
|
||||
m_currentBlock = implicitConstructor;
|
||||
m_context.addAssertion(m_error.currentValue() == 0);
|
||||
|
||||
if (auto constructor = _contract.constructor())
|
||||
constructor->accept(*this);
|
||||
else
|
||||
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)
|
||||
{
|
||||
auto const& target = m_verificationTargets.at(i);
|
||||
auto errorAppl = error(i + 1);
|
||||
if (query(errorAppl, target->location()))
|
||||
m_safeAssertions.insert(target);
|
||||
}
|
||||
clearIndices(m_currentContract, nullptr);
|
||||
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||
setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
|
||||
|
||||
addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue());
|
||||
connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
|
||||
|
||||
SMTEncoder::endVisit(_contract);
|
||||
}
|
||||
@ -182,7 +198,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
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;
|
||||
|
||||
initFunction(_function);
|
||||
@ -193,7 +209,17 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
|
||||
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);
|
||||
|
||||
setCurrentBlock(*bodyBlock);
|
||||
@ -225,18 +251,30 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
||||
// This is done in endVisit(ContractDefinition).
|
||||
if (_function.isConstructor())
|
||||
{
|
||||
auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id()));
|
||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables()));
|
||||
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
|
||||
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables()));
|
||||
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
auto stateExprs = currentStateVariables();
|
||||
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||
setCurrentBlock(*constructorExit, &stateExprs);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectBlocks(m_currentBlock, interface());
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
auto stateExprs = currentStateVariables();
|
||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
||||
auto assertionError = m_error.currentValue();
|
||||
auto sum = summary(_function);
|
||||
connectBlocks(m_currentBlock, sum);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -421,6 +459,8 @@ void CHC::endVisit(FunctionCall const& _funCall)
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Internal:
|
||||
internalFunctionCall(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::External:
|
||||
case FunctionType::Kind::DelegateCall:
|
||||
case FunctionType::Kind::BareCall:
|
||||
@ -468,12 +508,56 @@ void CHC::visitAssert(FunctionCall const& _funCall)
|
||||
solAssert(args.size() == 1, "");
|
||||
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());
|
||||
connectBlocks(m_currentBlock, error(), currentPathConditions() && assertNeg);
|
||||
auto previousError = m_error.currentValue();
|
||||
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&)
|
||||
@ -488,15 +572,23 @@ void CHC::unknownFunctionCall(FunctionCall const&)
|
||||
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_stateVariables.clear();
|
||||
m_verificationTargets.clear();
|
||||
m_safeAssertions.clear();
|
||||
m_unknownFunctionCallSeen = false;
|
||||
m_breakDest = nullptr;
|
||||
m_continueDest = nullptr;
|
||||
m_error.resetIndex();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (
|
||||
_function.isPublic() &&
|
||||
_function.isImplemented()
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
return _function.isImplemented();
|
||||
}
|
||||
|
||||
void CHC::setCurrentBlock(
|
||||
@ -547,7 +623,8 @@ void CHC::setCurrentBlock(
|
||||
vector<smt::Expression> const* _arguments
|
||||
)
|
||||
{
|
||||
m_context.popSolver();
|
||||
if (m_context.solverStackHeigh() > 0)
|
||||
m_context.popSolver();
|
||||
solAssert(m_currentContract, "");
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
m_context.pushSolver();
|
||||
@ -557,10 +634,42 @@ void CHC::setCurrentBlock(
|
||||
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()
|
||||
{
|
||||
// TODO this will change once we support function calls.
|
||||
return interfaceSort();
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
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()
|
||||
@ -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)
|
||||
{
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
vector<smt::SortPointer> varSorts;
|
||||
for (auto const& var: _function.parameters() + _function.returnParameters())
|
||||
{
|
||||
// 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()));
|
||||
}
|
||||
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||
vector<smt::SortPointer> inputSorts;
|
||||
for (auto const& var: _function.parameters())
|
||||
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
vector<smt::SortPointer> outputSorts;
|
||||
for (auto const& var: _function.returnParameters())
|
||||
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
return make_shared<smt::FunctionSort>(
|
||||
m_stateSorts + varSorts,
|
||||
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||
boolSort
|
||||
);
|
||||
}
|
||||
@ -601,19 +728,31 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
vector<smt::SortPointer> varSorts;
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
{
|
||||
// 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()));
|
||||
}
|
||||
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
return make_shared<smt::FunctionSort>(
|
||||
fSort->domain + varSorts,
|
||||
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)
|
||||
{
|
||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||
@ -625,12 +764,33 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPoin
|
||||
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()
|
||||
{
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
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()
|
||||
@ -643,6 +803,27 @@ smt::Expression CHC::error(unsigned _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)
|
||||
{
|
||||
return createSymbolicBlock(sort(_node),
|
||||
@ -653,6 +834,15 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
@ -680,11 +892,22 @@ vector<smt::Expression> CHC::currentStateVariables()
|
||||
|
||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||
{
|
||||
vector<smt::Expression> paramExprs;
|
||||
if (m_currentFunction)
|
||||
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return currentStateVariables() + paramExprs;
|
||||
vector<smt::Expression> initInputExprs;
|
||||
vector<smt::Expression> mutableInputExprs;
|
||||
for (auto const& var: m_currentFunction->parameters())
|
||||
{
|
||||
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()
|
||||
@ -696,7 +919,7 @@ vector<smt::Expression> CHC::currentBlockVariables()
|
||||
return currentFunctionVariables() + paramExprs;
|
||||
}
|
||||
|
||||
string CHC::predicateName(ASTNode const* _node)
|
||||
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
|
||||
{
|
||||
string prefix;
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||
@ -705,7 +928,12 @@ string CHC::predicateName(ASTNode const* _node)
|
||||
if (!funDef->name().empty())
|
||||
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)
|
||||
@ -721,12 +949,40 @@ smt::Expression CHC::predicate(
|
||||
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)
|
||||
{
|
||||
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;
|
||||
vector<string> values;
|
||||
@ -736,7 +992,7 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
|
||||
case smt::CheckResult::SATISFIABLE:
|
||||
break;
|
||||
case smt::CheckResult::UNSATISFIABLE:
|
||||
return true;
|
||||
break;
|
||||
case smt::CheckResult::UNKNOWN:
|
||||
break;
|
||||
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.");
|
||||
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()
|
||||
|
@ -76,25 +76,42 @@ private:
|
||||
void endVisit(Continue const& _node) override;
|
||||
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void internalFunctionCall(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.
|
||||
//@{
|
||||
void reset();
|
||||
void resetSourceAnalysis();
|
||||
void resetContractAnalysis();
|
||||
void eraseKnowledge();
|
||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||
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.
|
||||
//@{
|
||||
static std::vector<smt::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||
smt::SortPointer constructorSort();
|
||||
smt::SortPointer interfaceSort();
|
||||
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||
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.
|
||||
@ -102,14 +119,24 @@ private:
|
||||
/// @returns a new block of given _sort and _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.
|
||||
smt::Expression interface();
|
||||
smt::Expression interface(ContractDefinition const& _contract);
|
||||
/// Error predicate over current variables.
|
||||
smt::Expression error();
|
||||
smt::Expression error(unsigned _idx);
|
||||
|
||||
/// Creates a block for the given _node.
|
||||
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.
|
||||
/// 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));
|
||||
|
||||
/// @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.
|
||||
std::vector<smt::Expression> currentStateVariables();
|
||||
|
||||
@ -128,19 +160,28 @@ private:
|
||||
std::vector<smt::Expression> currentBlockVariables();
|
||||
|
||||
/// @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.
|
||||
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
||||
/// @returns a predicate application over @param _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.
|
||||
//@{
|
||||
/// Adds Horn rule to the solver.
|
||||
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
||||
/// @returns true if query is unsatisfiable (safe).
|
||||
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
||||
/// @returns <true, empty> if query is unsatisfiable (safe).
|
||||
/// @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.
|
||||
@ -157,15 +198,29 @@ private:
|
||||
|
||||
/// Implicit constructor predicate.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// Single error block for all assertions.
|
||||
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.
|
||||
@ -180,7 +235,12 @@ private:
|
||||
|
||||
/// 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.
|
||||
std::set<Expression const*> m_safeAssertions;
|
||||
@ -190,6 +250,10 @@ private:
|
||||
//@{
|
||||
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.
|
||||
smt::Expression m_currentBlock = smt::Expression(true);
|
||||
|
||||
|
@ -140,6 +140,7 @@ public:
|
||||
void pushSolver();
|
||||
void popSolver();
|
||||
void addAssertion(Expression const& _e);
|
||||
unsigned solverStackHeigh() { return m_assertions.size(); } const
|
||||
SolverInterface* solver()
|
||||
{
|
||||
solAssert(m_solver, "");
|
||||
|
@ -29,9 +29,9 @@ ModelChecker::ModelChecker(
|
||||
ReadCallback::Callback const& _smtCallback,
|
||||
smt::SMTSolverChoice _enabledSolvers
|
||||
):
|
||||
m_context(),
|
||||
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
||||
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
||||
m_context()
|
||||
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -62,14 +62,14 @@ public:
|
||||
static smt::SMTSolverChoice availableSolvers();
|
||||
|
||||
private:
|
||||
/// Stores the context of the encoding.
|
||||
smt::EncodingContext m_context;
|
||||
|
||||
/// Bounded Model Checker engine.
|
||||
BMC m_bmc;
|
||||
|
||||
/// Constrained Horn Clauses engine.
|
||||
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));
|
||||
solAssert(symbTuple, "");
|
||||
auto const& symbComponents = symbTuple->components();
|
||||
auto const& tupleComponents = _tuple.components();
|
||||
solAssert(symbComponents.size() == _tuple.components().size(), "");
|
||||
auto const* tupleComponents = &_tuple.components();
|
||||
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)
|
||||
{
|
||||
auto sComponent = symbComponents.at(i);
|
||||
auto tComponent = tupleComponents.at(i);
|
||||
auto tComponent = tupleComponents->at(i);
|
||||
if (sComponent && tComponent)
|
||||
{
|
||||
if (auto varDecl = identifierToVariable(*tComponent))
|
||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
||||
else
|
||||
{
|
||||
solAssert(m_context.knownExpression(*tComponent), "");
|
||||
if (!m_context.knownExpression(*tComponent))
|
||||
createExpr(*tComponent);
|
||||
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
||||
}
|
||||
}
|
||||
@ -666,7 +673,6 @@ void SMTEncoder::visitAssert(FunctionCall const& _funCall)
|
||||
auto const& args = _funCall.arguments();
|
||||
solAssert(args.size() == 1, "");
|
||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||
addPathImpliedExpression(expr(*args.front()));
|
||||
}
|
||||
|
||||
void SMTEncoder::visitRequire(FunctionCall const& _funCall)
|
||||
|
@ -58,11 +58,11 @@ private:
|
||||
z3::sort z3Sort(smt::Sort const& _sort);
|
||||
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::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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1373,10 +1372,7 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
|
||||
MetadataCBOREncoder encoder;
|
||||
|
||||
if (m_metadataHash == MetadataHash::IPFS)
|
||||
{
|
||||
solAssert(_metadata.length() < 1024 * 256, "Metadata too large.");
|
||||
encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
|
||||
}
|
||||
else if (m_metadataHash == MetadataHash::Bzzr1)
|
||||
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
|
||||
else
|
||||
|
@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
);
|
||||
|
||||
bool isIndexed = false;
|
||||
bool isDeclaredConst = false;
|
||||
VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
|
||||
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
||||
Visibility visibility(Visibility::Default);
|
||||
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
||||
@ -731,7 +731,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
if (_options.allowIndexed && token == Token::Indexed)
|
||||
isIndexed = true;
|
||||
else if (token == Token::Constant)
|
||||
isDeclaredConst = true;
|
||||
constantness = VariableDeclaration::Constantness::Constant;
|
||||
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
||||
{
|
||||
if (location != VariableDeclaration::Location::Unspecified)
|
||||
@ -790,7 +790,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
visibility,
|
||||
_options.isStateVariable,
|
||||
isIndexed,
|
||||
isDeclaredConst,
|
||||
constantness,
|
||||
overrides,
|
||||
location
|
||||
);
|
||||
|
@ -40,6 +40,21 @@ bytes varintEncoding(size_t _n)
|
||||
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)
|
||||
{
|
||||
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
|
||||
@ -53,36 +68,132 @@ string base58Encode(bytes const& _data)
|
||||
reverse(output.begin(), output.end());
|
||||
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)
|
||||
{
|
||||
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;
|
||||
// Type: File
|
||||
protobufEncodedData += bytes{0x08, 0x02};
|
||||
if (!_data.empty())
|
||||
for (unsigned long chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
|
||||
{
|
||||
// Data (length delimited bytes)
|
||||
protobufEncodedData += bytes{0x12};
|
||||
protobufEncodedData += lengthAsVarint;
|
||||
protobufEncodedData += asBytes(std::move(_data));
|
||||
bytes chunkBytes = asBytes(
|
||||
_data.substr(chunkIndex * maxChunkSize, min(maxChunkSize, _data.length() - chunkIndex * maxChunkSize))
|
||||
);
|
||||
|
||||
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:
|
||||
// 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;
|
||||
return groupChunksBottomUp(std::move(allChunks));
|
||||
}
|
||||
|
||||
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/SideEffects.h>
|
||||
#include <libyul/ControlFlowSideEffects.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
@ -42,6 +43,7 @@ struct BuiltinFunction
|
||||
std::vector<Type> parameters;
|
||||
std::vector<Type> returns;
|
||||
SideEffects sideEffects;
|
||||
ControlFlowSideEffects controlFlowSideEffects;
|
||||
/// If true, this is the msize instruction.
|
||||
bool isMSize = false;
|
||||
/// 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.returns.resize(info.ret);
|
||||
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.literalArguments = false;
|
||||
f.instruction = _instruction;
|
||||
|
@ -99,6 +99,8 @@ WasmDialect::WasmDialect()
|
||||
addFunction("unreachable", {}, {}, false);
|
||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = 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("dataoffset", {i64}, {i64}, true, true);
|
||||
@ -147,7 +149,13 @@ void WasmDialect::addEthereumExternals()
|
||||
static string const i64{"i64"};
|
||||
static string const i32{"i32"};
|
||||
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{
|
||||
{"getAddress", {i32ptr}, {}},
|
||||
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
||||
@ -175,11 +183,11 @@ void WasmDialect::addEthereumExternals()
|
||||
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||
{"getBlockNumber", {}, {i64}},
|
||||
{"getTxOrigin", {i32ptr}, {}},
|
||||
{"finish", {i32ptr, i32}, {}},
|
||||
{"revert", {i32ptr, i32}, {}},
|
||||
{"finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
|
||||
{"revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
|
||||
{"getReturnDataSize", {}, {i32}},
|
||||
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||
{"selfDestruct", {i32ptr}, {}},
|
||||
{"selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
|
||||
{"getBlockTimestamp", {}, {i64}}
|
||||
};
|
||||
for (External const& ext: externals)
|
||||
@ -193,6 +201,7 @@ void WasmDialect::addEthereumExternals()
|
||||
f.returns.emplace_back(YulString(p));
|
||||
// TODO some of them are side effect free.
|
||||
f.sideEffects = SideEffects::worst();
|
||||
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||
f.isMSize = false;
|
||||
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
||||
f.literalArguments = false;
|
||||
|
@ -10,6 +10,7 @@ SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
|
||||
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
|
||||
|
||||
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
|
||||
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
|
||||
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
|
||||
|
||||
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
|
||||
@ -75,7 +76,7 @@ echo "Looking at $NSOURCES .sol files..."
|
||||
WORKINGDIR=$PWD
|
||||
|
||||
# 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
|
||||
echo -n "."
|
||||
# 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_strYulDialect = "yul-dialect";
|
||||
static string const g_strIR = "ir";
|
||||
static string const g_strIROptimized = "ir-optimized";
|
||||
static string const g_strIPFS = "ipfs";
|
||||
static string const g_strLicense = "license";
|
||||
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_argYul = g_strYul;
|
||||
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_argLibraries = g_strLibraries;
|
||||
static string const g_argLink = g_strLink;
|
||||
@ -336,36 +338,50 @@ void CommandLineInterface::handleOpcode(string const& _contract)
|
||||
|
||||
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))
|
||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
|
||||
else
|
||||
{
|
||||
sout() << "IR:" << endl;
|
||||
sout() << m_compiler->yulIR(_contractName) << endl;
|
||||
}
|
||||
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)
|
||||
{
|
||||
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(
|
||||
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
||||
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sout() << "Ewasm text:" << endl;
|
||||
sout() << m_compiler->ewasm(_contractName) << endl;
|
||||
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
|
||||
}
|
||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
|
||||
createFile(
|
||||
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
||||
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sout() << "Ewasm text:" << 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_argAbi.c_str(), "ABI specification of the contracts.")
|
||||
(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_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
|
||||
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
|
||||
@ -1123,7 +1140,7 @@ bool CommandLineInterface::processInput()
|
||||
m_compiler->setRevertStringBehaviour(m_revertStrings);
|
||||
// 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));
|
||||
|
||||
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||
@ -1631,6 +1648,7 @@ void CommandLineInterface::outputCompilationResults()
|
||||
|
||||
handleBytecode(contract);
|
||||
handleIR(contract);
|
||||
handleIROptimized(contract);
|
||||
handleEwasm(contract);
|
||||
handleSignatureHashes(contract);
|
||||
handleMetadata(contract);
|
||||
|
@ -65,6 +65,7 @@ private:
|
||||
void handleBinary(std::string const& _contract);
|
||||
void handleOpcode(std::string const& _contract);
|
||||
void handleIR(std::string const& _contract);
|
||||
void handleIROptimized(std::string const& _contract);
|
||||
void handleEwasm(std::string const& _contract);
|
||||
void handleBytecode(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();
|
||||
else if (solidity::test::CommonOptions::get().optimize)
|
||||
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
|
||||
m_evmHost->reset();
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void ExecutionFramework::reset()
|
||||
{
|
||||
m_evmHost->reset();
|
||||
for (size_t i = 0; i < 10; i++)
|
||||
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
|
||||
EVMHost::convertToEVMC(u256(1) << 100);
|
||||
|
||||
}
|
||||
|
||||
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(
|
||||
|
@ -251,6 +251,8 @@ private:
|
||||
}
|
||||
|
||||
protected:
|
||||
void reset();
|
||||
|
||||
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
|
||||
void sendEther(Address const& _to, u256 const& _value);
|
||||
size_t currentTimestamp();
|
||||
|
@ -15,6 +15,7 @@
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <test/Common.h>
|
||||
#include <test/TestCase.h>
|
||||
|
||||
#include <libsolutil/StringUtils.h>
|
||||
@ -52,14 +53,18 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
|
||||
!boost::starts_with(_filename.string(), ".");
|
||||
}
|
||||
|
||||
bool TestCase::validateSettings(langutil::EVMVersion)
|
||||
void TestCase::validateSettings()
|
||||
{
|
||||
if (!m_settings.empty())
|
||||
throw runtime_error(
|
||||
"Unknown setting(s): " +
|
||||
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)
|
||||
@ -157,20 +162,19 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
|
||||
++_it;
|
||||
}
|
||||
|
||||
bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion)
|
||||
void EVMVersionRestrictedTestCase::validateSettings()
|
||||
{
|
||||
if (!m_settings.count("EVMVersion"))
|
||||
return true;
|
||||
return;
|
||||
|
||||
string versionString = m_settings["EVMVersion"];
|
||||
m_validatedSettings["EVMVersion"] = versionString;
|
||||
m_settings.erase("EVMVersion");
|
||||
|
||||
if (!TestCase::validateSettings(_evmVersion))
|
||||
return false;
|
||||
TestCase::validateSettings();
|
||||
|
||||
if (versionString.empty())
|
||||
return true;
|
||||
return;
|
||||
|
||||
string comparator;
|
||||
size_t versionBegin = 0;
|
||||
@ -188,18 +192,23 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer
|
||||
if (!version)
|
||||
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
|
||||
|
||||
langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
|
||||
bool comparisonResult;
|
||||
if (comparator == ">")
|
||||
return _evmVersion > version;
|
||||
comparisonResult = evmVersion > version;
|
||||
else if (comparator == ">=")
|
||||
return _evmVersion >= version;
|
||||
comparisonResult = evmVersion >= version;
|
||||
else if (comparator == "<")
|
||||
return _evmVersion < version;
|
||||
comparisonResult = evmVersion < version;
|
||||
else if (comparator == "<=")
|
||||
return _evmVersion <= version;
|
||||
comparisonResult = evmVersion <= version;
|
||||
else if (comparator == "=")
|
||||
return _evmVersion == version;
|
||||
comparisonResult = evmVersion == version;
|
||||
else if (comparator == "!")
|
||||
return !(_evmVersion == version);
|
||||
comparisonResult = !(evmVersion == version);
|
||||
else
|
||||
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.
|
||||
/// 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
|
||||
/// otherwise which causes this test to be skipped.
|
||||
/// This might check e.g. for restrictions on the EVM version.
|
||||
virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/);
|
||||
bool shouldRun();
|
||||
|
||||
protected:
|
||||
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;
|
||||
/// Updated settings after validation.
|
||||
std::map<std::string, std::string> m_validatedSettings;
|
||||
|
||||
bool m_shouldRun = true;
|
||||
};
|
||||
|
||||
class EVMVersionRestrictedTestCase: public TestCase
|
||||
{
|
||||
public:
|
||||
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise.
|
||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
||||
void validateSettings() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -94,7 +94,8 @@ int registerTests(
|
||||
{
|
||||
stringstream errorStream;
|
||||
auto testCase = _testCaseCreator(config);
|
||||
if (testCase->validateSettings(solidity::test::CommonOptions::get().evmVersion()))
|
||||
testCase->validateSettings();
|
||||
if (testCase->shouldRun())
|
||||
switch (testCase->run(errorStream))
|
||||
{
|
||||
case TestCase::TestResult::Success:
|
||||
|
@ -77,6 +77,11 @@ function compileFull()
|
||||
expect_output=1
|
||||
shift;
|
||||
fi
|
||||
if [[ $1 = '-o' ]]
|
||||
then
|
||||
expect_output=2
|
||||
shift;
|
||||
fi
|
||||
|
||||
local files="$*"
|
||||
local output
|
||||
@ -93,7 +98,7 @@ function compileFull()
|
||||
if [[ \
|
||||
"$exit_code" -ne "$expected_exit_code" || \
|
||||
( $expect_output -eq 0 && -n "$errors" ) || \
|
||||
( $expect_output -ne 0 && -z "$errors" ) \
|
||||
( $expect_output -eq 1 && -z "$errors" ) \
|
||||
]]
|
||||
then
|
||||
printError "Unexpected compilation result:"
|
||||
@ -350,6 +355,10 @@ SOLTMPDIR=$(mktemp -d)
|
||||
then
|
||||
opts="$opts -w"
|
||||
fi
|
||||
if grep "This may report a warning" "$f" >/dev/null
|
||||
then
|
||||
opts="$opts -o"
|
||||
fi
|
||||
compileFull $opts "$SOLTMPDIR/$f"
|
||||
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/Common.h>
|
||||
|
||||
#include <libsolidity/formal/ModelChecker.h>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
@ -50,6 +54,9 @@ SMTCheckerJSONTest::SMTCheckerJSONTest(string const& _filename, langutil::EVMVer
|
||||
!m_smtResponses.isObject()
|
||||
)
|
||||
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)
|
||||
|
@ -44,6 +44,15 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _ev
|
||||
}
|
||||
else
|
||||
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)
|
||||
@ -55,17 +64,3 @@ TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePr
|
||||
|
||||
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;
|
||||
|
||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
||||
|
||||
protected:
|
||||
/// This is set via option SMTSolvers in the test.
|
||||
/// The possible options are `all`, `z3`, `cvc4`, `none`,
|
||||
|
@ -71,6 +71,9 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
|
||||
m_settings.erase("ABIEncoderV1Only");
|
||||
}
|
||||
|
||||
if (m_runWithABIEncoderV1Only && solidity::test::CommonOptions::get().useABIEncoderV2)
|
||||
m_shouldRun = false;
|
||||
|
||||
if (m_settings.count("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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for(bool compileViaYul: set<bool>{!m_runWithoutYul, m_runWithYul})
|
||||
{
|
||||
reset();
|
||||
bool success = true;
|
||||
|
||||
m_compileViaYul = compileViaYul;
|
||||
|
@ -44,8 +44,6 @@ public:
|
||||
|
||||
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;
|
||||
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;
|
||||
|
@ -540,7 +540,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
|
||||
"default",
|
||||
"define",
|
||||
"final",
|
||||
"immutable",
|
||||
"implements",
|
||||
"in",
|
||||
"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) {
|
||||
// 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;
|
||||
} catch {
|
||||
return 2;
|
||||
|
@ -4,7 +4,7 @@ contract C {
|
||||
}
|
||||
function f(bool x) public returns (uint) {
|
||||
// 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;
|
||||
} catch {
|
||||
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;
|
||||
return x;
|
||||
}
|
||||
function g(bool a) public returns (bool) {
|
||||
function g() public returns (bool) {
|
||||
bool b;
|
||||
if (a) {
|
||||
x = 0;
|
||||
b = (f() == 0) && (f() == 0);
|
||||
assert(x == 1);
|
||||
assert(!b);
|
||||
} else {
|
||||
x = 100;
|
||||
b = (f() > 0) && (f() > 0);
|
||||
b = f() > 0;
|
||||
assert(x == 102);
|
||||
// Should fail.
|
||||
assert(!b);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// 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
|
||||
|
@ -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: (342-347): Overflow (resulting value larger than 2**256 - 1) 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: (342-347): Overflow (resulting value larger than 2**256 - 1) 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: (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: (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
|
||||
|
@ -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: (356-370): Assertion violation happens here
|
||||
|
@ -14,3 +14,4 @@ contract A is C {
|
||||
}
|
||||
// ----
|
||||
// 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
|
||||
{
|
||||
uint x;
|
||||
uint y;
|
||||
uint z;
|
||||
|
||||
function f() public {
|
||||
if (x == 1)
|
||||
x = 2;
|
||||
else
|
||||
x = 1;
|
||||
g();
|
||||
if (y != 1)
|
||||
g();
|
||||
assert(y == 1);
|
||||
}
|
||||
|
||||
function g() public {
|
||||
function g() internal {
|
||||
y = 1;
|
||||
h();
|
||||
assert(z == 1);
|
||||
}
|
||||
|
||||
function h() public {
|
||||
z = 1;
|
||||
x = 1;
|
||||
function h() internal {
|
||||
f();
|
||||
// This fails for the following calls to the contract:
|
||||
// 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);
|
||||
assert(y == 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: (111-114): Assertion checker does not support recursive function calls.
|
||||
// Warning: (130-144): Error trying to invoke SMT solver.
|
||||
|
@ -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