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

This commit is contained in:
chriseth 2020-03-11 18:44:06 +01:00
commit 9d92c9fbf1
198 changed files with 2907 additions and 761 deletions

View File

@ -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.
-->

View File

@ -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

View File

@ -9,20 +9,37 @@ Compiler Features:
Bugfixes: Bugfixes:
### 0.6.4 (unreleased) ### 0.6.5 (unreleased)
Language Features: Language Features:
Compiler Features:
Bugfixes:
* Metadata: Added support for IPFS hashes of large files that need to be split in multiple chunks.
### 0.6.4 (2020-03-10)
Language Features:
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers. * Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
Compiler Features: Compiler Features:
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources. * AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
* Commandline Interface: Enable output of experimental optimized IR via ``--ir-optimized``.
Bugfixes: Bugfixes:
* isoltest: Added new keyword `wei` to express function value in semantic tests * Inheritance: Fix incorrect error on calling unimplemented base functions.
* Reference Resolver: Fix scoping issue following try/catch statements.
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports. * Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
* SMTChecker: Fix internal errors when analysing tuples.
* Yul AST Import: correctly import blocks as statements, switch statements and string literals.
### 0.6.3 (2020-02-18) ### 0.6.3 (2020-02-18)
@ -37,6 +54,7 @@ Compiler Features:
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input. * Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode. * Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced. * Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
* SMTChecker: CHC support to internal function calls.
Bugfixes: Bugfixes:

View File

@ -11,7 +11,7 @@ You can interleave Solidity statements with inline assembly in a language close
to the one of the Ethereum virtual machine. This gives you more fine-grained control, to the one of the Ethereum virtual machine. This gives you more fine-grained control,
which is especially useful when you are enhancing the language by writing libraries. which is especially useful when you are enhancing the language by writing libraries.
The language used for inline assembly in Solidity is called `Yul <yul>`_ The language used for inline assembly in Solidity is called :ref:`Yul <yul>`
and it is documented in its own section. This section will only cover and it is documented in its own section. This section will only cover
how the inline assembly code can interface with the surrounding Solidity code. how the inline assembly code can interface with the surrounding Solidity code.
@ -24,7 +24,7 @@ how the inline assembly code can interface with the surrounding Solidity code.
An inline assembly block is marked by ``assembly { ... }``, where the code inside An inline assembly block is marked by ``assembly { ... }``, where the code inside
the curly braces is code in the `Yul <yul>`_ language. the curly braces is code in the :ref:`Yul <yul>` language.
The inline assembly code can access local Solidity variables as explained below. The inline assembly code can access local Solidity variables as explained below.

View File

@ -888,5 +888,9 @@
"0.6.3": { "0.6.3": {
"bugs": [], "bugs": [],
"released": "2020-02-18" "released": "2020-02-18"
},
"0.6.4": {
"bugs": [],
"released": "2020-03-10"
} }
} }

View File

@ -252,7 +252,7 @@ which only need to be created if there is a dispute.
/// This complicated expression just tells you how the address /// This complicated expression just tells you how the address
/// can be pre-computed. It is just there for illustration. /// can be pre-computed. It is just there for illustration.
/// You actually only need ``new D{salt: salt}(arg)``. /// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(bytes20(keccak256(abi.encodePacked( address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff), byte(0xff),
address(this), address(this),
salt, salt,

View File

@ -547,6 +547,7 @@ not mean loss of proving power.
pragma solidity >=0.5.0; pragma solidity >=0.5.0;
pragma experimental SMTChecker; pragma experimental SMTChecker;
// This may report a warning if no SMT solver available.
contract Recover contract Recover
{ {
@ -601,6 +602,7 @@ types.
pragma solidity >=0.5.0; pragma solidity >=0.5.0;
pragma experimental SMTChecker; pragma experimental SMTChecker;
// This will report a warning // This will report a warning
contract Aliasing contract Aliasing
{ {
uint[] array; uint[] array;

View File

@ -646,7 +646,7 @@ External (or public) functions have the following members:
Example that shows how to use the members:: Example that shows how to use the members::
pragma solidity >=0.4.16 <0.8.0; pragma solidity >=0.4.16 <0.8.0;
// This will report a warning
contract Example { contract Example {
function f() public payable returns (bytes4) { function f() public payable returns (bytes4) {

View File

@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
} }
} }
bool SemanticInformation::reverts(AssemblyItem const& _item)
{
if (_item.type() != Operation)
return false;
else
return reverts(_item.instruction());
}
bool SemanticInformation::reverts(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::INVALID:
case Instruction::REVERT:
return true;
default:
return false;
}
}
bool SemanticInformation::isDeterministic(AssemblyItem const& _item) bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
{ {
if (_item.type() != Operation) if (_item.type() != Operation)

View File

@ -47,6 +47,8 @@ struct SemanticInformation
static bool altersControlFlow(AssemblyItem const& _item); static bool altersControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(AssemblyItem const& _item); static bool terminatesControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(Instruction _instruction); static bool terminatesControlFlow(Instruction _instruction);
static bool reverts(AssemblyItem const& _item);
static bool reverts(Instruction _instruction);
/// @returns false if the value put on the stack by _item depends on anything else than /// @returns false if the value put on the stack by _item depends on anything else than
/// the information in the current block header, memory, storage or stack. /// the information in the current block header, memory, storage or stack.
static bool isDeterministic(AssemblyItem const& _item); static bool isDeterministic(AssemblyItem const& _item);

View File

@ -166,6 +166,7 @@ namespace solidity::langutil
K(Indexed, "indexed", 0) \ K(Indexed, "indexed", 0) \
K(Interface, "interface", 0) \ K(Interface, "interface", 0) \
K(Internal, "internal", 0) \ K(Internal, "internal", 0) \
K(Immutable, "immutable", 0) \
K(Import, "import", 0) \ K(Import, "import", 0) \
K(Is, "is", 0) \ K(Is, "is", 0) \
K(Library, "library", 0) \ K(Library, "library", 0) \
@ -243,7 +244,6 @@ namespace solidity::langutil
K(Default, "default", 0) \ K(Default, "default", 0) \
K(Define, "define", 0) \ K(Define, "define", 0) \
K(Final, "final", 0) \ K(Final, "final", 0) \
K(Immutable, "immutable", 0) \
K(Implements, "implements", 0) \ K(Implements, "implements", 0) \
K(In, "in", 0) \ K(In, "in", 0) \
K(Inline, "inline", 0) \ K(Inline, "inline", 0) \

View File

@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
{ {
auto const& functionFlow = m_cfg.functionFlow(_function); auto const& functionFlow = m_cfg.functionFlow(_function);
checkUninitializedAccess(functionFlow.entry, functionFlow.exit); checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert); checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
} }
return false; return false;
} }
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
m_errorReporter.typeError( m_errorReporter.typeError(
variableOccurrence->occurrence() ? variableOccurrence->occurrence() ?
variableOccurrence->occurrence()->location() : *variableOccurrence->occurrence() :
variableOccurrence->declaration().location(), variableOccurrence->declaration().location(),
ssl, ssl,
string("This variable is of storage pointer type and can be ") + string("This variable is of storage pointer type and can be ") +
@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
} }
} }
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
{ {
// collect all nodes reachable from the entry point // collect all nodes reachable from the entry point
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run( std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
} }
).visited; ).visited;
// traverse all paths backwards from exit and revert // traverse all paths backwards from exit, revert and transaction return
// and extract (valid) source locations of unreachable nodes into sorted set // and extract (valid) source locations of unreachable nodes into sorted set
std::set<SourceLocation> unreachable; std::set<SourceLocation> unreachable;
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run( util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
[&](CFGNode const* _node, auto&& _addChild) { [&](CFGNode const* _node, auto&& _addChild) {
if (!reachable.count(_node) && _node->location.isValid()) if (!reachable.count(_node) && _node->location.isValid())
unreachable.insert(_node->location); unreachable.insert(_node->location);

View File

@ -36,9 +36,9 @@ public:
private: private:
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit. /// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const; void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert /// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
/// that can not be reached from @param _entry. /// that can not be reached from @param _entry.
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const; void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
CFG const& m_cfg; CFG const& m_cfg;
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;

View File

@ -16,6 +16,8 @@
*/ */
#include <libsolidity/analysis/ControlFlowBuilder.h> #include <libsolidity/analysis/ControlFlowBuilder.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/EVMDialect.h>
using namespace solidity; using namespace solidity;
using namespace solidity::langutil; using namespace solidity::langutil;
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
m_nodeContainer(_nodeContainer), m_nodeContainer(_nodeContainer),
m_currentNode(_functionFlow.entry), m_currentNode(_functionFlow.entry),
m_returnNode(_functionFlow.exit), m_returnNode(_functionFlow.exit),
m_revertNode(_functionFlow.revert) m_revertNode(_functionFlow.revert),
m_transactionReturnNode(_functionFlow.transactionReturn)
{ {
} }
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow( unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer, CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function FunctionDefinition const& _function
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
functionFlow->entry = _nodeContainer.newNode(); functionFlow->entry = _nodeContainer.newNode();
functionFlow->exit = _nodeContainer.newNode(); functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode(); functionFlow->revert = _nodeContainer.newNode();
functionFlow->transactionReturn = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow); ControlFlowBuilder builder(_nodeContainer, *functionFlow);
builder.appendControlFlow(_function); builder.appendControlFlow(_function);
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
if (_forStatement.condition()) if (_forStatement.condition())
appendControlFlow(*_forStatement.condition()); appendControlFlow(*_forStatement.condition());
auto loopExpression = newLabel(); auto postPart = newLabel();
auto nodes = splitFlow<2>(); auto nodes = splitFlow<2>();
auto afterFor = nodes[1]; auto afterFor = nodes[1];
m_currentNode = nodes[0]; m_currentNode = nodes[0];
{ {
BreakContinueScope scope(*this, afterFor, loopExpression); BreakContinueScope scope(*this, afterFor, postPart);
appendControlFlow(_forStatement.body()); appendControlFlow(_forStatement.body());
} }
placeAndConnectLabel(loopExpression); placeAndConnectLabel(postPart);
if (auto expression = _forStatement.loopExpression()) if (auto expression = _forStatement.loopExpression())
appendControlFlow(*expression); appendControlFlow(*expression);
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
appendControlFlow(*returnParameter); appendControlFlow(*returnParameter);
m_returnNode->variableOccurrences.emplace_back( m_returnNode->variableOccurrences.emplace_back(
*returnParameter, *returnParameter,
VariableOccurrence::Kind::Return, VariableOccurrence::Kind::Return
nullptr
); );
} }
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
*returnParameter, *returnParameter,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment,
&_return _return.location()
); );
} }
connect(m_currentNode, m_returnNode); connect(m_currentNode, m_returnNode);
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
{ {
solAssert(!!m_currentNode, ""); solAssert(!!m_currentNode && !m_inlineAssembly, "");
visitNode(_inlineAssembly);
for (auto const& ref: _inlineAssembly.annotation().externalReferences) m_inlineAssembly = &_inlineAssembly;
(*this)(_inlineAssembly.operations());
m_inlineAssembly = nullptr;
return false;
}
void ControlFlowBuilder::visit(yul::Statement const& _statement)
{
solAssert(m_currentNode && m_inlineAssembly, "");
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
ASTWalker::visit(_statement);
}
void ControlFlowBuilder::operator()(yul::If const& _if)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_if.condition);
auto nodes = splitFlow<2>();
m_currentNode = nodes[0];
(*this)(_if.body);
nodes[0] = m_currentNode;
mergeFlow(nodes, nodes[1]);
}
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_switch.expression);
auto beforeSwitch = m_currentNode;
auto nodes = splitFlow(_switch.cases.size());
for (size_t i = 0u; i < _switch.cases.size(); ++i)
{ {
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) m_currentNode = nodes[i];
(*this)(_switch.cases[i].body);
nodes[i] = m_currentNode;
}
mergeFlow(nodes);
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
if (!hasDefault)
connect(beforeSwitch, m_currentNode);
}
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
{
solAssert(m_currentNode && m_inlineAssembly, "");
(*this)(_forLoop.pre);
auto condition = createLabelHere();
if (_forLoop.condition)
visit(*_forLoop.condition);
auto loopExpression = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
(*this)(_forLoop.body);
}
placeAndConnectLabel(loopExpression);
(*this)(_forLoop.post);
connect(m_currentNode, condition);
m_currentNode = afterFor;
}
void ControlFlowBuilder::operator()(yul::Break const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_breakJump, "");
connect(m_currentNode, m_breakJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Continue const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_continueJump, "");
connect(m_currentNode, m_continueJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
{
solAssert(m_currentNode && m_inlineAssembly, "");
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
if (externalReferences.count(&_identifier))
{
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
*variableDeclaration, *declaration,
VariableOccurrence::Kind::InlineAssembly, VariableOccurrence::Kind::Access,
&_inlineAssembly _identifier.location
); );
} }
return true; }
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_assignment.value);
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
for (auto const& variable: _assignment.variableNames)
if (externalReferences.count(&variable))
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
m_currentNode->variableOccurrences.emplace_back(
*declaration,
VariableOccurrence::Kind::Assignment,
variable.location
);
}
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
{
using namespace yul;
solAssert(m_currentNode && m_inlineAssembly, "");
yul::ASTWalker::operator()(_functionCall);
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
if (builtinFunction->controlFlowSideEffects.terminates)
{
if (builtinFunction->controlFlowSideEffects.reverts)
connect(m_currentNode, m_revertNode);
else
connect(m_currentNode, m_transactionReturnNode);
m_currentNode = newLabel();
}
}
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
// External references cannot be accessed from within functions, so we can ignore their control flow.
// TODO: we might still want to track if they always revert or return, though.
}
void ControlFlowBuilder::operator()(yul::Leave const&)
{
// This has to be implemented, if we ever decide to visit functions.
solUnimplementedAssert(false, "");
} }
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration, _variableDeclaration,
VariableOccurrence::Kind::Declaration, VariableOccurrence::Kind::Declaration
nullptr
); );
// Handle declaration with immediate assignment. // Handle declaration with immediate assignment.
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration, _variableDeclaration,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment,
_variableDeclaration.value().get() _variableDeclaration.value()->location()
); );
// Function arguments are considered to be immediately assigned as well (they are "externally assigned"). // Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter()) else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration, _variableDeclaration,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment
nullptr
); );
return true; return true;
} }
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
*var, *var,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment,
expression expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
); );
} }
} }
@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
static_cast<Expression const&>(_identifier).annotation().lValueRequested ? static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
VariableOccurrence::Kind::Assignment : VariableOccurrence::Kind::Assignment :
VariableOccurrence::Kind::Access, VariableOccurrence::Kind::Access,
&_identifier _identifier.location()
); );
return true; return true;

View File

