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

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,
which is especially useful when you are enhancing the language by writing libraries.
The language used for inline assembly in Solidity is called `Yul <yul>`_
The language used for inline assembly in Solidity is called :ref:`Yul <yul>`
and it is documented in its own section. This section will only cover
how the inline assembly code can interface with the surrounding Solidity code.
@ -24,7 +24,7 @@ how the inline assembly code can interface with the surrounding Solidity code.
An inline assembly block is marked by ``assembly { ... }``, where the code inside
the curly braces is code in the `Yul <yul>`_ language.
the curly braces is code in the :ref:`Yul <yul>` language.
The inline assembly code can access local Solidity variables as explained below.

View File

@ -888,5 +888,9 @@
"0.6.3": {
"bugs": [],
"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
/// can be pre-computed. It is just there for illustration.
/// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(bytes20(keccak256(abi.encodePacked(
address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff),
address(this),
salt,

View File

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

View File

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

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)
{
if (_item.type() != Operation)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -540,7 +540,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
"default",
"define",
"final",
"immutable",
"implements",
"in",
"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) {
// Set the gas to make this work on pre-byzantium VMs
try this.g.gas(8000)(x) {
try this.g{gas: 8000}(x) {
return 1;
} catch {
return 2;

View File

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

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;
return x;
}
function g(bool a) public returns (bool) {
function g() public returns (bool) {
bool b;
if (a) {
x = 0;
b = (f() == 0) && (f() == 0);
assert(x == 1);
assert(!b);
} else {
x = 100;
b = (f() > 0) && (f() > 0);
b = f() > 0;
assert(x == 102);
// Should fail.
assert(!b);
}
return b;
}
}
// ----
// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (362-372): Assertion violation happens here
// Warning: (202-218): Assertion violation happens here
// Warning: (242-252): Assertion violation happens here

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

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: (342-347): Overflow (resulting value larger than 2**256 - 1) 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: (342-347): Overflow (resulting value larger than 2**256 - 1) 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: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (239-244): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (262-267): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (174-179): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning: (362-378): Assertion violation happens here

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: (356-370): Assertion violation happens here

View File

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

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: (111-114): Assertion checker does not support recursive function calls.
// Warning: (130-144): Error trying to invoke SMT solver.

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