@ -20,6 +20,7 @@
#include <libsolidity/analysis/ControlFlowGraph.h> #include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libyul/optimiser/ASTWalker.h>
#include <array> #include <array>
#include <memory> #include <memory>
@ -30,7 +31,7 @@ namespace solidity::frontend {
* Modifiers are not yet applied to the functions. This is done in a second * Modifiers are not yet applied to the functions. This is done in a second
* step in the CFG class. * step in the CFG class.
*/ */
class ControlFlowBuilder: private ASTConstVisitor class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
{ {
public: public:
static std::unique_ptr<FunctionFlow> createFunctionFlow( static std::unique_ptr<FunctionFlow> createFunctionFlow(
@ -39,7 +40,10 @@ public:
); );
private: private:
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow); explicit ControlFlowBuilder(
CFG::NodeContainer& _nodeContainer,
FunctionFlow const& _functionFlow
);
// Visits for constructing the control flow. // Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override; bool visit(BinaryOperation const& _operation) override;
@ -62,6 +66,17 @@ private:
// Visits for filling variable occurrences. // Visits for filling variable occurrences.
bool visit(FunctionTypeName const& _functionTypeName) override; bool visit(FunctionTypeName const& _functionTypeName) override;
bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(InlineAssembly const& _inlineAssembly) override;
void visit(yul::Statement const& _statement) override;
void operator()(yul::If const& _if) override;
void operator()(yul::Switch const& _switch) override;
void operator()(yul::ForLoop const& _for) override;
void operator()(yul::Break const&) override;
void operator()(yul::Continue const&) override;
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::Assignment const& _assignment) override;
void operator()(yul::FunctionCall const& _functionCall) override;
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
void operator()(yul::Leave const& _leaveStatement) override;
bool visit(VariableDeclaration const& _variableDeclaration) override; bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
bool visit(Identifier const& _identifier) override; bool visit(Identifier const& _identifier) override;
@ -70,6 +85,9 @@ protected:
bool visitNode(ASTNode const&) override; bool visitNode(ASTNode const&) override;
private: private:
using ASTConstVisitor::visit;
using yul::ASTWalker::visit;
using yul::ASTWalker::operator();
/// Appends the control flow of @a _node to the current control flow. /// Appends the control flow of @a _node to the current control flow.
void appendControlFlow(ASTNode const& _node); void appendControlFlow(ASTNode const& _node);
@ -136,6 +154,7 @@ private:
CFGNode* m_currentNode = nullptr; CFGNode* m_currentNode = nullptr;
CFGNode* m_returnNode = nullptr; CFGNode* m_returnNode = nullptr;
CFGNode* m_revertNode = nullptr; CFGNode* m_revertNode = nullptr;
CFGNode* m_transactionReturnNode = nullptr;
/// The current jump destination of break Statements. /// The current jump destination of break Statements.
CFGNode* m_breakJump = nullptr; CFGNode* m_breakJump = nullptr;
@ -145,6 +164,8 @@ private:
CFGNode* m_placeholderEntry = nullptr; CFGNode* m_placeholderEntry = nullptr;
CFGNode* m_placeholderExit = nullptr; CFGNode* m_placeholderExit = nullptr;
InlineAssembly const* m_inlineAssembly = nullptr;
/// Helper class that replaces the break and continue jump destinations for the /// Helper class that replaces the break and continue jump destinations for the
/// current scope and restores the originals at the end of the scope. /// current scope and restores the originals at the end of the scope.
class BreakContinueScope class BreakContinueScope

View File

@ -20,6 +20,7 @@
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <map> #include <map>
@ -33,8 +34,8 @@ namespace solidity::frontend
/** /**
* Occurrence of a variable in a block of control flow. * Occurrence of a variable in a block of control flow.
* Stores the declaration of the referenced variable, the * Stores the declaration of the referenced variable, the
* kind of the occurrence and possibly the node at which * kind of the occurrence and possibly the source location
* it occurred. * at which it occurred.
*/ */
class VariableOccurrence class VariableOccurrence
{ {
@ -47,7 +48,7 @@ public:
Assignment, Assignment,
InlineAssembly InlineAssembly
}; };
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence): VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence) m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
{ {
} }
@ -57,8 +58,8 @@ public:
{ {
if (m_occurrence && _rhs.m_occurrence) if (m_occurrence && _rhs.m_occurrence)
{ {
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true; if (*m_occurrence < *_rhs.m_occurrence) return true;
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false; if (*_rhs.m_occurrence < *m_occurrence) return false;
} }
else if (_rhs.m_occurrence) else if (_rhs.m_occurrence)
return true; return true;
@ -74,14 +75,14 @@ public:
VariableDeclaration const& declaration() const { return m_declaration; } VariableDeclaration const& declaration() const { return m_declaration; }
Kind kind() const { return m_occurrenceKind; }; Kind kind() const { return m_occurrenceKind; };
ASTNode const* occurrence() const { return m_occurrence; } std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
private: private:
/// Declaration of the occurring variable. /// Declaration of the occurring variable.
VariableDeclaration const& m_declaration; VariableDeclaration const& m_declaration;
/// Kind of occurrence. /// Kind of occurrence.
Kind m_occurrenceKind = Kind::Access; Kind m_occurrenceKind = Kind::Access;
/// AST node at which the variable occurred, if available (may be nullptr). /// Source location at which the variable occurred, if available (may be nullptr).
ASTNode const* m_occurrence = nullptr; std::optional<langutil::SourceLocation> m_occurrence;
}; };
/** /**
@ -119,6 +120,10 @@ struct FunctionFlow
/// This node is empty does not have any exits, but may have multiple entries /// This node is empty does not have any exits, but may have multiple entries
/// (e.g. all assert, require, revert and throw statements). /// (e.g. all assert, require, revert and throw statements).
CFGNode* revert = nullptr; CFGNode* revert = nullptr;
/// Transaction return node. Destination node for inline assembly "return" calls.
/// This node is empty and does not have any exits, but may have multiple entries
/// (e.g. all inline assembly return calls).
CFGNode* transactionReturn = nullptr;
}; };
class CFG: private ASTConstVisitor class CFG: private ASTConstVisitor
@ -140,7 +145,6 @@ public:
std::vector<std::unique_ptr<CFGNode>> m_nodes; std::vector<std::unique_ptr<CFGNode>> m_nodes;
}; };
private: private:
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;
/// Node container. /// Node container.

View File

@ -67,6 +67,22 @@ void ReferencesResolver::endVisit(Block const& _block)
m_resolver.setScope(_block.scope()); m_resolver.setScope(_block.scope());
} }
bool ReferencesResolver::visit(TryCatchClause const& _tryCatchClause)
{
if (!m_resolveInsideCode)
return false;
m_resolver.setScope(&_tryCatchClause);
return true;
}
void ReferencesResolver::endVisit(TryCatchClause const& _tryCatchClause)
{
if (!m_resolveInsideCode)
return;
m_resolver.setScope(_tryCatchClause.scope());
}
bool ReferencesResolver::visit(ForStatement const& _for) bool ReferencesResolver::visit(ForStatement const& _for)
{ {
if (!m_resolveInsideCode) if (!m_resolveInsideCode)

View File

@ -70,6 +70,8 @@ private:
bool visit(Block const& _block) override; bool visit(Block const& _block) override;
void endVisit(Block const& _block) override; void endVisit(Block const& _block) override;
bool visit(TryCatchClause const& _tryCatchClause) override;
void endVisit(TryCatchClause const& _tryCatchClause) override;
bool visit(ForStatement const& _for) override; bool visit(ForStatement const& _for) override;
void endVisit(ForStatement const& _for) override; void endVisit(ForStatement const& _for) override;
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override; void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;

View File

@ -1703,22 +1703,21 @@ void TypeChecker::typeCheckFunctionCall(
if (_functionType->kind() == FunctionType::Kind::Declaration) if (_functionType->kind() == FunctionType::Kind::Declaration)
{ {
m_errorReporter.typeError( if (
_functionCall.location(), m_scope->derivesFrom(*_functionType->declaration().annotation().contract) &&
"Cannot call function via contract type name." !dynamic_cast<FunctionDefinition const&>(_functionType->declaration()).isImplemented()
); )
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call unimplemented base function."
);
else
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call function via contract type name."
);
return; return;
} }
if (_functionType->kind() == FunctionType::Kind::Internal && _functionType->hasDeclaration())
if (auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(&_functionType->declaration()))
// functionDefinition->annotation().contract != m_scope ensures that this is a qualified access,
// e.g. ``A.f();`` instead of a simple function call like ``f();`` (the latter is valid for unimplemented
// functions).
if (functionDefinition->annotation().contract != m_scope && !functionDefinition->isImplemented())
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call unimplemented base function."
);
// Check for unsupported use of bare static call // Check for unsupported use of bare static call
if ( if (
@ -2312,7 +2311,11 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
else if (!expressionFunctionType->isPayable()) else if (!expressionFunctionType->isPayable())
m_errorReporter.typeError( m_errorReporter.typeError(
_functionCallOptions.location(), _functionCallOptions.location(),
"Cannot set option \"value\" on a non-payable function type." kind == FunctionType::Kind::Creation ?
"Cannot set option \"value\", since the constructor of " +
expressionFunctionType->returnParameterTypes().front()->toString() +
" is not payable." :
"Cannot set option \"value\" on a non-payable function type."
); );
else else
{ {
@ -2522,12 +2525,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.type = possibleMembers.front().type; annotation.type = possibleMembers.front().type;
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type)) if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
{
solAssert( solAssert(
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), !funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
"Function \"" + memberName + "\" cannot be called on an object of type " + "Function \"" + memberName + "\" cannot be called on an object of type " +
exprType->toString() + " (expected " + funType->selfType()->toString() + ")." exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
); );
if (
dynamic_cast<FunctionType const*>(exprType) &&
!annotation.referencedDeclaration &&
(memberName == "value" || memberName == "gas")
)
m_errorReporter.warning(
_memberAccess.location(),
"Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead."
);
}
if (auto const* structType = dynamic_cast<StructType const*>(exprType)) if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array) else if (exprType->category() == Type::Category::Array)

View File

@ -390,7 +390,7 @@ DeclarationAnnotation& Declaration::annotation() const
bool VariableDeclaration::isLValue() const bool VariableDeclaration::isLValue() const
{ {
// Constant declared variables are Read-Only // Constant declared variables are Read-Only
if (m_isConstant) if (isConstant())
return false; return false;
// External function arguments of reference type are Read-Only // External function arguments of reference type are Read-Only
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type())) if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))

View File

@ -814,6 +814,7 @@ class VariableDeclaration: public Declaration
{ {
public: public:
enum Location { Unspecified, Storage, Memory, CallData }; enum Location { Unspecified, Storage, Memory, CallData };
enum class Constantness { Mutable, Immutable, Constant };
VariableDeclaration( VariableDeclaration(
int64_t _id, int64_t _id,
@ -824,7 +825,7 @@ public:
Visibility _visibility, Visibility _visibility,
bool _isStateVar = false, bool _isStateVar = false,
bool _isIndexed = false, bool _isIndexed = false,
bool _isConstant = false, Constantness _constantness = Constantness::Mutable,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr, ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
Location _referenceLocation = Location::Unspecified Location _referenceLocation = Location::Unspecified
): ):
@ -833,7 +834,7 @@ public:
m_value(_value), m_value(_value),
m_isStateVariable(_isStateVar), m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed), m_isIndexed(_isIndexed),
m_isConstant(_isConstant), m_constantness(_constantness),
m_overrides(_overrides), m_overrides(_overrides),
m_location(_referenceLocation) {} m_location(_referenceLocation) {}
@ -877,7 +878,7 @@ public:
bool hasReferenceOrMappingType() const; bool hasReferenceOrMappingType() const;
bool isStateVariable() const { return m_isStateVariable; } bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; } bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; } bool isConstant() const { return m_constantness == Constantness::Constant; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; } ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; } Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable. /// @returns a set of allowed storage locations for the variable.
@ -904,7 +905,8 @@ private:
ASTPointer<Expression> m_value; ASTPointer<Expression> m_value;
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events). bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
bool m_isConstant = false; ///< Whether the variable is a compile-time constant. /// Whether the variable is "constant", "immutable" or non-marked (mutable).
Constantness m_constantness = Constantness::Mutable;
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type. Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
}; };

View File

@ -411,6 +411,12 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
{ {
astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
VariableDeclaration::Constantness constantness{};
if (memberAsBool(_node, "constant"))
constantness = VariableDeclaration::Constantness::Constant;
else
constantness = VariableDeclaration::Constantness::Mutable;
return createASTNode<VariableDeclaration>( return createASTNode<VariableDeclaration>(
_node, _node,
nullOrCast<TypeName>(member(_node, "typeName")), nullOrCast<TypeName>(member(_node, "typeName")),
@ -419,7 +425,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
visibility(_node), visibility(_node),
memberAsBool(_node, "stateVariable"), memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
memberAsBool(_node, "constant"), constantness,
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
location(_node) location(_node)
); );

View File

@ -104,6 +104,8 @@ yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node)
return createContinue(_node); return createContinue(_node);
else if (nodeType == "Leave") else if (nodeType == "Leave")
return createLeave(_node); return createLeave(_node);
else if (nodeType == "Block")
return createBlock(_node);
else else
astAssert(false, "Invalid nodeType as statement"); astAssert(false, "Invalid nodeType as statement");
} }
@ -158,10 +160,9 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
lit.value = YulString{member(_node, "value").asString()}; lit.value = YulString{member(_node, "value").asString()};
lit.type= YulString{member(_node, "type").asString()}; lit.type= YulString{member(_node, "type").asString()};
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
if (kind == "number") if (kind == "number")
{ {
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
lit.kind = yul::LiteralKind::Number; lit.kind = yul::LiteralKind::Number;
astAssert( astAssert(
scanner.currentToken() == Token::Number, scanner.currentToken() == Token::Number,
@ -170,6 +171,7 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
} }
else if (kind == "bool") else if (kind == "bool")
{ {
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
lit.kind = yul::LiteralKind::Boolean; lit.kind = yul::LiteralKind::Boolean;
astAssert( astAssert(
scanner.currentToken() == Token::TrueLiteral || scanner.currentToken() == Token::TrueLiteral ||
@ -180,7 +182,10 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
else if (kind == "string") else if (kind == "string")
{ {
lit.kind = yul::LiteralKind::String; lit.kind = yul::LiteralKind::String;
astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!"); astAssert(
lit.value.str().size() <= 32,
"String literal too long (" + to_string(lit.value.str().size()) + " > 32)"
);
} }
else else
solAssert(false, "unknown type of literal"); solAssert(false, "unknown type of literal");
@ -268,7 +273,11 @@ yul::If AsmJsonImporter::createIf(Json::Value const& _node)
yul::Case AsmJsonImporter::createCase(Json::Value const& _node) yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
{ {
auto caseStatement = createAsmNode<yul::Case>(_node); auto caseStatement = createAsmNode<yul::Case>(_node);
caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique<yul::Literal>(createLiteral(member(_node, "value"))); auto const& value = member(_node, "value");
if (value.isString())
astAssert(value.asString() == "default", "Expected default case");
else
caseStatement.value = make_unique<yul::Literal>(createLiteral(value));
caseStatement.body = createBlock(member(_node, "body")); caseStatement.body = createBlock(member(_node, "body"));
return caseStatement; return caseStatement;
} }
@ -276,7 +285,7 @@ yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node) yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
{ {
auto switchStatement = createAsmNode<yul::Switch>(_node); auto switchStatement = createAsmNode<yul::Switch>(_node);
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "value"))); switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "expression")));
for (auto const& var: member(_node, "cases")) for (auto const& var: member(_node, "cases"))
switchStatement.cases.emplace_back(createCase(var)); switchStatement.cases.emplace_back(createCase(var));
return switchStatement; return switchStatement;

View File

@ -2924,7 +2924,10 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{ {
case Kind::External: case Kind::External:
case Kind::DelegateCall: case Kind::DelegateCall:
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))}; slots = {
make_tuple("address", TypeProvider::address()),
make_tuple("functionIdentifier", TypeProvider::uint(32))
};
break; break;
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
@ -3471,7 +3474,15 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
continue; continue;
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts()) if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
members.emplace_back(declaration->name(), declaration->type(), declaration); {
if (
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(declaration);
functionDefinition && !functionDefinition->isImplemented()
)
members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration);
else
members.emplace_back(declaration->name(), declaration->type(), declaration);
}
else if ( else if (
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) || (contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
declaration->isVisibleViaContractTypeAccess() declaration->isVisibleViaContractTypeAccess()

View File

@ -55,7 +55,7 @@ string ABIFunctions::tupleEncoder(
functionName += t->identifier() + "_"; functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix(); functionName += options.toFunctionNameSuffix();
return createExternallyUsedFunction(functionName, [&]() { return createFunction(functionName, [&]() {
// Note that the values are in reverse due to the difference in calling semantics. // Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(headStart <valueParams>) -> tail { function <functionName>(headStart <valueParams>) -> tail {
@ -121,7 +121,7 @@ string ABIFunctions::tupleEncoderPacked(
functionName += t->identifier() + "_"; functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix(); functionName += options.toFunctionNameSuffix();
return createExternallyUsedFunction(functionName, [&]() { return createFunction(functionName, [&]() {
solAssert(!_givenTypes.empty(), ""); solAssert(!_givenTypes.empty(), "");
// Note that the values are in reverse due to the difference in calling semantics. // Note that the values are in reverse due to the difference in calling semantics.
@ -173,7 +173,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
if (_fromMemory) if (_fromMemory)
functionName += "_fromMemory"; functionName += "_fromMemory";
return createExternallyUsedFunction(functionName, [&]() { return createFunction(functionName, [&]() {
TypePointers decodingTypes; TypePointers decodingTypes;
for (auto const& t: _types) for (auto const& t: _types)
decodingTypes.emplace_back(t->decodingType()); decodingTypes.emplace_back(t->decodingType());
@ -240,13 +240,6 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
}); });
} }
pair<string, set<string>> ABIFunctions::requestedFunctions()
{
std::set<string> empty;
swap(empty, m_externallyUsedFunctions);
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
}
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
{ {
string suffix; string suffix;
@ -1499,14 +1492,7 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator) string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
{ {
return m_functionCollector->createFunction(_name, _creator); return m_functionCollector.createFunction(_name, _creator);
}
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
{
string name = createFunction(_name, _creator);
m_externallyUsedFunctions.insert(name);
return name;
} }
size_t ABIFunctions::headSize(TypePointers const& _targetTypes) size_t ABIFunctions::headSize(TypePointers const& _targetTypes)

View File

@ -31,7 +31,6 @@
#include <functional> #include <functional>
#include <map> #include <map>
#include <set>
#include <vector> #include <vector>
namespace solidity::frontend namespace solidity::frontend
@ -58,11 +57,11 @@ public:
explicit ABIFunctions( explicit ABIFunctions(
langutil::EVMVersion _evmVersion, langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings, RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>() MultiUseYulFunctionCollector& _functionCollector
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)), m_functionCollector(_functionCollector),
m_utils(_evmVersion, m_revertStrings, m_functionCollector) m_utils(_evmVersion, m_revertStrings, m_functionCollector)
{} {}
@ -104,12 +103,6 @@ public:
/// stack slot, it takes exactly that number of values. /// stack slot, it takes exactly that number of values.
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
/// @returns concatenation of all generated functions and a set of the
/// externally used functions.
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedFunctions();
private: private:
struct EncodingOptions struct EncodingOptions
{ {
@ -239,11 +232,6 @@ private:
/// cases. /// cases.
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator); std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
/// Helper function that uses @a _creator to create a function and add it to
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
/// cases. Also adds it to the list of externally used functions.
std::string createExternallyUsedFunction(std::string const& _name, std::function<std::string()> const& _creator);
/// @returns the size of the static part of the encoding of the given types. /// @returns the size of the static part of the encoding of the given types.
static size_t headSize(TypePointers const& _targetTypes); static size_t headSize(TypePointers const& _targetTypes);
@ -259,8 +247,7 @@ private:
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings; RevertStrings const m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; MultiUseYulFunctionCollector& m_functionCollector;
std::set<std::string> m_externallyUsedFunctions;
YulUtilFunctions m_utils; YulUtilFunctions m_utils;
}; };

View File

@ -94,6 +94,20 @@ void CompilerContext::callLowLevelFunction(
*this << retTag.tag(); *this << retTag.tag();
} }
void CompilerContext::callYulFunction(
string const& _name,
unsigned _inArgs,
unsigned _outArgs
)
{
m_externallyUsedYulFunctions.insert(_name);
auto const retTag = pushNewTag();
CompilerUtils(*this).moveIntoStack(_inArgs);
appendJumpTo(namedTag(_name));
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
*this << retTag.tag();
}
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag( evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
string const& _name, string const& _name,
unsigned _inArgs, unsigned _inArgs,
@ -133,6 +147,13 @@ void CompilerContext::appendMissingLowLevelFunctions()
} }
} }
pair<string, set<string>> CompilerContext::requestedYulFunctions()
{
set<string> empty;
swap(empty, m_externallyUsedYulFunctions);
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
}
void CompilerContext::addVariable( void CompilerContext::addVariable(
VariableDeclaration const& _declaration, VariableDeclaration const& _declaration,
unsigned _offsetToCurrent unsigned _offsetToCurrent

View File

@ -65,7 +65,8 @@ public:
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_runtimeContext(_runtimeContext), m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings) m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
{ {
if (m_runtimeContext) if (m_runtimeContext)
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
@ -131,6 +132,14 @@ public:
unsigned _outArgs, unsigned _outArgs,
std::function<void(CompilerContext&)> const& _generator std::function<void(CompilerContext&)> const& _generator
); );
/// Appends a call to a yul function and registers the function as externally used.
void callYulFunction(
std::string const& _name,
unsigned _inArgs,
unsigned _outArgs
);
/// Returns the tag of the named low-level function and inserts the generator into the /// Returns the tag of the named low-level function and inserts the generator into the
/// list of low-level-functions to be generated, unless it already exists. /// list of low-level-functions to be generated, unless it already exists.
/// Note that the generator should not assume that objects are still alive when it is called, /// Note that the generator should not assume that objects are still alive when it is called,
@ -144,6 +153,12 @@ public:
/// Generates the code for missing low-level functions, i.e. calls the generators passed above. /// Generates the code for missing low-level functions, i.e. calls the generators passed above.
void appendMissingLowLevelFunctions(); void appendMissingLowLevelFunctions();
ABIFunctions& abiFunctions() { return m_abiFunctions; } ABIFunctions& abiFunctions() { return m_abiFunctions; }
YulUtilFunctions& utilFunctions() { return m_yulUtilFunctions; }
/// @returns concatenation of all generated functions and a set of the
/// externally used functions.
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const; ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function). /// Returns the distance of the given local variable from the bottom of the stack (of the current function).
@ -355,8 +370,14 @@ private:
size_t m_runtimeSub = -1; size_t m_runtimeSub = -1;
/// An index of low-level function labels by name. /// An index of low-level function labels by name.
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions; std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
/// Collector for yul functions.
MultiUseYulFunctionCollector m_yulFunctionCollector;
/// Set of externally used yul functions.
std::set<std::string> m_externallyUsedYulFunctions;
/// Container for ABI functions to be generated. /// Container for ABI functions to be generated.
ABIFunctions m_abiFunctions; ABIFunctions m_abiFunctions;
/// Container for Yul Util functions to be generated.
YulUtilFunctions m_yulUtilFunctions;
/// The queue of low-level functions to generate. /// The queue of low-level functions to generate.
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue; std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
}; };

View File

@ -121,56 +121,12 @@ void CompilerUtils::returnDataToArray()
void CompilerUtils::accessCalldataTail(Type const& _type) void CompilerUtils::accessCalldataTail(Type const& _type)
{ {
solAssert(_type.dataStoredIn(DataLocation::CallData), ""); m_context << Instruction::SWAP1;
solAssert(_type.isDynamicallyEncoded(), ""); m_context.callYulFunction(
m_context.utilFunctions().accessCalldataTailFunction(_type),
unsigned int tailSize = _type.calldataEncodedTailSize(); 2,
solAssert(tailSize > 1, ""); _type.isDynamicallySized() ? 2 : 1
);
// returns the absolute offset of the tail in "base_ref"
m_context.appendInlineAssembly(Whiskers(R"({
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
base_ref := add(base_ref, rel_offset_of_tail)
})")
("neededLength", toCompactHexWithPrefix(tailSize))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
.render(), {"base_ref", "ptr_to_tail"});
// stack layout: <absolute_offset_of_tail> <garbage>
if (!_type.isDynamicallySized())
{
m_context << Instruction::POP;
// stack layout: <absolute_offset_of_tail>
solAssert(
_type.category() == Type::Category::Struct ||
_type.category() == Type::Category::Array,
"Invalid dynamically encoded base type on tail access."
);
}
else
{
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
solAssert(!!arrayType, "Invalid dynamically sized type.");
unsigned int calldataStride = arrayType->calldataStride();
solAssert(calldataStride > 0, "");
// returns the absolute offset of the tail in "base_ref"
// and the length of the tail in "length"
m_context.appendInlineAssembly(
Whiskers(R"({
length := calldataload(base_ref)
base_ref := add(base_ref, 0x20)
if gt(length, 0xffffffffffffffff) { <revertString> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
})")
("calldataStride", toCompactHexWithPrefix(calldataStride))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
.render(),
{"base_ref", "length"}
);
// stack layout: <absolute_offset_of_tail> <length>
}
} }
unsigned CompilerUtils::loadFromMemory( unsigned CompilerUtils::loadFromMemory(
@ -539,6 +495,10 @@ void CompilerUtils::encodeToMemory(
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{ {
// copy tail pointer (=mem_end - mem_start) to memory // copy tail pointer (=mem_end - mem_start) to memory
solAssert(
(2 + dynPointers) <= 16,
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
);
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
m_context << Instruction::SUB; m_context << Instruction::SUB;
m_context << dupInstruction(2 + dynPointers - thisDynPointer); m_context << dupInstruction(2 + dynPointers - thisDynPointer);
@ -595,31 +555,21 @@ void CompilerUtils::abiEncodeV2(
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
auto ret = m_context.pushNewTag();
moveIntoStack(sizeOnStack(_givenTypes) + 1);
string encoderName = string encoderName =
_padToWordBoundaries ? _padToWordBoundaries ?
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) : m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes); m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
m_context.appendJumpTo(m_context.namedTag(encoderName)); m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
m_context << ret.tag();
} }
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
{ {
// stack: <source_offset> <length> [stack top] // stack: <source_offset> <length> [stack top]
auto ret = m_context.pushNewTag();
moveIntoStack(2);
// stack: <return tag> <source_offset> <length> [stack top]
m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
// stack: <return tag> <end> <start> // stack: <end> <start>
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
m_context.appendJumpTo(m_context.namedTag(decoderName)); m_context.callYulFunction(decoderName, 2, sizeOnStack(_parameterTypes));
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
m_context << ret.tag();
} }
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)

View File

@ -1267,12 +1267,12 @@ void ContractCompiler::appendMissingFunctions()
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
} }
m_context.appendMissingLowLevelFunctions(); m_context.appendMissingLowLevelFunctions();
auto abiFunctions = m_context.abiFunctions().requestedFunctions(); auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
if (!abiFunctions.first.empty()) if (!yulFunctions.empty())
m_context.appendInlineAssembly( m_context.appendInlineAssembly(
"{" + move(abiFunctions.first) + "}", "{" + move(yulFunctions) + "}",
{}, {},
abiFunctions.second, externallyUsedYulFunctions,
true, true,
m_optimiserSettings m_optimiserSettings
); );

View File

@ -39,7 +39,7 @@ using namespace solidity::frontend;
string YulUtilFunctions::combineExternalFunctionIdFunction() string YulUtilFunctions::combineExternalFunctionIdFunction()
{ {
string functionName = "combine_external_function_id"; string functionName = "combine_external_function_id";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(addr, selector) -> combined { function <functionName>(addr, selector) -> combined {
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff))) combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
@ -55,7 +55,7 @@ string YulUtilFunctions::combineExternalFunctionIdFunction()
string YulUtilFunctions::splitExternalFunctionIdFunction() string YulUtilFunctions::splitExternalFunctionIdFunction()
{ {
string functionName = "split_external_function_id"; string functionName = "split_external_function_id";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(combined) -> addr, selector { function <functionName>(combined) -> addr, selector {
combined := <shr64>(combined) combined := <shr64>(combined)
@ -73,7 +73,7 @@ string YulUtilFunctions::splitExternalFunctionIdFunction()
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
{ {
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if (_fromCalldata) if (_fromCalldata)
{ {
return Whiskers(R"( return Whiskers(R"(
@ -116,7 +116,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
solAssert(!_assert || !_messageType, "Asserts can't have messages!"); solAssert(!_assert || !_messageType, "Asserts can't have messages!");
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if (!_messageType) if (!_messageType)
return Whiskers(R"( return Whiskers(R"(
function <functionName>(condition) { function <functionName>(condition) {
@ -166,7 +166,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
string YulUtilFunctions::leftAlignFunction(Type const& _type) string YulUtilFunctions::leftAlignFunction(Type const& _type)
{ {
string functionName = string("leftAlign_") + _type.identifier(); string functionName = string("leftAlign_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> aligned { function <functionName>(value) -> aligned {
<body> <body>
@ -228,7 +228,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
solAssert(_numBits < 256, ""); solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits); string functionName = "shift_left_" + to_string(_numBits);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(value) -> newValue { function <functionName>(value) -> newValue {
@ -251,7 +251,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
string YulUtilFunctions::shiftLeftFunctionDynamic() string YulUtilFunctions::shiftLeftFunctionDynamic()
{ {
string functionName = "shift_left_dynamic"; string functionName = "shift_left_dynamic";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(bits, value) -> newValue { function <functionName>(bits, value) -> newValue {
@ -277,7 +277,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// the opcodes SAR and SDIV behave differently with regards to rounding! // the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(value) -> newValue { function <functionName>(value) -> newValue {
@ -303,7 +303,7 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
// the opcodes SAR and SDIV behave differently with regards to rounding! // the opcodes SAR and SDIV behave differently with regards to rounding!
string const functionName = "shift_right_unsigned_dynamic"; string const functionName = "shift_right_unsigned_dynamic";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(bits, value) -> newValue { function <functionName>(bits, value) -> newValue {
@ -328,7 +328,7 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
size_t numBits = _numBytes * 8; size_t numBits = _numBytes * 8;
size_t shiftBits = _shiftBytes * 8; size_t shiftBits = _shiftBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes); string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(value, toInsert) -> result { function <functionName>(value, toInsert) -> result {
@ -350,7 +350,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
solAssert(_numBytes <= 32, ""); solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8; size_t numBits = _numBytes * 8;
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes); string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(value, shiftBytes, toInsert) -> result { function <functionName>(value, shiftBytes, toInsert) -> result {
@ -371,7 +371,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
string YulUtilFunctions::roundUpFunction() string YulUtilFunctions::roundUpFunction()
{ {
string functionName = "round_up_to_mul_of_32"; string functionName = "round_up_to_mul_of_32";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(value) -> result { function <functionName>(value) -> result {
@ -389,7 +389,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
// TODO: Consider to add a special case for unsigned 256-bit integers // TODO: Consider to add a special case for unsigned 256-bit integers
// and use the following instead: // and use the following instead:
// sum := add(x, y) if lt(sum, x) { revert(0, 0) } // sum := add(x, y) if lt(sum, x) { revert(0, 0) }
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> sum { function <functionName>(x, y) -> sum {
@ -416,7 +416,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{ {
string functionName = "checked_mul_" + _type.identifier(); string functionName = "checked_mul_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
// Multiplication by zero could be treated separately and directly return zero. // Multiplication by zero could be treated separately and directly return zero.
Whiskers(R"( Whiskers(R"(
@ -448,7 +448,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{ {
string functionName = "checked_div_" + _type.identifier(); string functionName = "checked_div_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> r { function <functionName>(x, y) -> r {
@ -473,7 +473,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
{ {
string functionName = "checked_mod_" + _type.identifier(); string functionName = "checked_mod_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> r { function <functionName>(x, y) -> r {
@ -490,7 +490,7 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{ {
string functionName = "checked_sub_" + _type.identifier(); string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> diff { function <functionName>(x, y) -> diff {
@ -516,7 +516,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{ {
string functionName = "array_length_" + _type.identifier(); string functionName = "array_length_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"( Whiskers w(R"(
function <functionName>(value) -> length { function <functionName>(value) -> length {
<?dynamic> <?dynamic>
@ -564,7 +564,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
string functionName = "resize_array_" + _type.identifier(); string functionName = "resize_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(array, newLen) { function <functionName>(array, newLen) {
if gt(newLen, <maxArrayLength>) { if gt(newLen, <maxArrayLength>) {
@ -604,7 +604,7 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_pop_" + _type.identifier(); string functionName = "array_pop_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(array) { function <functionName>(array) {
let oldLen := <fetchLength>(array) let oldLen := <fetchLength>(array)
@ -632,7 +632,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier(); string functionName = "array_push_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(array, value) { function <functionName>(array, value) {
let oldLen := <fetchLength>(array) let oldLen := <fetchLength>(array)
@ -659,7 +659,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_zero_" + _type.identifier(); string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(array) -> slot, offset { function <functionName>(array) -> slot, offset {
let oldLen := <fetchLength>(array) let oldLen := <fetchLength>(array)
@ -684,7 +684,7 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes"); solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(start, end) { function <functionName>(start, end) {
for {} lt(start, end) { start := add(start, <increment>) } for {} lt(start, end) { start := add(start, <increment>) }
@ -715,7 +715,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
string functionName = "clear_storage_array_" + _type.identifier(); string functionName = "clear_storage_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot) { function <functionName>(slot) {
<?dynamic> <?dynamic>
@ -745,7 +745,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{ {
string functionName = "array_convert_length_to_size_" + _type.identifier(); string functionName = "array_convert_length_to_size_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Type const& baseType = *_type.baseType(); Type const& baseType = *_type.baseType();
switch (_type.location()) switch (_type.location())
@ -798,7 +798,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{ {
solAssert(_type.dataStoredIn(DataLocation::Memory), ""); solAssert(_type.dataStoredIn(DataLocation::Memory), "");
string functionName = "array_allocation_size_" + _type.identifier(); string functionName = "array_allocation_size_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"( Whiskers w(R"(
function <functionName>(length) -> size { function <functionName>(length) -> size {
// Make sure we can allocate memory without overflow // Make sure we can allocate memory without overflow
@ -825,7 +825,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{ {
string functionName = "array_dataslot_" + _type.identifier(); string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
// No special processing for calldata arrays, because they are stored as // No special processing for calldata arrays, because they are stored as
// offset of the data area and length on the stack, so the offset already // offset of the data area and length on the stack, so the offset already
// points to the data area. // points to the data area.
@ -858,7 +858,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, ""); solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "storage_array_index_access_" + _type.identifier(); string functionName = "storage_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(array, index) -> slot, offset { function <functionName>(array, index) -> slot, offset {
if iszero(lt(index, <arrayLen>(array))) { if iszero(lt(index, <arrayLen>(array))) {
@ -886,7 +886,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type) string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
{ {
string functionName = "memory_array_index_access_" + _type.identifier(); string functionName = "memory_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(baseRef, index) -> addr { function <functionName>(baseRef, index) -> addr {
if iszero(lt(index, <arrayLen>(baseRef))) { if iszero(lt(index, <arrayLen>(baseRef))) {
@ -912,7 +912,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
{ {
solAssert(_type.dataStoredIn(DataLocation::CallData), ""); solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_array_index_access_" + _type.identifier(); string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> { function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() } if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
@ -938,17 +938,17 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
solAssert(_type.isDynamicallyEncoded(), ""); solAssert(_type.isDynamicallyEncoded(), "");
solAssert(_type.dataStoredIn(DataLocation::CallData), ""); solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "access_calldata_tail_" + _type.identifier(); string functionName = "access_calldata_tail_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> { function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
let rel_offset_of_tail := calldataload(ptr_to_tail) let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) } if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <invalidCalldataTailOffset> }
addr := add(base_ref, rel_offset_of_tail) addr := add(base_ref, rel_offset_of_tail)
<?dynamicallySized> <?dynamicallySized>
length := calldataload(addr) length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { revert(0, 0) } if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
addr := add(addr, 32) addr := add(addr, 32)
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail> }
</dynamicallySized> </dynamicallySized>
} }
)") )")
@ -956,6 +956,9 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
("dynamicallySized", _type.isDynamicallySized()) ("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize())) ("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0)) ("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
("invalidCalldataTailOffset", revertReasonIfDebug("Invalid calldata tail offset"))
("invalidCalldataTailLength", revertReasonIfDebug("Invalid calldata tail length"))
("shortCalldataTail", revertReasonIfDebug("Calldata tail too short"))
.render(); .render();
}); });
} }
@ -966,7 +969,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
if (_type.dataStoredIn(DataLocation::Storage)) if (_type.dataStoredIn(DataLocation::Storage))
solAssert(_type.baseType()->storageBytes() > 16, ""); solAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "array_nextElement_" + _type.identifier(); string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(ptr) -> next { function <functionName>(ptr) -> next {
next := add(ptr, <advance>) next := add(ptr, <advance>)
@ -1002,7 +1005,7 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
solAssert(_keyType.sizeOnStack() <= 1, ""); solAssert(_keyType.sizeOnStack() <= 1, "");
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier(); string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized()) if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot <comma> <key>) -> dataSlot { function <functionName>(slot <comma> <key>) -> dataSlot {
@ -1050,7 +1053,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
to_string(_offset) + to_string(_offset) +
"_" + "_" +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, ""); solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot) -> value { function <functionName>(slot) -> value {
@ -1071,7 +1074,7 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
string(_splitFunctionTypes ? "split_" : "") + string(_splitFunctionTypes ? "split_" : "") +
"_" + "_" +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, ""); solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot, offset) -> value { function <functionName>(slot, offset) -> value {
@ -1101,7 +1104,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") + (_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
if (_type.isValueType()) if (_type.isValueType())
{ {
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size."); solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
@ -1141,7 +1144,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
string("write_to_memory_") + string("write_to_memory_") +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), ""); solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
if (auto ref = dynamic_cast<ReferenceType const*>(&_type)) if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{ {
@ -1201,7 +1204,7 @@ string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool
"extract_from_storage_value_dynamic" + "extract_from_storage_value_dynamic" +
string(_splitFunctionTypes ? "split_" : "") + string(_splitFunctionTypes ? "split_" : "") +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot_value, offset) -> value { function <functionName>(slot_value, offset) -> value {
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value)) value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
@ -1224,7 +1227,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
"offset_" + "offset_" +
to_string(_offset) + to_string(_offset) +
_type.identifier(); _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot_value) -> value { function <functionName>(slot_value) -> value {
value := <cleanupStorage>(<shr>(slot_value)) value := <cleanupStorage>(<shr>(slot_value))
@ -1243,7 +1246,7 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
solUnimplementedAssert(!_splitFunctionTypes, ""); solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier(); string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> cleaned { function <functionName>(value) -> cleaned {
cleaned := <cleaned> cleaned := <cleaned>
@ -1275,7 +1278,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
solUnimplementedAssert(_type.category() != Type::Category::Function, ""); solUnimplementedAssert(_type.category() != Type::Category::Function, "");
string functionName = "prepare_store_" + _type.identifier(); string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
ret := <actualPrepare> ret := <actualPrepare>
@ -1293,7 +1296,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
string YulUtilFunctions::allocationFunction() string YulUtilFunctions::allocationFunction()
{ {
string functionName = "allocateMemory"; string functionName = "allocateMemory";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(size) -> memPtr { function <functionName>(size) -> memPtr {
memPtr := mload(<freeMemoryPointer>) memPtr := mload(<freeMemoryPointer>)
@ -1314,7 +1317,7 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), ""); solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_memory_array_" + _type.identifier(); string functionName = "allocate_memory_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(length) -> memPtr { function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length)) memPtr := <alloc>(<allocSize>(length))
@ -1333,6 +1336,35 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{ {
if (_from.category() == Type::Category::Function)
{
solAssert(_to.category() == Type::Category::Function, "");
FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from);
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to);
solAssert(
fromType.isImplicitlyConvertibleTo(targetType) &&
fromType.sizeOnStack() == targetType.sizeOnStack() &&
(fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) &&
fromType.kind() == targetType.kind(),
"Invalid function type conversion requested."
);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr, functionId) -> outAddr, outFunctionId {
outAddr := addr
outFunctionId := functionId
}
)")
("functionName", functionName)
.render();
});
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to); return conversionFunctionSpecial(_from, _to);
@ -1341,7 +1373,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
_from.identifier() + _from.identifier() +
"_to_" + "_to_" +
_to.identifier(); _to.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> converted { function <functionName>(value) -> converted {
<body> <body>
@ -1440,22 +1472,34 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
break; break;
case Type::Category::Array: case Type::Category::Array:
{ {
bool equal = _from == _to; if (_from == _to)
body = "converted := value";
if (!equal) else
{ {
ArrayType const& from = dynamic_cast<decltype(from)>(_from); ArrayType const& from = dynamic_cast<decltype(from)>(_from);
ArrayType const& to = dynamic_cast<decltype(to)>(_to); ArrayType const& to = dynamic_cast<decltype(to)>(_to);
if (*from.mobileType() == *to.mobileType()) switch (to.location())
equal = true; {
case DataLocation::Storage:
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
solAssert(
(to.isPointer() || (from.isByteArray() && to.isByteArray())) &&
from.location() == DataLocation::Storage,
"Invalid conversion to storage type."
);
body = "converted := value";
break;
case DataLocation::Memory:
// Copy the array to a free position in memory, unless it is already in memory.
solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet.");
body = "converted := value";
break;
case DataLocation::CallData:
solUnimplemented("Conversion of calldata types not yet implemented.");
break;
}
} }
if (equal)
body = "converted := value";
else
solUnimplementedAssert(false, "Array conversion not implemented.");
break; break;
} }
case Type::Category::Struct: case Type::Category::Struct:
@ -1519,7 +1563,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
string YulUtilFunctions::cleanupFunction(Type const& _type) string YulUtilFunctions::cleanupFunction(Type const& _type)
{ {
string functionName = string("cleanup_") + _type.identifier(); string functionName = string("cleanup_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> cleaned { function <functionName>(value) -> cleaned {
<body> <body>
@ -1606,7 +1650,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
{ {
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) { function <functionName>(value) {
if iszero(<condition>) { <failure> } if iszero(<condition>) { <failure> }
@ -1667,7 +1711,7 @@ string YulUtilFunctions::packedHashFunction(
size_t sizeOnStack = 0; size_t sizeOnStack = 0;
for (Type const* t: _givenTypes) for (Type const* t: _givenTypes)
sizeOnStack += t->sizeOnStack(); sizeOnStack += t->sizeOnStack();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(<variables>) -> hash { function <functionName>(<variables>) -> hash {
let pos := mload(<freeMemoryPointer>) let pos := mload(<freeMemoryPointer>)
@ -1688,7 +1732,7 @@ string YulUtilFunctions::forwardingRevertFunction()
{ {
bool forward = m_evmVersion.supportsReturndata(); bool forward = m_evmVersion.supportsReturndata();
string functionName = "revert_forward_" + to_string(forward); string functionName = "revert_forward_" + to_string(forward);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if (forward) if (forward)
return Whiskers(R"( return Whiskers(R"(
function <functionName>() { function <functionName>() {
@ -1715,7 +1759,7 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
string const functionName = "decrement_" + _type.identifier(); string const functionName = "decrement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 minintval; u256 minintval;
// Smallest admissible value to decrement // Smallest admissible value to decrement
@ -1743,7 +1787,7 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
string const functionName = "increment_" + _type.identifier(); string const functionName = "increment_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 maxintval; u256 maxintval;
// Biggest admissible value to increment // Biggest admissible value to increment
@ -1774,7 +1818,7 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(_value) -> ret { function <functionName>(_value) -> ret {
if slt(_value, <minval>) { revert(0,0) } if slt(_value, <minval>) { revert(0,0) }
@ -1794,7 +1838,7 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type)
string const functionName = "zero_value_for_" + _type.identifier(); string const functionName = "zero_value_for_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>() -> ret { function <functionName>() -> ret {
<body> <body>
@ -1810,7 +1854,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
{ {
string const functionName = "storage_set_to_zero_" + _type.identifier(); string const functionName = "storage_set_to_zero_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if (_type.isValueType()) if (_type.isValueType())
return Whiskers(R"( return Whiskers(R"(
function <functionName>(slot, offset) { function <functionName>(slot, offset) {
@ -1842,7 +1886,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
_from.identifier() + _from.identifier() +
"_to_" + "_to_" +
_to.identifier(); _to.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
if ( if (
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to); auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size() fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
@ -1950,7 +1994,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
if (_fromCalldata) if (_fromCalldata)
solAssert(!_type.isDynamicallyEncoded(), ""); solAssert(!_type.isDynamicallyEncoded(), "");
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector.createFunction(functionName, [&] {
if (auto refType = dynamic_cast<ReferenceType const*>(&_type)) if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
{ {
solAssert(refType->sizeOnStack() == 1, ""); solAssert(refType->sizeOnStack() == 1, "");

View File

@ -47,11 +47,11 @@ public:
explicit YulUtilFunctions( explicit YulUtilFunctions(
langutil::EVMVersion _evmVersion, langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings, RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector MultiUseYulFunctionCollector& _functionCollector
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)) m_functionCollector(_functionCollector)
{} {}
/// @returns a function that combines the address and selector to a single value /// @returns a function that combines the address and selector to a single value
@ -306,7 +306,7 @@ private:
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings; RevertStrings m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; MultiUseYulFunctionCollector& m_functionCollector;
}; };
} }

View File

@ -100,7 +100,7 @@ string IRGenerationContext::newYulVariable()
string IRGenerationContext::internalDispatch(size_t _in, size_t _out) string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{ {
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() { return m_functions.createFunction(funName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(fun <comma> <in>) <arrow> <out> { function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun switch fun

View File

@ -56,11 +56,10 @@ public:
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings))
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
{} {}
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; } MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base. /// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy) void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
@ -108,7 +107,7 @@ private:
std::map<VariableDeclaration const*, IRVariable> m_localVariables; std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions; MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0; size_t m_varCounter = 0;
}; };

View File

@ -107,7 +107,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
for (auto const* contract: _contract.annotation().linearizedBaseContracts) for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions()) for (auto const* fun: contract->definedFunctions())
generateFunction(*fun); generateFunction(*fun);
t("functions", m_context.functionCollector()->requestedFunctions()); t("functions", m_context.functionCollector().requestedFunctions());
resetContext(_contract); resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
@ -116,7 +116,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
for (auto const* contract: _contract.annotation().linearizedBaseContracts) for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions()) for (auto const* fun: contract->definedFunctions())
generateFunction(*fun); generateFunction(*fun);
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions()); t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
return t.render(); return t.render();
} }
@ -130,7 +130,7 @@ string IRGenerator::generate(Block const& _block)
string IRGenerator::generateFunction(FunctionDefinition const& _function) string IRGenerator::generateFunction(FunctionDefinition const& _function)
{ {
string functionName = m_context.functionName(_function); string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"( Whiskers t(R"(
function <functionName>(<params>) <returns> { function <functionName>(<params>) <returns> {
<body> <body>
@ -160,7 +160,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
solAssert(_varDecl.isStateVariable(), ""); solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type)) if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
return m_context.functionCollector()->createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
solAssert(slot_offset.second == 0, ""); solAssert(slot_offset.second == 0, "");
FunctionType funType(_varDecl); FunctionType funType(_varDecl);
@ -209,7 +209,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
{ {
solUnimplementedAssert(type->isValueType(), ""); solUnimplementedAssert(type->isValueType(), "");
return m_context.functionCollector()->createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
return Whiskers(R"( return Whiskers(R"(
@ -383,11 +383,10 @@ string IRGenerator::memoryInit()
void IRGenerator::resetContext(ContractDefinition const& _contract) void IRGenerator::resetContext(ContractDefinition const& _contract)
{ {
solAssert( solAssert(
m_context.functionCollector()->requestedFunctions().empty(), m_context.functionCollector().requestedFunctions().empty(),
"Reset context while it still had functions." "Reset context while it still had functions."
); );
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings); m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables()) for (auto const& var: ContractType(_contract).stateVariables())

View File

@ -47,6 +47,7 @@ using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util; using namespace solidity::util;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace std::string_literals;
namespace namespace
{ {
@ -800,11 +801,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
case Type::Category::Function: case Type::Category::Function:
if (member == "selector") if (member == "selector")
{ {
solUnimplementedAssert(false, ""); solUnimplementedAssert(
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
FunctionType::Kind::External, ""
);
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
} }
else if (member == "address") else if (member == "address")
{ {
solUnimplementedAssert(false, ""); solUnimplementedAssert(
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
FunctionType::Kind::External, ""
);
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
} }
else else
solAssert( solAssert(
@ -1204,7 +1213,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
argumentTypes.emplace_back(&type(*arg)); argumentTypes.emplace_back(&type(*arg));
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList()); argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
} }
string argumentString = ", " + joinHumanReadable(argumentStrings); string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");

View File

@ -432,12 +432,6 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
m_context.newValue(*param); m_context.newValue(*param);
m_context.setUnknownValue(*param); m_context.setUnknownValue(*param);
} }
m_errorReporter.warning(
_funCall.location(),
"Assertion checker does not support recursive function calls.",
SecondarySourceLocation().append("Starting from function:", funDef->location())
);
} }
else else
{ {

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Algorithms.h>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::langutil; using namespace solidity::langutil;
@ -75,16 +77,39 @@ void CHC::analyze(SourceUnit const& _source)
m_context.setAssertionAccumulation(false); m_context.setAssertionAccumulation(false);
m_variableUsage.setFunctionInlining(false); m_variableUsage.setFunctionInlining(false);
resetSourceAnalysis();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool); auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto genesisSort = make_shared<smt::FunctionSort>( auto genesisSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(), vector<smt::SortPointer>(),
boolSort boolSort
); );
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis"); m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
auto genesis = (*m_genesisPredicate)({}); addRule(genesis(), "genesis");
addRule(genesis, genesis.name);
_source.accept(*this); set<SourceUnit const*, IdCompare> sources;
sources.insert(&_source);
for (auto const& source: _source.referencedSourceUnits(true))
sources.insert(source);
for (auto const* source: sources)
defineInterfacesAndSummaries(*source);
for (auto const* source: sources)
source->accept(*this);
for (auto const& [scope, target]: m_verificationTargets)
{
auto assertions = transactionAssertions(scope);
for (auto const* assertion: assertions)
{
createErrorBlock();
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
auto [result, model] = query(error(), assertion->location());
// This should be fine but it's a bug in the old compiler
(void)model;
if (result == smt::CheckResult::UNSATISFIABLE)
m_safeAssertions.insert(assertion);
}
}
} }
vector<string> CHC::unhandledQueries() const vector<string> CHC::unhandledQueries() const
@ -97,26 +122,15 @@ vector<string> CHC::unhandledQueries() const
bool CHC::visit(ContractDefinition const& _contract) bool CHC::visit(ContractDefinition const& _contract)
{ {
if (!shouldVisit(_contract)) resetContractAnalysis();
return false;
reset();
initContract(_contract); initContract(_contract);
m_stateVariables = _contract.stateVariablesIncludingInherited(); m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
m_stateSorts = stateSorts(_contract);
for (auto const& var: m_stateVariables)
// SMT solvers do not support function types as arguments.
if (var->type()->category() == Type::Category::Function)
m_stateSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
else
m_stateSorts.push_back(smt::smtSort(*var->type()));
clearIndices(&_contract); clearIndices(&_contract);
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_interfacePredicate = createSymbolicBlock(interfaceSort(), "interface_" + suffix);
// TODO create static instances for Bool/Int sorts in SolverInterface. // TODO create static instances for Bool/Int sorts in SolverInterface.
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool); auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
@ -125,10 +139,12 @@ bool CHC::visit(ContractDefinition const& _contract)
boolSort boolSort
); );
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix); m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
m_constructorPredicate = createSymbolicBlock(constructorSort(), "implicit_constructor_" + to_string(_contract.id())); m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
auto stateExprs = currentStateVariables(); auto stateExprs = currentStateVariables();
setCurrentBlock(*m_interfacePredicate, &stateExprs); setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
SMTEncoder::visit(_contract); SMTEncoder::visit(_contract);
return false; return false;
@ -136,33 +152,33 @@ bool CHC::visit(ContractDefinition const& _contract)
void CHC::endVisit(ContractDefinition const& _contract) void CHC::endVisit(ContractDefinition const& _contract)
{ {
if (!shouldVisit(_contract))
return;
for (auto const& var: m_stateVariables) for (auto const& var: m_stateVariables)
{ {
solAssert(m_context.knownVariable(*var), ""); solAssert(m_context.knownVariable(*var), "");
auto const& symbVar = m_context.variable(*var);
symbVar->resetIndex();
m_context.setZeroValue(*var); m_context.setZeroValue(*var);
symbVar->increaseIndex();
} }
auto genesisPred = (*m_genesisPredicate)({}); auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables()); connectBlocks(genesis(), implicitConstructor);
connectBlocks(genesisPred, implicitConstructor);
m_currentBlock = implicitConstructor; m_currentBlock = implicitConstructor;
m_context.addAssertion(m_error.currentValue() == 0);
if (auto constructor = _contract.constructor()) if (auto constructor = _contract.constructor())
constructor->accept(*this); constructor->accept(*this);
else else
inlineConstructorHierarchy(_contract); inlineConstructorHierarchy(_contract);
connectBlocks(m_currentBlock, interface()); auto summary = predicate(*m_constructorSummaryPredicate, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables());
connectBlocks(m_currentBlock, summary);
for (unsigned i = 0; i < m_verificationTargets.size(); ++i) clearIndices(m_currentContract, nullptr);
{ auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
auto const& target = m_verificationTargets.at(i); setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
auto errorAppl = error(i + 1);
if (query(errorAppl, target->location())) addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue());
m_safeAssertions.insert(target); connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
}
SMTEncoder::endVisit(_contract); SMTEncoder::endVisit(_contract);
} }
@ -182,7 +198,7 @@ bool CHC::visit(FunctionDefinition const& _function)
return false; return false;
} }
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented"); solAssert(!m_currentFunction, "Function inlining should not happen in CHC.");
m_currentFunction = &_function; m_currentFunction = &_function;
initFunction(_function); initFunction(_function);
@ -193,7 +209,17 @@ bool CHC::visit(FunctionDefinition const& _function)
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables()); auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
auto bodyPred = predicate(*bodyBlock); auto bodyPred = predicate(*bodyBlock);
connectBlocks(m_currentBlock, functionPred); if (_function.isConstructor())
connectBlocks(m_currentBlock, functionPred);
else
connectBlocks(genesis(), functionPred);
m_context.addAssertion(m_error.currentValue() == 0);
for (auto const* var: m_stateVariables)
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
for (auto const& var: _function.parameters())
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
connectBlocks(functionPred, bodyPred); connectBlocks(functionPred, bodyPred);
setCurrentBlock(*bodyBlock); setCurrentBlock(*bodyBlock);
@ -225,18 +251,30 @@ void CHC::endVisit(FunctionDefinition const& _function)
// This is done in endVisit(ContractDefinition). // This is done in endVisit(ContractDefinition).
if (_function.isConstructor()) if (_function.isConstructor())
{ {
auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id())); string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables())); auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables()));
clearIndices(m_currentContract, m_currentFunction); clearIndices(m_currentContract, m_currentFunction);
auto stateExprs = currentStateVariables(); auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
setCurrentBlock(*constructorExit, &stateExprs); setCurrentBlock(*constructorExit, &stateExprs);
} }
else else
{ {
connectBlocks(m_currentBlock, interface()); auto assertionError = m_error.currentValue();
clearIndices(m_currentContract, m_currentFunction); auto sum = summary(_function);
auto stateExprs = currentStateVariables(); connectBlocks(m_currentBlock, sum);
setCurrentBlock(*m_interfacePredicate, &stateExprs);
auto iface = interface();
auto stateExprs = initialStateVariables();
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
if (_function.isPublic())
{
addVerificationTarget(&_function, m_currentBlock, sum, assertionError);
connectBlocks(m_currentBlock, iface, sum && (assertionError == 0));
}
} }
m_currentFunction = nullptr; m_currentFunction = nullptr;
} }
@ -421,6 +459,8 @@ void CHC::endVisit(FunctionCall const& _funCall)
SMTEncoder::endVisit(_funCall); SMTEncoder::endVisit(_funCall);
break; break;
case FunctionType::Kind::Internal: case FunctionType::Kind::Internal:
internalFunctionCall(_funCall);
break;
case FunctionType::Kind::External: case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall: case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
@ -468,12 +508,56 @@ void CHC::visitAssert(FunctionCall const& _funCall)
solAssert(args.size() == 1, ""); solAssert(args.size() == 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
createErrorBlock(); solAssert(m_currentContract, "");
solAssert(m_currentFunction, "");
if (m_currentFunction->isConstructor())
m_functionAssertions[m_currentContract].insert(&_funCall);
else
m_functionAssertions[m_currentFunction].insert(&_funCall);
smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue()); auto previousError = m_error.currentValue();
connectBlocks(m_currentBlock, error(), currentPathConditions() && assertNeg); m_error.increaseIndex();
m_verificationTargets.push_back(&_funCall); connectBlocks(
m_currentBlock,
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
currentPathConditions() && !m_context.expression(*args.front())->currentValue() && (m_error.currentValue() == _funCall.id())
);
m_context.addAssertion(m_error.currentValue() == previousError);
}
void CHC::internalFunctionCall(FunctionCall const& _funCall)
{
solAssert(m_currentContract, "");
auto const* function = functionCallToDefinition(_funCall);
if (function)
{
if (m_currentFunction && !m_currentFunction->isConstructor())
m_callGraph[m_currentFunction].insert(function);
else
m_callGraph[m_currentContract].insert(function);
auto const* contract = function->annotation().contract;
// Libraries can have constants as their "state" variables,
// so we need to ensure they were constructed correctly.
if (contract->isLibrary())
m_context.addAssertion(interface(*contract));
}
auto previousError = m_error.currentValue();
m_context.addAssertion(predicate(_funCall));
connectBlocks(
m_currentBlock,
(m_currentFunction && !m_currentFunction->isConstructor()) ? summary(*m_currentFunction) : summary(*m_currentContract),
(m_error.currentValue() > 0)
);
m_context.addAssertion(m_error.currentValue() == 0);
m_error.increaseIndex();
m_context.addAssertion(m_error.currentValue() == previousError);
} }
void CHC::unknownFunctionCall(FunctionCall const&) void CHC::unknownFunctionCall(FunctionCall const&)
@ -488,15 +572,23 @@ void CHC::unknownFunctionCall(FunctionCall const&)
m_unknownFunctionCallSeen = true; m_unknownFunctionCallSeen = true;
} }
void CHC::reset() void CHC::resetSourceAnalysis()
{
m_verificationTargets.clear();
m_safeAssertions.clear();
m_functionAssertions.clear();
m_callGraph.clear();
m_summaries.clear();
}
void CHC::resetContractAnalysis()
{ {
m_stateSorts.clear(); m_stateSorts.clear();
m_stateVariables.clear(); m_stateVariables.clear();
m_verificationTargets.clear();
m_safeAssertions.clear();
m_unknownFunctionCallSeen = false; m_unknownFunctionCallSeen = false;
m_breakDest = nullptr; m_breakDest = nullptr;
m_continueDest = nullptr; m_continueDest = nullptr;
m_error.resetIndex();
} }
void CHC::eraseKnowledge() void CHC::eraseKnowledge()
@ -521,25 +613,9 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
} }
} }
bool CHC::shouldVisit(ContractDefinition const& _contract) const
{
if (
_contract.isLibrary() ||
_contract.isInterface()
)
return false;
return true;
}
bool CHC::shouldVisit(FunctionDefinition const& _function) const bool CHC::shouldVisit(FunctionDefinition const& _function) const
{ {
if ( return _function.isImplemented();
_function.isPublic() &&
_function.isImplemented()
)
return true;
return false;
} }
void CHC::setCurrentBlock( void CHC::setCurrentBlock(
@ -547,7 +623,8 @@ void CHC::setCurrentBlock(
vector<smt::Expression> const* _arguments vector<smt::Expression> const* _arguments
) )
{ {
m_context.popSolver(); if (m_context.solverStackHeigh() > 0)
m_context.popSolver();
solAssert(m_currentContract, ""); solAssert(m_currentContract, "");
clearIndices(m_currentContract, m_currentFunction); clearIndices(m_currentContract, m_currentFunction);
m_context.pushSolver(); m_context.pushSolver();
@ -557,10 +634,42 @@ void CHC::setCurrentBlock(
m_currentBlock = predicate(_block); m_currentBlock = predicate(_block);
} }
set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const* _txRoot)
{
set<Expression const*, IdCompare> assertions;
solidity::util::BreadthFirstSearch<ASTNode const*>{{_txRoot}}.run([&](auto const* function, auto&& _addChild) {
assertions.insert(m_functionAssertions[function].begin(), m_functionAssertions[function].end());
for (auto const* called: m_callGraph[function])
_addChild(called);
});
return assertions;
}
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
vector<VariableDeclaration const*> stateVars;
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
for (auto var: contract->stateVariables())
stateVars.push_back(var);
return stateVars;
}
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
{
vector<smt::SortPointer> stateSorts;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return stateSorts;
}
smt::SortPointer CHC::constructorSort() smt::SortPointer CHC::constructorSort()
{ {
// TODO this will change once we support function calls. auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return interfaceSort(); auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts,
boolSort
);
} }
smt::SortPointer CHC::interfaceSort() smt::SortPointer CHC::interfaceSort()
@ -572,20 +681,38 @@ smt::SortPointer CHC::interfaceSort()
); );
} }
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
stateSorts(_contract),
boolSort
);
}
/// A function in the symbolic CFG requires:
/// - Index of failed assertion. 0 means no assertion failed.
/// - 2 sets of state variables:
/// - State variables at the beginning of the current function, immutable
/// - Current state variables
/// At the beginning of the function these must equal set 1
/// - 2 sets of input variables:
/// - Input variables at the beginning of the current function, immutable
/// - Current input variables
/// At the beginning of the function these must equal set 1
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function) smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{ {
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool); auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts; auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
for (auto const& var: _function.parameters() + _function.returnParameters()) vector<smt::SortPointer> inputSorts;
{ for (auto const& var: _function.parameters())
// SMT solvers do not support function types as arguments. inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
if (var->type()->category() == Type::Category::Function) vector<smt::SortPointer> outputSorts;
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int)); for (auto const& var: _function.returnParameters())
else outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
varSorts.push_back(smt::smtSort(*var->type()));
}
return make_shared<smt::FunctionSort>( return make_shared<smt::FunctionSort>(
m_stateSorts + varSorts, vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
boolSort boolSort
); );
} }
@ -601,19 +728,31 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool); auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts; vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables()) for (auto const& var: m_currentFunction->localVariables())
{ varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
// SMT solvers do not support function types as arguments.
if (var->type()->category() == Type::Category::Function)
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
else
varSorts.push_back(smt::smtSort(*var->type()));
}
return make_shared<smt::FunctionSort>( return make_shared<smt::FunctionSort>(
fSort->domain + varSorts, fSort->domain + varSorts,
boolSort boolSort
); );
} }
smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
{
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
boolSort
);
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name) unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
{ {
auto block = make_unique<smt::SymbolicFunctionVariable>( auto block = make_unique<smt::SymbolicFunctionVariable>(
@ -625,12 +764,33 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPoin
return block; return block;
} }
void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
{
for (auto const& node: _source.nodes())
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
for (auto const* base: contract->annotation().linearizedBaseContracts)
{
string suffix = base->name() + "_" + to_string(base->id());
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
if (!m_context.knownVariable(*var))
createVariable(*var);
for (auto const* function: base->definedFunctions())
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
}
}
smt::Expression CHC::interface() smt::Expression CHC::interface()
{ {
vector<smt::Expression> paramExprs; vector<smt::Expression> paramExprs;
for (auto const& var: m_stateVariables) for (auto const& var: m_stateVariables)
paramExprs.push_back(m_context.variable(*var)->currentValue()); paramExprs.push_back(m_context.variable(*var)->currentValue());
return (*m_interfacePredicate)(paramExprs); return (*m_interfaces.at(m_currentContract))(paramExprs);
}
smt::Expression CHC::interface(ContractDefinition const& _contract)
{
return (*m_interfaces.at(&_contract))(stateVariablesAtIndex(0, _contract));
} }
smt::Expression CHC::error() smt::Expression CHC::error()
@ -643,6 +803,27 @@ smt::Expression CHC::error(unsigned _idx)
return m_errorPredicate->functionValueAtIndex(_idx)({}); return m_errorPredicate->functionValueAtIndex(_idx)({});
} }
smt::Expression CHC::summary(ContractDefinition const&)
{
return (*m_constructorSummaryPredicate)(
vector<smt::Expression>{m_error.currentValue()} +
currentStateVariables()
);
}
smt::Expression CHC::summary(FunctionDefinition const& _function)
{
vector<smt::Expression> args{m_error.currentValue()};
auto contract = _function.annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
for (auto const& var: _function.parameters())
args.push_back(m_context.variable(*var)->valueAtIndex(0));
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
for (auto const& var: _function.returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(m_currentContract).at(&_function))(args);
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix) unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
{ {
return createSymbolicBlock(sort(_node), return createSymbolicBlock(sort(_node),
@ -653,6 +834,15 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node,
predicateName(_node)); predicateName(_node));
} }
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
{
return createSymbolicBlock(summarySort(_function, _contract),
"summary_" +
uniquePrefix() +
"_" +
predicateName(&_function, &_contract));
}
void CHC::createErrorBlock() void CHC::createErrorBlock()
{ {
solAssert(m_errorPredicate, ""); solAssert(m_errorPredicate, "");
@ -669,6 +859,28 @@ void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to
addRule(edge, _from.name + "_to_" + _to.name); addRule(edge, _from.name + "_to_" + _to.name);
} }
vector<smt::Expression> CHC::initialStateVariables()
{
return stateVariablesAtIndex(0);
}
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
{
solAssert(m_currentContract, "");
vector<smt::Expression> exprs;
for (auto const& var: m_stateVariables)
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
}
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
{
vector<smt::Expression> exprs;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
}
vector<smt::Expression> CHC::currentStateVariables() vector<smt::Expression> CHC::currentStateVariables()
{ {
solAssert(m_currentContract, ""); solAssert(m_currentContract, "");
@ -680,11 +892,22 @@ vector<smt::Expression> CHC::currentStateVariables()
vector<smt::Expression> CHC::currentFunctionVariables() vector<smt::Expression> CHC::currentFunctionVariables()
{ {
vector<smt::Expression> paramExprs; vector<smt::Expression> initInputExprs;
if (m_currentFunction) vector<smt::Expression> mutableInputExprs;
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) for (auto const& var: m_currentFunction->parameters())
paramExprs.push_back(m_context.variable(*var)->currentValue()); {
return currentStateVariables() + paramExprs; initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
}
vector<smt::Expression> returnExprs;
for (auto const& var: m_currentFunction->returnParameters())
returnExprs.push_back(m_context.variable(*var)->currentValue());
return vector<smt::Expression>{m_error.currentValue()} +
initialStateVariables() +
initInputExprs +
currentStateVariables() +
mutableInputExprs +
returnExprs;
} }
vector<smt::Expression> CHC::currentBlockVariables() vector<smt::Expression> CHC::currentBlockVariables()
@ -696,7 +919,7 @@ vector<smt::Expression> CHC::currentBlockVariables()
return currentFunctionVariables() + paramExprs; return currentFunctionVariables() + paramExprs;
} }
string CHC::predicateName(ASTNode const* _node) string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
{ {
string prefix; string prefix;
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node)) if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
@ -705,7 +928,12 @@ string CHC::predicateName(ASTNode const* _node)
if (!funDef->name().empty()) if (!funDef->name().empty())
prefix += "_" + funDef->name() + "_"; prefix += "_" + funDef->name() + "_";
} }
return prefix + to_string(_node->id()); else if (m_currentFunction && !m_currentFunction->name().empty())
prefix += m_currentFunction->name();
auto contract = _contract ? _contract : m_currentContract;
solAssert(contract, "");
return prefix + "_" + to_string(_node->id()) + "_" + to_string(contract->id());
} }
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block) smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
@ -721,12 +949,40 @@ smt::Expression CHC::predicate(
return _block(_arguments); return _block(_arguments);
} }
smt::Expression CHC::predicate(FunctionCall const& _funCall)
{
auto const* function = functionCallToDefinition(_funCall);
if (!function)
return smt::Expression(true);
m_error.increaseIndex();
vector<smt::Expression> args{m_error.currentValue()};
auto const* contract = function->annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
args += symbolicArguments(_funCall);
for (auto const& var: m_stateVariables)
m_context.variable(*var)->increaseIndex();
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
auto const& returnParams = function->returnParameters();
for (auto param: returnParams)
if (m_context.knownVariable(*param))
m_context.variable(*param)->increaseIndex();
else
createVariable(*param);
for (auto const& var: function->returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(contract).at(function))(args);
}
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName) void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
{ {
m_interface->addRule(_rule, _ruleName); m_interface->addRule(_rule, _ruleName);
} }
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location) pair<smt::CheckResult, vector<string>> CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
{ {
smt::CheckResult result; smt::CheckResult result;
vector<string> values; vector<string> values;
@ -736,7 +992,7 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
case smt::CheckResult::SATISFIABLE: case smt::CheckResult::SATISFIABLE:
break; break;
case smt::CheckResult::UNSATISFIABLE: case smt::CheckResult::UNSATISFIABLE:
return true; break;
case smt::CheckResult::UNKNOWN: case smt::CheckResult::UNKNOWN:
break; break;
case smt::CheckResult::CONFLICTING: case smt::CheckResult::CONFLICTING:
@ -746,7 +1002,12 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver."); m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
break; break;
} }
return false; return {result, values};
}
void CHC::addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId)
{
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{VerificationTarget::Type::Assert, _from, _constraints}, _errorId});
} }
string CHC::uniquePrefix() string CHC::uniquePrefix()

View File

@ -76,25 +76,42 @@ private:
void endVisit(Continue const& _node) override; void endVisit(Continue const& _node) override;
void visitAssert(FunctionCall const& _funCall); void visitAssert(FunctionCall const& _funCall);
void internalFunctionCall(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall); void unknownFunctionCall(FunctionCall const& _funCall);
//@} //@}
struct IdCompare
{
bool operator()(ASTNode const* lhs, ASTNode const* rhs) const
{
return lhs->id() < rhs->id();
}
};
/// Helpers. /// Helpers.
//@{ //@{
void reset(); void resetSourceAnalysis();
void resetContractAnalysis();
void eraseKnowledge(); void eraseKnowledge();
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override; void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
bool shouldVisit(ContractDefinition const& _contract) const;
bool shouldVisit(FunctionDefinition const& _function) const; bool shouldVisit(FunctionDefinition const& _function) const;
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr); void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
//@} //@}
/// Sort helpers. /// Sort helpers.
//@{ //@{
static std::vector<smt::SortPointer> stateSorts(ContractDefinition const& _contract);
smt::SortPointer constructorSort(); smt::SortPointer constructorSort();
smt::SortPointer interfaceSort(); smt::SortPointer interfaceSort();
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
smt::SortPointer sort(FunctionDefinition const& _function); smt::SortPointer sort(FunctionDefinition const& _function);
smt::SortPointer sort(ASTNode const* _block); smt::SortPointer sort(ASTNode const* _block);
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
/// where the sort changes because the set of state variables might change.
static smt::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
//@} //@}
/// Predicate helpers. /// Predicate helpers.
@ -102,14 +119,24 @@ private:
/// @returns a new block of given _sort and _name. /// @returns a new block of given _sort and _name.
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name); std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
/// Creates summary predicates for all functions of all contracts
/// in a given _source.
void defineInterfacesAndSummaries(SourceUnit const& _source);
/// Genesis predicate.
smt::Expression genesis() { return (*m_genesisPredicate)({}); }
/// Interface predicate over current variables. /// Interface predicate over current variables.
smt::Expression interface(); smt::Expression interface();
smt::Expression interface(ContractDefinition const& _contract);
/// Error predicate over current variables. /// Error predicate over current variables.
smt::Expression error(); smt::Expression error();
smt::Expression error(unsigned _idx); smt::Expression error(unsigned _idx);
/// Creates a block for the given _node. /// Creates a block for the given _node.
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = ""); std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
/// Creates a call block for the given function _function from contract _contract.
/// The contract is needed here because of inheritance.
std::unique_ptr<smt::SymbolicFunctionVariable> createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
/// Creates a new error block to be used by an assertion. /// Creates a new error block to be used by an assertion.
/// Also registers the predicate. /// Also registers the predicate.
@ -117,6 +144,11 @@ private:
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true)); void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
/// @returns the symbolic values of the state variables at the beginning
/// of the current transaction.
std::vector<smt::Expression> initialStateVariables();
std::vector<smt::Expression> stateVariablesAtIndex(int _index);
std::vector<smt::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
/// @returns the current symbolic values of the current state variables. /// @returns the current symbolic values of the current state variables.
std::vector<smt::Expression> currentStateVariables(); std::vector<smt::Expression> currentStateVariables();
@ -128,19 +160,28 @@ private:
std::vector<smt::Expression> currentBlockVariables(); std::vector<smt::Expression> currentBlockVariables();
/// @returns the predicate name for a given node. /// @returns the predicate name for a given node.
std::string predicateName(ASTNode const* _node); std::string predicateName(ASTNode const* _node, ContractDefinition const* _contract = nullptr);
/// @returns a predicate application over the current scoped variables. /// @returns a predicate application over the current scoped variables.
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block); smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
/// @returns a predicate application over @param _arguments. /// @returns a predicate application over @param _arguments.
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments); smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
/// @returns the summary predicate for the called function.
smt::Expression predicate(FunctionCall const& _funCall);
/// @returns a predicate that defines a constructor summary.
smt::Expression summary(ContractDefinition const& _contract);
/// @returns a predicate that defines a function summary.
smt::Expression summary(FunctionDefinition const& _function);
//@} //@}
/// Solver related. /// Solver related.
//@{ //@{
/// Adds Horn rule to the solver. /// Adds Horn rule to the solver.
void addRule(smt::Expression const& _rule, std::string const& _ruleName); void addRule(smt::Expression const& _rule, std::string const& _ruleName);
/// @returns true if query is unsatisfiable (safe). /// @returns <true, empty> if query is unsatisfiable (safe).
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location); /// @returns <false, model> otherwise.
std::pair<smt::CheckResult, std::vector<std::string>> query(smt::Expression const& _query, langutil::SourceLocation const& _location);
void addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId);
//@} //@}
/// Misc. /// Misc.
@ -157,15 +198,29 @@ private:
/// Implicit constructor predicate. /// Implicit constructor predicate.
/// Explicit constructors are handled as functions. /// Explicit constructors are handled as functions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorPredicate; std::unique_ptr<smt::SymbolicFunctionVariable> m_implicitConstructorPredicate;
/// Constructor summary predicate, exists after the constructor
/// (implicit or explicit) and before the interface.
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorSummaryPredicate;
/// Artificial Interface predicate. /// Artificial Interface predicate.
/// Single entry block for all functions. /// Single entry block for all functions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_interfacePredicate; std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
/// Artificial Error predicate. /// Artificial Error predicate.
/// Single error block for all assertions. /// Single error block for all assertions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate; std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
/// Function predicates.
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>>> m_summaries;
smt::SymbolicIntVariable m_error{
TypeProvider::uint256(),
TypeProvider::uint256(),
"error",
m_context
};
//@} //@}
/// Variables. /// Variables.
@ -180,7 +235,12 @@ private:
/// Verification targets. /// Verification targets.
//@{ //@{
std::vector<Expression const*> m_verificationTargets; struct CHCVerificationTarget: VerificationTarget
{
smt::Expression errorId;
};
std::map<ASTNode const*, CHCVerificationTarget, IdCompare> m_verificationTargets;
/// Assertions proven safe. /// Assertions proven safe.
std::set<Expression const*> m_safeAssertions; std::set<Expression const*> m_safeAssertions;
@ -190,6 +250,10 @@ private:
//@{ //@{
FunctionDefinition const* m_currentFunction = nullptr; FunctionDefinition const* m_currentFunction = nullptr;
std::map<ASTNode const*, std::set<ASTNode const*, IdCompare>, IdCompare> m_callGraph;
std::map<ASTNode const*, std::set<Expression const*>, IdCompare> m_functionAssertions;
/// The current block. /// The current block.
smt::Expression m_currentBlock = smt::Expression(true); smt::Expression m_currentBlock = smt::Expression(true);

View File

@ -140,6 +140,7 @@ public:
void pushSolver(); void pushSolver();
void popSolver(); void popSolver();
void addAssertion(Expression const& _e); void addAssertion(Expression const& _e);
unsigned solverStackHeigh() { return m_assertions.size(); } const
SolverInterface* solver() SolverInterface* solver()
{ {
solAssert(m_solver, ""); solAssert(m_solver, "");

View File

@ -29,9 +29,9 @@ ModelChecker::ModelChecker(
ReadCallback::Callback const& _smtCallback, ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers smt::SMTSolverChoice _enabledSolvers
): ):
m_context(),
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers), m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers), m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers)
m_context()
{ {
} }

View File

@ -62,14 +62,14 @@ public:
static smt::SMTSolverChoice availableSolvers(); static smt::SMTSolverChoice availableSolvers();
private: private:
/// Stores the context of the encoding.
smt::EncodingContext m_context;
/// Bounded Model Checker engine. /// Bounded Model Checker engine.
BMC m_bmc; BMC m_bmc;
/// Constrained Horn Clauses engine. /// Constrained Horn Clauses engine.
CHC m_chc; CHC m_chc;
/// Stores the context of the encoding.
smt::EncodingContext m_context;
}; };
} }

View File

@ -407,19 +407,26 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple)); auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, ""); solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components(); auto const& symbComponents = symbTuple->components();
auto const& tupleComponents = _tuple.components(); auto const* tupleComponents = &_tuple.components();
solAssert(symbComponents.size() == _tuple.components().size(), ""); while (tupleComponents->size() == 1)
{
auto innerTuple = dynamic_pointer_cast<TupleExpression>(tupleComponents->front());
solAssert(innerTuple, "");
tupleComponents = &innerTuple->components();
}
solAssert(symbComponents.size() == tupleComponents->size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i) for (unsigned i = 0; i < symbComponents.size(); ++i)
{ {
auto sComponent = symbComponents.at(i); auto sComponent = symbComponents.at(i);
auto tComponent = tupleComponents.at(i); auto tComponent = tupleComponents->at(i);
if (sComponent && tComponent) if (sComponent && tComponent)
{ {
if (auto varDecl = identifierToVariable(*tComponent)) if (auto varDecl = identifierToVariable(*tComponent))
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl)); m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
else else
{ {
solAssert(m_context.knownExpression(*tComponent), ""); if (!m_context.knownExpression(*tComponent))
createExpr(*tComponent);
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent)); m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
} }
} }
@ -666,7 +673,6 @@ void SMTEncoder::visitAssert(FunctionCall const& _funCall)
auto const& args = _funCall.arguments(); auto const& args = _funCall.arguments();
solAssert(args.size() == 1, ""); solAssert(args.size() == 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
addPathImpliedExpression(expr(*args.front()));
} }
void SMTEncoder::visitRequire(FunctionCall const& _funCall) void SMTEncoder::visitRequire(FunctionCall const& _funCall)

View File

@ -58,11 +58,11 @@ private:
z3::sort z3Sort(smt::Sort const& _sort); z3::sort z3Sort(smt::Sort const& _sort);
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts); z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
std::map<std::string, z3::expr> m_constants;
std::map<std::string, z3::func_decl> m_functions;
z3::context m_context; z3::context m_context;
z3::solver m_solver; z3::solver m_solver;
std::map<std::string, z3::expr> m_constants;
std::map<std::string, z3::func_decl> m_functions;
}; };
} }

View File

@ -899,8 +899,7 @@ h256 const& CompilerStack::Source::swarmHash() const
string const& CompilerStack::Source::ipfsUrl() const string const& CompilerStack::Source::ipfsUrl() const
{ {
if (ipfsUrlCached.empty()) if (ipfsUrlCached.empty())
if (scanner->source().size() < 1024 * 256) ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
return ipfsUrlCached; return ipfsUrlCached;
} }
@ -1373,10 +1372,7 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
MetadataCBOREncoder encoder; MetadataCBOREncoder encoder;
if (m_metadataHash == MetadataHash::IPFS) if (m_metadataHash == MetadataHash::IPFS)
{
solAssert(_metadata.length() < 1024 * 256, "Metadata too large.");
encoder.pushBytes("ipfs", util::ipfsHash(_metadata)); encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
}
else if (m_metadataHash == MetadataHash::Bzzr1) else if (m_metadataHash == MetadataHash::Bzzr1)
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes()); encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
else else

View File

@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
); );
bool isIndexed = false; bool isIndexed = false;
bool isDeclaredConst = false; VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
ASTPointer<OverrideSpecifier> overrides = nullptr; ASTPointer<OverrideSpecifier> overrides = nullptr;
Visibility visibility(Visibility::Default); Visibility visibility(Visibility::Default);
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified; VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
@ -731,7 +731,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (_options.allowIndexed && token == Token::Indexed) if (_options.allowIndexed && token == Token::Indexed)
isIndexed = true; isIndexed = true;
else if (token == Token::Constant) else if (token == Token::Constant)
isDeclaredConst = true; constantness = VariableDeclaration::Constantness::Constant;
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token)) else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
{ {
if (location != VariableDeclaration::Location::Unspecified) if (location != VariableDeclaration::Location::Unspecified)
@ -790,7 +790,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
visibility, visibility,
_options.isStateVariable, _options.isStateVariable,
isIndexed, isIndexed,
isDeclaredConst, constantness,
overrides, overrides,
location location
); );

View File

@ -40,6 +40,21 @@ bytes varintEncoding(size_t _n)
return encoded; return encoded;
} }
bytes encodeByteArray(bytes const& _data)
{
return bytes{0x0a} + varintEncoding(_data.size()) + _data;
}
bytes encodeHash(bytes const& _data)
{
return bytes{0x12, 0x20} + picosha2::hash256(_data);
}
bytes encodeLinkData(bytes const& _data)
{
return bytes{0x12} + varintEncoding(_data.size()) + _data;
}
string base58Encode(bytes const& _data) string base58Encode(bytes const& _data)
{ {
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}; static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
@ -53,36 +68,132 @@ string base58Encode(bytes const& _data)
reverse(output.begin(), output.end()); reverse(output.begin(), output.end());
return output; return output;
} }
struct Chunk
{
Chunk() = default;
Chunk(bytes _hash, size_t _size, size_t _blockSize):
hash(std::move(_hash)),
size(_size),
blockSize(_blockSize)
{}
bytes hash = {};
size_t size = 0;
size_t blockSize = 0;
};
using Chunks = vector<Chunk>;
Chunk combineLinks(Chunks& _links)
{
bytes data = {};
bytes lengths = {};
Chunk chunk = {};
for (Chunk& link: _links)
{
chunk.size += link.size;
chunk.blockSize += link.blockSize;
data += encodeLinkData(
bytes {0x0a} +
varintEncoding(link.hash.size()) +
std::move(link.hash) +
bytes{0x12, 0x00, 0x18} +
varintEncoding(link.blockSize)
);
lengths += bytes{0x20} + varintEncoding(link.size);
}
bytes blockData = data + encodeByteArray(bytes{0x08, 0x02, 0x18} + varintEncoding(chunk.size) + lengths);
chunk.blockSize += blockData.size();
chunk.hash = encodeHash(blockData);
return chunk;
}
Chunks buildNextLevel(Chunks& _currentLevel)
{
size_t const maxChildNum = 174;
Chunks nextLevel;
Chunks links;
for (Chunk& chunk: _currentLevel)
{
links.emplace_back(std::move(chunk.hash), chunk.size, chunk.blockSize);
if (links.size() == maxChildNum)
{
nextLevel.emplace_back(combineLinks(links));
links = {};
}
}
if (!links.empty())
nextLevel.emplace_back(combineLinks(links));
return nextLevel;
}
/// Builds a tree starting from the bottom level where nodes are data nodes.
/// Data nodes should be calculated and passed as the only level in chunk levels
/// Each next level is calculated as following:
/// - Pick up to maxChildNum (174) nodes until a whole level is added, group them and pass to the node in the next level
/// - Do this until the current level has only one node, return the hash in that node
bytes groupChunksBottomUp(Chunks _currentLevel)
{
// when we reach root it will be the only node in that level
while (_currentLevel.size() != 1)
_currentLevel = buildNextLevel(_currentLevel);
// top level's only node stores the hash for file
return _currentLevel.front().hash;
}
} }
bytes solidity::util::ipfsHash(string _data) bytes solidity::util::ipfsHash(string _data)
{ {
assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented."); size_t const maxChunkSize = 1024 * 256;
size_t chunkCount = _data.length() / maxChunkSize + (_data.length() % maxChunkSize > 0 ? 1 : 0);
chunkCount = chunkCount == 0 ? 1 : chunkCount;
bytes lengthAsVarint = varintEncoding(_data.size()); Chunks allChunks;
bytes protobufEncodedData; for (unsigned long chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
// Type: File
protobufEncodedData += bytes{0x08, 0x02};
if (!_data.empty())
{ {
// Data (length delimited bytes) bytes chunkBytes = asBytes(
protobufEncodedData += bytes{0x12}; _data.substr(chunkIndex * maxChunkSize, min(maxChunkSize, _data.length() - chunkIndex * maxChunkSize))
protobufEncodedData += lengthAsVarint; );
protobufEncodedData += asBytes(std::move(_data));
bytes lengthAsVarint = varintEncoding(chunkBytes.size());
bytes protobufEncodedData;
// Type: File
protobufEncodedData += bytes{0x08, 0x02};
if (!chunkBytes.empty())
{
// Data (length delimited bytes)
protobufEncodedData += bytes{0x12};
protobufEncodedData += lengthAsVarint;
protobufEncodedData += chunkBytes;
}
// filesize: length as varint
protobufEncodedData += bytes{0x18} + lengthAsVarint;
// PBDag:
// Data: (length delimited bytes)
bytes blockData = encodeByteArray(protobufEncodedData);
// Multihash: sha2-256, 256 bits
allChunks.emplace_back(
encodeHash(blockData),
chunkBytes.size(),
blockData.size()
);
} }
// filesize: length as varint
protobufEncodedData += bytes{0x18} + lengthAsVarint;
// PBDag: return groupChunksBottomUp(std::move(allChunks));
// Data: (length delimited bytes)
size_t protobufLength = protobufEncodedData.size();
bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData);
// TODO Handle "large" files with multiple blocks
// Multihash: sha2-256, 256 bits
bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData));
return hash;
} }
string solidity::util::ipfsHashBase58(string _data) string solidity::util::ipfsHashBase58(string _data)

View 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;
};
}

View File

@ -22,6 +22,7 @@
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <libyul/SideEffects.h> #include <libyul/SideEffects.h>
#include <libyul/ControlFlowSideEffects.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
@ -42,6 +43,7 @@ struct BuiltinFunction
std::vector<Type> parameters; std::vector<Type> parameters;
std::vector<Type> returns; std::vector<Type> returns;
SideEffects sideEffects; SideEffects sideEffects;
ControlFlowSideEffects controlFlowSideEffects;
/// If true, this is the msize instruction. /// If true, this is the msize instruction.
bool isMSize = false; bool isMSize = false;
/// If true, can only accept literals as arguments and they cannot be moved to variables. /// If true, can only accept literals as arguments and they cannot be moved to variables.

View File

@ -52,6 +52,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.parameters.resize(info.args); f.parameters.resize(info.args);
f.returns.resize(info.ret); f.returns.resize(info.ret);
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction); f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
f.isMSize = _instruction == evmasm::Instruction::MSIZE; f.isMSize = _instruction == evmasm::Instruction::MSIZE;
f.literalArguments = false; f.literalArguments = false;
f.instruction = _instruction; f.instruction = _instruction;

View File

@ -99,6 +99,8 @@ WasmDialect::WasmDialect()
addFunction("unreachable", {}, {}, false); addFunction("unreachable", {}, {}, false);
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false; m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false; m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
addFunction("datasize", {i64}, {i64}, true, true); addFunction("datasize", {i64}, {i64}, true, true);
addFunction("dataoffset", {i64}, {i64}, true, true); addFunction("dataoffset", {i64}, {i64}, true, true);
@ -147,7 +149,13 @@ void WasmDialect::addEthereumExternals()
static string const i64{"i64"}; static string const i64{"i64"};
static string const i32{"i32"}; static string const i32{"i32"};
static string const i32ptr{"i32"}; // Uses "i32" on purpose. static string const i32ptr{"i32"}; // Uses "i32" on purpose.
struct External { string name; vector<string> parameters; vector<string> returns; }; struct External
{
string name;
vector<string> parameters;
vector<string> returns;
ControlFlowSideEffects controlFlowSideEffects = ControlFlowSideEffects{};
};
static vector<External> externals{ static vector<External> externals{
{"getAddress", {i32ptr}, {}}, {"getAddress", {i32ptr}, {}},
{"getExternalBalance", {i32ptr, i32ptr}, {}}, {"getExternalBalance", {i32ptr, i32ptr}, {}},
@ -175,11 +183,11 @@ void WasmDialect::addEthereumExternals()
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}}, {"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
{"getBlockNumber", {}, {i64}}, {"getBlockNumber", {}, {i64}},
{"getTxOrigin", {i32ptr}, {}}, {"getTxOrigin", {i32ptr}, {}},
{"finish", {i32ptr, i32}, {}}, {"finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
{"revert", {i32ptr, i32}, {}}, {"revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
{"getReturnDataSize", {}, {i32}}, {"getReturnDataSize", {}, {i32}},
{"returnDataCopy", {i32ptr, i32, i32}, {}}, {"returnDataCopy", {i32ptr, i32, i32}, {}},
{"selfDestruct", {i32ptr}, {}}, {"selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
{"getBlockTimestamp", {}, {i64}} {"getBlockTimestamp", {}, {i64}}
}; };
for (External const& ext: externals) for (External const& ext: externals)
@ -193,6 +201,7 @@ void WasmDialect::addEthereumExternals()
f.returns.emplace_back(YulString(p)); f.returns.emplace_back(YulString(p));
// TODO some of them are side effect free. // TODO some of them are side effect free.
f.sideEffects = SideEffects::worst(); f.sideEffects = SideEffects::worst();
f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false; f.isMSize = false;
f.sideEffects.invalidatesStorage = (ext.name == "storageStore"); f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
f.literalArguments = false; f.literalArguments = false;

View File

@ -10,6 +10,7 @@ SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)" NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/" # DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
@ -75,7 +76,7 @@ echo "Looking at $NSOURCES .sol files..."
WORKINGDIR=$PWD WORKINGDIR=$PWD
# for solfile in $(find $DEV_DIR -name *.sol) # for solfile in $(find $DEV_DIR -name *.sol)
for solfile in $(find $SYNTAXTESTS_DIR -name *.sol) for solfile in $(find $SYNTAXTESTS_DIR $ASTJSONTESTS_DIR -name *.sol)
do do
echo -n "." echo -n "."
# create a temporary sub-directory # create a temporary sub-directory

View File

@ -127,6 +127,7 @@ static string const g_strInterface = "interface";
static string const g_strYul = "yul"; static string const g_strYul = "yul";
static string const g_strYulDialect = "yul-dialect"; static string const g_strYulDialect = "yul-dialect";
static string const g_strIR = "ir"; static string const g_strIR = "ir";
static string const g_strIROptimized = "ir-optimized";
static string const g_strIPFS = "ipfs"; static string const g_strIPFS = "ipfs";
static string const g_strLicense = "license"; static string const g_strLicense = "license";
static string const g_strLibraries = "libraries"; static string const g_strLibraries = "libraries";
@ -190,6 +191,7 @@ static string const g_argImportAst = g_strImportAst;
static string const g_argInputFile = g_strInputFile; static string const g_argInputFile = g_strInputFile;
static string const g_argYul = g_strYul; static string const g_argYul = g_strYul;
static string const g_argIR = g_strIR; static string const g_argIR = g_strIR;
static string const g_argIROptimized = g_strIROptimized;
static string const g_argEwasm = g_strEwasm; static string const g_argEwasm = g_strEwasm;
static string const g_argLibraries = g_strLibraries; static string const g_argLibraries = g_strLibraries;
static string const g_argLink = g_strLink; static string const g_argLink = g_strLink;
@ -336,36 +338,50 @@ void CommandLineInterface::handleOpcode(string const& _contract)
void CommandLineInterface::handleIR(string const& _contractName) void CommandLineInterface::handleIR(string const& _contractName)
{ {
if (m_args.count(g_argIR)) if (!m_args.count(g_argIR))
return;
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
else
{ {
if (m_args.count(g_argOutputDir)) sout() << "IR:" << endl;
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); sout() << m_compiler->yulIR(_contractName) << endl;
else }
{ }
sout() << "IR:" << endl;
sout() << m_compiler->yulIR(_contractName) << endl; void CommandLineInterface::handleIROptimized(string const& _contractName)
} {
if (!m_args.count(g_argIROptimized))
return;
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName));
else
{
sout() << "Optimized IR:" << endl;
sout() << m_compiler->yulIROptimized(_contractName) << endl;
} }
} }
void CommandLineInterface::handleEwasm(string const& _contractName) void CommandLineInterface::handleEwasm(string const& _contractName)
{ {
if (m_args.count(g_argEwasm)) if (!m_args.count(g_argEwasm))
return;
if (m_args.count(g_argOutputDir))
{ {
if (m_args.count(g_argOutputDir)) createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
{ createFile(
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName)); m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
createFile( asString(m_compiler->ewasmObject(_contractName).bytecode)
m_compiler->filesystemFriendlyName(_contractName) + ".wasm", );
asString(m_compiler->ewasmObject(_contractName).bytecode) }
); else
} {
else sout() << "Ewasm text:" << endl;
{ sout() << m_compiler->ewasm(_contractName) << endl;
sout() << "Ewasm text:" << endl; sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
sout() << m_compiler->ewasm(_contractName) << endl;
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
}
} }
} }
@ -812,6 +828,7 @@ Allowed options)",
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.") (g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
(g_argAbi.c_str(), "ABI specification of the contracts.") (g_argAbi.c_str(), "ABI specification of the contracts.")
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).") (g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
(g_argIROptimized.c_str(), "Optimized intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).") (g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.") (g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.") (g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
@ -1123,7 +1140,7 @@ bool CommandLineInterface::processInput()
m_compiler->setRevertStringBehaviour(m_revertStrings); m_compiler->setRevertStringBehaviour(m_revertStrings);
// TODO: Perhaps we should not compile unless requested // TODO: Perhaps we should not compile unless requested
m_compiler->enableIRGeneration(m_args.count(g_argIR)); m_compiler->enableIRGeneration(m_args.count(g_argIR) || m_args.count(g_argIROptimized));
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm)); m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal(); OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
@ -1631,6 +1648,7 @@ void CommandLineInterface::outputCompilationResults()
handleBytecode(contract); handleBytecode(contract);
handleIR(contract); handleIR(contract);
handleIROptimized(contract);
handleEwasm(contract); handleEwasm(contract);
handleSignatureHashes(contract); handleSignatureHashes(contract);
handleMetadata(contract); handleMetadata(contract);

View File

@ -65,6 +65,7 @@ private:
void handleBinary(std::string const& _contract); void handleBinary(std::string const& _contract);
void handleOpcode(std::string const& _contract); void handleOpcode(std::string const& _contract);
void handleIR(std::string const& _contract); void handleIR(std::string const& _contract);
void handleIROptimized(std::string const& _contract);
void handleEwasm(std::string const& _contract); void handleEwasm(std::string const& _contract);
void handleBytecode(std::string const& _contract); void handleBytecode(std::string const& _contract);
void handleSignatureHashes(std::string const& _contract); void handleSignatureHashes(std::string const& _contract);

View File

@ -53,12 +53,16 @@ ExecutionFramework::ExecutionFramework(langutil::EVMVersion _evmVersion):
m_optimiserSettings = solidity::frontend::OptimiserSettings::full(); m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
else if (solidity::test::CommonOptions::get().optimize) else if (solidity::test::CommonOptions::get().optimize)
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard(); m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
m_evmHost->reset();
reset();
}
void ExecutionFramework::reset()
{
m_evmHost->reset();
for (size_t i = 0; i < 10; i++) for (size_t i = 0; i < 10; i++)
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance = m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
EVMHost::convertToEVMC(u256(1) << 100); EVMHost::convertToEVMC(u256(1) << 100);
} }
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage( std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(

View File

@ -251,6 +251,8 @@ private:
} }
protected: protected:
void reset();
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0); void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
void sendEther(Address const& _to, u256 const& _value); void sendEther(Address const& _to, u256 const& _value);
size_t currentTimestamp(); size_t currentTimestamp();

View File

@ -15,6 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>. along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <test/Common.h>
#include <test/TestCase.h> #include <test/TestCase.h>
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
@ -52,14 +53,18 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
!boost::starts_with(_filename.string(), "."); !boost::starts_with(_filename.string(), ".");
} }
bool TestCase::validateSettings(langutil::EVMVersion) void TestCase::validateSettings()
{ {
if (!m_settings.empty()) if (!m_settings.empty())
throw runtime_error( throw runtime_error(
"Unknown setting(s): " + "Unknown setting(s): " +
util::joinHumanReadable(m_settings | boost::adaptors::map_keys) util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
); );
return true; }
bool TestCase::shouldRun()
{
return m_shouldRun;
} }
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream) pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
@ -157,20 +162,19 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
++_it; ++_it;
} }
bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion) void EVMVersionRestrictedTestCase::validateSettings()
{ {
if (!m_settings.count("EVMVersion")) if (!m_settings.count("EVMVersion"))
return true; return;
string versionString = m_settings["EVMVersion"]; string versionString = m_settings["EVMVersion"];
m_validatedSettings["EVMVersion"] = versionString; m_validatedSettings["EVMVersion"] = versionString;
m_settings.erase("EVMVersion"); m_settings.erase("EVMVersion");
if (!TestCase::validateSettings(_evmVersion)) TestCase::validateSettings();
return false;
if (versionString.empty()) if (versionString.empty())
return true; return;
string comparator; string comparator;
size_t versionBegin = 0; size_t versionBegin = 0;
@ -188,18 +192,23 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer
if (!version) if (!version)
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""}); BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
bool comparisonResult;
if (comparator == ">") if (comparator == ">")
return _evmVersion > version; comparisonResult = evmVersion > version;
else if (comparator == ">=") else if (comparator == ">=")
return _evmVersion >= version; comparisonResult = evmVersion >= version;
else if (comparator == "<") else if (comparator == "<")
return _evmVersion < version; comparisonResult = evmVersion < version;
else if (comparator == "<=") else if (comparator == "<=")
return _evmVersion <= version; comparisonResult = evmVersion <= version;
else if (comparator == "=") else if (comparator == "=")
return _evmVersion == version; comparisonResult = evmVersion == version;
else if (comparator == "!") else if (comparator == "!")
return !(_evmVersion == version); comparisonResult = !(evmVersion == version);
else else
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""}); BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
if (!comparisonResult)
m_shouldRun = false;
} }

View File

@ -70,10 +70,12 @@ public:
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings. /// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting). /// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
virtual void validateSettings();
/// Returns true, if the test case is supported in the current environment and false /// Returns true, if the test case is supported in the current environment and false
/// otherwise which causes this test to be skipped. /// otherwise which causes this test to be skipped.
/// This might check e.g. for restrictions on the EVM version. /// This might check e.g. for restrictions on the EVM version.
virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/); bool shouldRun();
protected: protected:
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file); std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
@ -102,13 +104,14 @@ protected:
std::map<std::string, std::string> m_settings; std::map<std::string, std::string> m_settings;
/// Updated settings after validation. /// Updated settings after validation.
std::map<std::string, std::string> m_validatedSettings; std::map<std::string, std::string> m_validatedSettings;
bool m_shouldRun = true;
}; };
class EVMVersionRestrictedTestCase: public TestCase class EVMVersionRestrictedTestCase: public TestCase
{ {
public: public:
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise. void validateSettings() override;
bool validateSettings(langutil::EVMVersion _evmVersion) override;
}; };
} }

View File

@ -94,7 +94,8 @@ int registerTests(
{ {
stringstream errorStream; stringstream errorStream;
auto testCase = _testCaseCreator(config); auto testCase = _testCaseCreator(config);
if (testCase->validateSettings(solidity::test::CommonOptions::get().evmVersion())) testCase->validateSettings();
if (testCase->shouldRun())
switch (testCase->run(errorStream)) switch (testCase->run(errorStream))
{ {
case TestCase::TestResult::Success: case TestCase::TestResult::Success:

View File

@ -77,6 +77,11 @@ function compileFull()
expect_output=1 expect_output=1
shift; shift;
fi fi
if [[ $1 = '-o' ]]
then
expect_output=2
shift;
fi
local files="$*" local files="$*"
local output local output
@ -93,7 +98,7 @@ function compileFull()
if [[ \ if [[ \
"$exit_code" -ne "$expected_exit_code" || \ "$exit_code" -ne "$expected_exit_code" || \
( $expect_output -eq 0 && -n "$errors" ) || \ ( $expect_output -eq 0 && -n "$errors" ) || \
( $expect_output -ne 0 && -z "$errors" ) \ ( $expect_output -eq 1 && -z "$errors" ) \
]] ]]
then then
printError "Unexpected compilation result:" printError "Unexpected compilation result:"
@ -350,6 +355,10 @@ SOLTMPDIR=$(mktemp -d)
then then
opts="$opts -w" opts="$opts -w"
fi fi
if grep "This may report a warning" "$f" >/dev/null
then
opts="$opts -o"
fi
compileFull $opts "$SOLTMPDIR/$f" compileFull $opts "$SOLTMPDIR/$f"
done done
) )

View 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"
}

View File

@ -0,0 +1,7 @@
contract C {
function g() view public {
assembly { {} }
}
}
// ----

View 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"
}

View 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"
}

View File

@ -0,0 +1,7 @@
contract C {
function g() view public {
assembly { switch 0 default {} }
}
}
// ----

View 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"
}

View File

@ -17,14 +17,18 @@
#include <test/libsolidity/SMTCheckerJSONTest.h> #include <test/libsolidity/SMTCheckerJSONTest.h>
#include <test/Common.h> #include <test/Common.h>
#include <libsolidity/formal/ModelChecker.h>
#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/StandardCompiler.h>
#include <libsolutil/CommonIO.h> #include <libsolutil/CommonIO.h>
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
@ -50,6 +54,9 @@ SMTCheckerJSONTest::SMTCheckerJSONTest(string const& _filename, langutil::EVMVer
!m_smtResponses.isObject() !m_smtResponses.isObject()
) )
BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file.")); BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file."));
if (ModelChecker::availableSolvers().none())
m_shouldRun = false;
} }
TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)

View File

@ -44,6 +44,15 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _ev
} }
else else
m_enabledSolvers = smt::SMTSolverChoice::All(); m_enabledSolvers = smt::SMTSolverChoice::All();
auto available = ModelChecker::availableSolvers();
if (!available.z3)
m_enabledSolvers.z3 = false;
if (!available.cvc4)
m_enabledSolvers.cvc4 = false;
if (m_enabledSolvers.none())
m_shouldRun = false;
} }
TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
@ -55,17 +64,3 @@ TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePr
return printExpectationAndError(_stream, _linePrefix, _formatted) ? TestResult::Success : TestResult::Failure; return printExpectationAndError(_stream, _linePrefix, _formatted) ? TestResult::Success : TestResult::Failure;
} }
bool SMTCheckerTest::validateSettings(langutil::EVMVersion _evmVersion)
{
auto available = ModelChecker::availableSolvers();
if (!available.z3)
m_enabledSolvers.z3 = false;
if (!available.cvc4)
m_enabledSolvers.cvc4 = false;
if (m_enabledSolvers.none())
return false;
return SyntaxTest::validateSettings(_evmVersion);
}

View File

@ -37,8 +37,6 @@ public:
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
bool validateSettings(langutil::EVMVersion _evmVersion) override;
protected: protected:
/// This is set via option SMTSolvers in the test. /// This is set via option SMTSolvers in the test.
/// The possible options are `all`, `z3`, `cvc4`, `none`, /// The possible options are `all`, `z3`, `cvc4`, `none`,

View File

@ -71,6 +71,9 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
m_settings.erase("ABIEncoderV1Only"); m_settings.erase("ABIEncoderV1Only");
} }
if (m_runWithABIEncoderV1Only && solidity::test::CommonOptions::get().useABIEncoderV2)
m_shouldRun = false;
if (m_settings.count("revertStrings")) if (m_settings.count("revertStrings"))
{ {
auto revertStrings = revertStringsFromString(m_settings["revertStrings"]); auto revertStrings = revertStringsFromString(m_settings["revertStrings"]);
@ -90,17 +93,11 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
soltestAssert(!m_tests.empty(), "No tests specified in " + _filename); soltestAssert(!m_tests.empty(), "No tests specified in " + _filename);
} }
bool SemanticTest::validateSettings(langutil::EVMVersion _evmVersion)
{
if (m_runWithABIEncoderV1Only && solidity::test::CommonOptions::get().useABIEncoderV2)
return false;
return EVMVersionRestrictedTestCase::validateSettings(_evmVersion);
}
TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
{ {
for(bool compileViaYul: set<bool>{!m_runWithoutYul, m_runWithYul}) for(bool compileViaYul: set<bool>{!m_runWithoutYul, m_runWithYul})
{ {
reset();
bool success = true; bool success = true;
m_compileViaYul = compileViaYul; m_compileViaYul = compileViaYul;

View File

@ -44,8 +44,6 @@ public:
explicit SemanticTest(std::string const& _filename, langutil::EVMVersion _evmVersion); explicit SemanticTest(std::string const& _filename, langutil::EVMVersion _evmVersion);
bool validateSettings(langutil::EVMVersion _evmVersion) override;
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool _formatted = false) const override; void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool _formatted = false) const override;
void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix = "") const override; void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix = "") const override;

View File

@ -540,7 +540,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
"default", "default",
"define", "define",
"final", "final",
"immutable",
"implements", "implements",
"in", "in",
"inline", "inline",

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -4,7 +4,7 @@ contract C {
} }
function f(bool x) public returns (uint) { function f(bool x) public returns (uint) {
// Set the gas to make this work on pre-byzantium VMs // Set the gas to make this work on pre-byzantium VMs
try this.g.gas(8000)(x) { try this.g{gas: 8000}(x) {
return 1; return 1;
} catch { } catch {
return 2; return 2;

View File

@ -4,7 +4,7 @@ contract C {
} }
function f(bool x) public returns (uint) { function f(bool x) public returns (uint) {
// Set the gas to make this work on pre-byzantium VMs // Set the gas to make this work on pre-byzantium VMs
try this.g.gas(8000)(x) { try this.g{gas: 8000}(x) {
return 1; return 1;
} catch { } catch {
return 2; return 2;

View File

@ -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

View 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

View 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)

View File

@ -6,23 +6,17 @@ contract c {
x = x + 1; x = x + 1;
return x; return x;
} }
function g(bool a) public returns (bool) { function g() public returns (bool) {
bool b; bool b;
if (a) {
x = 0;
b = (f() == 0) && (f() == 0);
assert(x == 1);
assert(!b);
} else {
x = 100; x = 100;
b = (f() > 0) && (f() > 0); b = f() > 0;
assert(x == 102); assert(x == 102);
// Should fail. // Should fail.
assert(!b); assert(!b);
}
return b; return b;
} }
} }
// ---- // ----
// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (362-372): Assertion violation happens here // Warning: (202-218): Assertion violation happens here
// Warning: (242-252): Assertion violation happens here

View File

@ -19,4 +19,6 @@ contract A is B {
} }
} }
// ---- // ----
// Warning: (217-222): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (265-270): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (253-271): Assertion violation happens here // Warning: (253-271): Assertion violation happens here

View File

@ -26,4 +26,6 @@ contract A is B2, B1 {
} }
// ---- // ----
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (342-347): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (330-348): Assertion violation happens here // Warning: (330-348): Assertion violation happens here

View File

@ -26,4 +26,6 @@ contract A is B2, B1 {
} }
// ---- // ----
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (214-219): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (342-347): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (330-348): Assertion violation happens here // Warning: (330-348): Assertion violation happens here

View File

@ -31,4 +31,7 @@ contract A is B2, B1 {
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (362-378): Assertion violation happens here // Warning: (362-378): Assertion violation happens here

View File

@ -26,4 +26,5 @@ contract A is B {
} }
// ---- // ----
// Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here // Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (261-266): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (356-370): Assertion violation happens here // Warning: (356-370): Assertion violation happens here

View File

@ -14,3 +14,4 @@ contract A is C {
} }
// ---- // ----
// Warning: (148-162): Assertion violation happens here // Warning: (148-162): Assertion violation happens here
// Warning: (166-182): Assertion violation happens here

View File

@ -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.

View File

@ -2,40 +2,22 @@ pragma experimental SMTChecker;
contract C contract C
{ {
uint x;
uint y; uint y;
uint z;
function f() public { function f() public {
if (x == 1) if (y != 1)
x = 2; g();
else
x = 1;
g();
assert(y == 1); assert(y == 1);
} }
function g() public { function g() internal {
y = 1; y = 1;
h(); h();
assert(z == 1);
} }
function h() public { function h() internal {
z = 1;
x = 1;
f(); f();
// This fails for the following calls to the contract: assert(y == 1);
// h()
// g() h()
// It does not fail for f() g() h() because in that case
// h() will not inline f() since it already is in the callstack.
assert(x == 1);
} }
} }
// ---- // ----
// Warning: (271-274): Assertion checker does not support recursive function calls.
// Warning: (140-143): Assertion checker does not support recursive function calls.
// Warning: (483-497): Assertion violation happens here
// Warning: (201-204): Assertion checker does not support recursive function calls.
// Warning: (483-497): Assertion violation happens here

View File

@ -14,4 +14,3 @@ contract C
} }
// ---- // ----
// Warning: (111-114): Assertion checker does not support recursive function calls.

View File

@ -22,5 +22,4 @@ contract C
} }
} }
// ---- // ----
// Warning: (206-209): Assertion checker does not support recursive function calls. // Warning: (130-144): Error trying to invoke SMT solver.
// Warning: (111-114): Assertion checker does not support recursive function calls.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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