chriseth
9 years ago
78 changed files with 1 additions and 33088 deletions
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,94 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Forward-declarations of AST classes. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <memory> |
|||
#include <vector> |
|||
|
|||
// Forward-declare all AST node types
|
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class ASTNode; |
|||
class SourceUnit; |
|||
class ImportDirective; |
|||
class Declaration; |
|||
class ContractDefinition; |
|||
class InheritanceSpecifier; |
|||
class StructDefinition; |
|||
class EnumDefinition; |
|||
class EnumValue; |
|||
class ParameterList; |
|||
class FunctionDefinition; |
|||
class VariableDeclaration; |
|||
class ModifierDefinition; |
|||
class ModifierInvocation; |
|||
class EventDefinition; |
|||
class MagicVariableDeclaration; |
|||
class TypeName; |
|||
class ElementaryTypeName; |
|||
class UserDefinedTypeName; |
|||
class Mapping; |
|||
class ArrayTypeName; |
|||
class Statement; |
|||
class Block; |
|||
class PlaceholderStatement; |
|||
class IfStatement; |
|||
class BreakableStatement; |
|||
class WhileStatement; |
|||
class ForStatement; |
|||
class Continue; |
|||
class Break; |
|||
class Return; |
|||
class VariableDeclarationStatement; |
|||
class ExpressionStatement; |
|||
class Expression; |
|||
class Assignment; |
|||
class UnaryOperation; |
|||
class BinaryOperation; |
|||
class FunctionCall; |
|||
class NewExpression; |
|||
class MemberAccess; |
|||
class IndexAccess; |
|||
class PrimaryExpression; |
|||
class Identifier; |
|||
class ElementaryTypeNameExpression; |
|||
class Literal; |
|||
|
|||
class VariableScope; |
|||
|
|||
// Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do
|
|||
// not do reference counting but point to a special memory area that is completely released
|
|||
// explicitly.
|
|||
template <class T> |
|||
using ASTPointer = std::shared_ptr<T>; |
|||
|
|||
using ASTString = std::string; |
|||
|
|||
|
|||
} |
|||
} |
@ -1,437 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
* Converts the AST into json format |
|||
*/ |
|||
|
|||
#include <libsolidity/ASTJsonConverter.h> |
|||
#include <libsolidity/AST.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
void ASTJsonConverter::addKeyValue(Json::Value& _obj, string const& _key, string const& _val) |
|||
{ |
|||
// special handling for booleans
|
|||
if (_key == "const" || _key == "public" || _key == "local" || |
|||
_key == "lvalue" || _key == "local_lvalue" || _key == "prefix") |
|||
_obj[_key] = (_val == "1") ? true : false; |
|||
else |
|||
// else simply add it as a string
|
|||
_obj[_key] = _val; |
|||
} |
|||
|
|||
void ASTJsonConverter::addJsonNode(string const& _nodeName, |
|||
initializer_list<pair<string const, string const>> _list, |
|||
bool _hasChildren = false) |
|||
{ |
|||
Json::Value node; |
|||
|
|||
node["name"] = _nodeName; |
|||
if (_list.size() != 0) |
|||
{ |
|||
Json::Value attrs; |
|||
for (auto& e: _list) |
|||
addKeyValue(attrs, e.first, e.second); |
|||
node["attributes"] = attrs; |
|||
} |
|||
|
|||
m_jsonNodePtrs.top()->append(node); |
|||
|
|||
if (_hasChildren) |
|||
{ |
|||
Json::Value& addedNode = (*m_jsonNodePtrs.top())[m_jsonNodePtrs.top()->size() - 1]; |
|||
Json::Value children(Json::arrayValue); |
|||
addedNode["children"] = children; |
|||
m_jsonNodePtrs.push(&addedNode["children"]); |
|||
} |
|||
} |
|||
|
|||
ASTJsonConverter::ASTJsonConverter(ASTNode const& _ast): m_ast(&_ast) |
|||
{ |
|||
Json::Value children(Json::arrayValue); |
|||
|
|||
m_astJson["name"] = "root"; |
|||
m_astJson["children"] = children; |
|||
m_jsonNodePtrs.push(&m_astJson["children"]); |
|||
} |
|||
|
|||
void ASTJsonConverter::print(ostream& _stream) |
|||
{ |
|||
process(); |
|||
_stream << m_astJson; |
|||
} |
|||
|
|||
Json::Value const& ASTJsonConverter::json() |
|||
{ |
|||
process(); |
|||
return m_astJson; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ImportDirective const& _node) |
|||
{ |
|||
addJsonNode("Import", { make_pair("file", _node.getIdentifier())}); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ContractDefinition const& _node) |
|||
{ |
|||
addJsonNode("Contract", { make_pair("name", _node.getName()) }, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(StructDefinition const& _node) |
|||
{ |
|||
addJsonNode("Struct", { make_pair("name", _node.getName()) }, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ParameterList const&) |
|||
{ |
|||
addJsonNode("ParameterList", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(FunctionDefinition const& _node) |
|||
{ |
|||
addJsonNode("Function", |
|||
{ make_pair("name", _node.getName()), |
|||
make_pair("public", boost::lexical_cast<std::string>(_node.isPublic())), |
|||
make_pair("const", boost::lexical_cast<std::string>(_node.isDeclaredConst())) }, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(VariableDeclaration const& _node) |
|||
{ |
|||
addJsonNode("VariableDeclaration", { make_pair("name", _node.getName()) }, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(TypeName const&) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ElementaryTypeName const& _node) |
|||
{ |
|||
addJsonNode("ElementaryTypeName", { make_pair("name", Token::toString(_node.getTypeName())) }); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) |
|||
{ |
|||
addJsonNode("UserDefinedTypeName", { make_pair("name", _node.getName()) }); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Mapping const&) |
|||
{ |
|||
addJsonNode("Mapping", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Block const&) |
|||
{ |
|||
addJsonNode("Block", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(IfStatement const&) |
|||
{ |
|||
addJsonNode("IfStatement", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(WhileStatement const&) |
|||
{ |
|||
addJsonNode("WhileStatement", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ForStatement const&) |
|||
{ |
|||
addJsonNode("ForStatement", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Continue const&) |
|||
{ |
|||
addJsonNode("Continue", {}); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Break const&) |
|||
{ |
|||
addJsonNode("Break", {}); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Return const&) |
|||
{ |
|||
addJsonNode("Return", {}, true);; |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(VariableDeclarationStatement const&) |
|||
{ |
|||
addJsonNode("VariableDefinition", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ExpressionStatement const&) |
|||
{ |
|||
addJsonNode("ExpressionStatement", {}, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Assignment const& _node) |
|||
{ |
|||
addJsonNode("Assignment", |
|||
{ make_pair("operator", Token::toString(_node.getAssignmentOperator())), |
|||
make_pair("type", getType(_node)) }, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(UnaryOperation const& _node) |
|||
{ |
|||
addJsonNode("UnaryOperation", |
|||
{ make_pair("prefix", boost::lexical_cast<std::string>(_node.isPrefixOperation())), |
|||
make_pair("operator", Token::toString(_node.getOperator())), |
|||
make_pair("type", getType(_node)) }, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(BinaryOperation const& _node) |
|||
{ |
|||
addJsonNode("BinaryOperation", |
|||
{ make_pair("operator", Token::toString(_node.getOperator())), |
|||
make_pair("type", getType(_node))}, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(FunctionCall const& _node) |
|||
{ |
|||
addJsonNode("FunctionCall", |
|||
{ make_pair("type_conversion", boost::lexical_cast<std::string>(_node.isTypeConversion())), |
|||
make_pair("type", getType(_node)) }, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(NewExpression const& _node) |
|||
{ |
|||
addJsonNode("NewExpression", { make_pair("type", getType(_node)) }, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(MemberAccess const& _node) |
|||
{ |
|||
addJsonNode("MemberAccess", |
|||
{ make_pair("member_name", _node.getMemberName()), |
|||
make_pair("type", getType(_node)) }, |
|||
true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(IndexAccess const& _node) |
|||
{ |
|||
addJsonNode("IndexAccess", { make_pair("type", getType(_node)) }, true); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Identifier const& _node) |
|||
{ |
|||
addJsonNode("Identifier", |
|||
{ make_pair("value", _node.getName()), make_pair("type", getType(_node)) }); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) |
|||
{ |
|||
addJsonNode("ElementaryTypenameExpression", |
|||
{ make_pair("value", Token::toString(_node.getTypeToken())), make_pair("type", getType(_node)) }); |
|||
return true; |
|||
} |
|||
|
|||
bool ASTJsonConverter::visit(Literal const& _node) |
|||
{ |
|||
char const* tokenString = Token::toString(_node.getToken()); |
|||
addJsonNode("Literal", |
|||
{ make_pair("string", (tokenString) ? tokenString : "null"), |
|||
make_pair("value", _node.getValue()), |
|||
make_pair("type", getType(_node)) }); |
|||
return true; |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ImportDirective const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ContractDefinition const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(StructDefinition const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ParameterList const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(FunctionDefinition const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(VariableDeclaration const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(TypeName const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ElementaryTypeName const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(UserDefinedTypeName const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Mapping const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Block const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(IfStatement const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(WhileStatement const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ForStatement const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Continue const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Break const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Return const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(VariableDeclarationStatement const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ExpressionStatement const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Assignment const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(UnaryOperation const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(BinaryOperation const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(FunctionCall const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(NewExpression const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(MemberAccess const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(IndexAccess const&) |
|||
{ |
|||
goUp(); |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Identifier const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(ElementaryTypeNameExpression const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::endVisit(Literal const&) |
|||
{ |
|||
} |
|||
|
|||
void ASTJsonConverter::process() |
|||
{ |
|||
if (!processed) |
|||
m_ast->accept(*this); |
|||
processed = true; |
|||
} |
|||
|
|||
string ASTJsonConverter::getType(Expression const& _expression) |
|||
{ |
|||
return (_expression.getType()) ? _expression.getType()->toString() : "Unknown"; |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,130 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
* Converts the AST into json format |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <stack> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libsolidity/Utils.h> |
|||
#include <json/json.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/**
|
|||
* Converter of the AST into JSON format |
|||
*/ |
|||
class ASTJsonConverter: public ASTConstVisitor |
|||
{ |
|||
public: |
|||
/// Create a converter to JSON for the given abstract syntax tree.
|
|||
ASTJsonConverter(ASTNode const& _ast); |
|||
/// Output the json representation of the AST to _stream.
|
|||
void print(std::ostream& _stream); |
|||
Json::Value const& json(); |
|||
|
|||
bool visit(ImportDirective const& _node) override; |
|||
bool visit(ContractDefinition const& _node) override; |
|||
bool visit(StructDefinition const& _node) override; |
|||
bool visit(ParameterList const& _node) override; |
|||
bool visit(FunctionDefinition const& _node) override; |
|||
bool visit(VariableDeclaration const& _node) override; |
|||
bool visit(TypeName const& _node) override; |
|||
bool visit(ElementaryTypeName const& _node) override; |
|||
bool visit(UserDefinedTypeName const& _node) override; |
|||
bool visit(Mapping const& _node) override; |
|||
bool visit(Block const& _node) override; |
|||
bool visit(IfStatement const& _node) override; |
|||
bool visit(WhileStatement const& _node) override; |
|||
bool visit(ForStatement const& _node) override; |
|||
bool visit(Continue const& _node) override; |
|||
bool visit(Break const& _node) override; |
|||
bool visit(Return const& _node) override; |
|||
bool visit(VariableDeclarationStatement const& _node) override; |
|||
bool visit(ExpressionStatement const& _node) override; |
|||
bool visit(Assignment const& _node) override; |
|||
bool visit(UnaryOperation const& _node) override; |
|||
bool visit(BinaryOperation const& _node) override; |
|||
bool visit(FunctionCall const& _node) override; |
|||
bool visit(NewExpression const& _node) override; |
|||
bool visit(MemberAccess const& _node) override; |
|||
bool visit(IndexAccess const& _node) override; |
|||
bool visit(Identifier const& _node) override; |
|||
bool visit(ElementaryTypeNameExpression const& _node) override; |
|||
bool visit(Literal const& _node) override; |
|||
|
|||
void endVisit(ImportDirective const&) override; |
|||
void endVisit(ContractDefinition const&) override; |
|||
void endVisit(StructDefinition const&) override; |
|||
void endVisit(ParameterList const&) override; |
|||
void endVisit(FunctionDefinition const&) override; |
|||
void endVisit(VariableDeclaration const&) override; |
|||
void endVisit(TypeName const&) override; |
|||
void endVisit(ElementaryTypeName const&) override; |
|||
void endVisit(UserDefinedTypeName const&) override; |
|||
void endVisit(Mapping const&) override; |
|||
void endVisit(Block const&) override; |
|||
void endVisit(IfStatement const&) override; |
|||
void endVisit(WhileStatement const&) override; |
|||
void endVisit(ForStatement const&) override; |
|||
void endVisit(Continue const&) override; |
|||
void endVisit(Break const&) override; |
|||
void endVisit(Return const&) override; |
|||
void endVisit(VariableDeclarationStatement const&) override; |
|||
void endVisit(ExpressionStatement const&) override; |
|||
void endVisit(Assignment const&) override; |
|||
void endVisit(UnaryOperation const&) override; |
|||
void endVisit(BinaryOperation const&) override; |
|||
void endVisit(FunctionCall const&) override; |
|||
void endVisit(NewExpression const&) override; |
|||
void endVisit(MemberAccess const&) override; |
|||
void endVisit(IndexAccess const&) override; |
|||
void endVisit(Identifier const&) override; |
|||
void endVisit(ElementaryTypeNameExpression const&) override; |
|||
void endVisit(Literal const&) override; |
|||
|
|||
private: |
|||
void process(); |
|||
void addKeyValue(Json::Value& _obj, std::string const& _key, std::string const& _val); |
|||
void addJsonNode(std::string const& _nodeName, |
|||
std::initializer_list<std::pair<std::string const, std::string const>> _list, |
|||
bool _hasChildren); |
|||
std::string getType(Expression const& _expression); |
|||
inline void goUp() |
|||
{ |
|||
solAssert(!m_jsonNodePtrs.empty(), "Uneven json nodes stack. Internal error."); |
|||
m_jsonNodePtrs.pop(); |
|||
} |
|||
|
|||
bool processed = false; |
|||
Json::Value m_astJson; |
|||
std::stack<Json::Value*> m_jsonNodePtrs; |
|||
std::string m_source; |
|||
ASTNode const* m_ast; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,538 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. |
|||
*/ |
|||
|
|||
#include <libsolidity/ASTPrinter.h> |
|||
#include <libsolidity/AST.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
ASTPrinter::ASTPrinter( |
|||
ASTNode const& _ast, |
|||
string const& _source, |
|||
GasEstimator::ASTGasConsumption const& _gasCosts |
|||
): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts) |
|||
{ |
|||
} |
|||
|
|||
void ASTPrinter::print(ostream& _stream) |
|||
{ |
|||
m_ostream = &_stream; |
|||
m_ast->accept(*this); |
|||
m_ostream = nullptr; |
|||
} |
|||
|
|||
|
|||
bool ASTPrinter::visit(ImportDirective const& _node) |
|||
{ |
|||
writeLine("ImportDirective \"" + _node.getIdentifier() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ContractDefinition const& _node) |
|||
{ |
|||
writeLine("ContractDefinition \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(InheritanceSpecifier const& _node) |
|||
{ |
|||
writeLine("InheritanceSpecifier \"" + _node.getName()->getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(StructDefinition const& _node) |
|||
{ |
|||
writeLine("StructDefinition \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(EnumDefinition const& _node) |
|||
{ |
|||
writeLine("EnumDefinition \"" + _node.getName() + "\""); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(EnumValue const& _node) |
|||
{ |
|||
writeLine("EnumValue \"" + _node.getName() + "\""); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ParameterList const& _node) |
|||
{ |
|||
writeLine("ParameterList"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(FunctionDefinition const& _node) |
|||
{ |
|||
writeLine("FunctionDefinition \"" + _node.getName() + "\"" + |
|||
(_node.isPublic() ? " - public" : "") + |
|||
(_node.isDeclaredConst() ? " - const" : "")); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(VariableDeclaration const& _node) |
|||
{ |
|||
writeLine("VariableDeclaration \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ModifierDefinition const& _node) |
|||
{ |
|||
writeLine("ModifierDefinition \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ModifierInvocation const& _node) |
|||
{ |
|||
writeLine("ModifierInvocation \"" + _node.getName()->getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(EventDefinition const& _node) |
|||
{ |
|||
writeLine("EventDefinition \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(TypeName const& _node) |
|||
{ |
|||
writeLine("TypeName"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ElementaryTypeName const& _node) |
|||
{ |
|||
writeLine(string("ElementaryTypeName ") + Token::toString(_node.getTypeName())); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(UserDefinedTypeName const& _node) |
|||
{ |
|||
writeLine("UserDefinedTypeName \"" + _node.getName() + "\""); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Mapping const& _node) |
|||
{ |
|||
writeLine("Mapping"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ArrayTypeName const& _node) |
|||
{ |
|||
writeLine("ArrayTypeName"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Block const& _node) |
|||
{ |
|||
writeLine("Block"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(PlaceholderStatement const& _node) |
|||
{ |
|||
writeLine("PlaceholderStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(IfStatement const& _node) |
|||
{ |
|||
writeLine("IfStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(WhileStatement const& _node) |
|||
{ |
|||
writeLine("WhileStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ForStatement const& _node) |
|||
{ |
|||
writeLine("ForStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Continue const& _node) |
|||
{ |
|||
writeLine("Continue"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Break const& _node) |
|||
{ |
|||
writeLine("Break"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Return const& _node) |
|||
{ |
|||
writeLine("Return"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(VariableDeclarationStatement const& _node) |
|||
{ |
|||
writeLine("VariableDeclarationStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ExpressionStatement const& _node) |
|||
{ |
|||
writeLine("ExpressionStatement"); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Assignment const& _node) |
|||
{ |
|||
writeLine(string("Assignment using operator ") + Token::toString(_node.getAssignmentOperator())); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(UnaryOperation const& _node) |
|||
{ |
|||
writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + |
|||
") " + Token::toString(_node.getOperator())); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(BinaryOperation const& _node) |
|||
{ |
|||
writeLine(string("BinaryOperation using operator ") + Token::toString(_node.getOperator())); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(FunctionCall const& _node) |
|||
{ |
|||
writeLine("FunctionCall"); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(NewExpression const& _node) |
|||
{ |
|||
writeLine("NewExpression"); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(MemberAccess const& _node) |
|||
{ |
|||
writeLine("MemberAccess to member " + _node.getMemberName()); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(IndexAccess const& _node) |
|||
{ |
|||
writeLine("IndexAccess"); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Identifier const& _node) |
|||
{ |
|||
writeLine(string("Identifier ") + _node.getName()); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(ElementaryTypeNameExpression const& _node) |
|||
{ |
|||
writeLine(string("ElementaryTypeNameExpression ") + Token::toString(_node.getTypeToken())); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
bool ASTPrinter::visit(Literal const& _node) |
|||
{ |
|||
char const* tokenString = Token::toString(_node.getToken()); |
|||
if (!tokenString) |
|||
tokenString = "[no token]"; |
|||
writeLine(string("Literal, token: ") + tokenString + " value: " + _node.getValue()); |
|||
printType(_node); |
|||
printSourcePart(_node); |
|||
return goDeeper(); |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ImportDirective const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ContractDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(InheritanceSpecifier const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(StructDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(EnumDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(EnumValue const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ParameterList const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(FunctionDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(VariableDeclaration const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ModifierDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ModifierInvocation const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(EventDefinition const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(TypeName const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ElementaryTypeName const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(UserDefinedTypeName const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Mapping const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ArrayTypeName const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Block const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(PlaceholderStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(IfStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(WhileStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ForStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Continue const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Break const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Return const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(VariableDeclarationStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ExpressionStatement const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Assignment const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(UnaryOperation const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(BinaryOperation const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(FunctionCall const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(NewExpression const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(MemberAccess const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(IndexAccess const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Identifier const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(ElementaryTypeNameExpression const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::endVisit(Literal const&) |
|||
{ |
|||
m_indentation--; |
|||
} |
|||
|
|||
void ASTPrinter::printSourcePart(ASTNode const& _node) |
|||
{ |
|||
if (m_gasCosts.count(&_node)) |
|||
*m_ostream << getIndentation() << " Gas costs: " << m_gasCosts.at(&_node) << endl; |
|||
if (!m_source.empty()) |
|||
{ |
|||
SourceLocation const& location(_node.getLocation()); |
|||
*m_ostream << getIndentation() << " Source: " |
|||
<< escaped(m_source.substr(location.start, location.end - location.start), false) << endl; |
|||
} |
|||
} |
|||
|
|||
void ASTPrinter::printType(Expression const& _expression) |
|||
{ |
|||
if (_expression.getType()) |
|||
*m_ostream << getIndentation() << " Type: " << _expression.getType()->toString() << "\n"; |
|||
else |
|||
*m_ostream << getIndentation() << " Type unknown.\n"; |
|||
} |
|||
|
|||
string ASTPrinter::getIndentation() const |
|||
{ |
|||
return string(m_indentation * 2, ' '); |
|||
} |
|||
|
|||
void ASTPrinter::writeLine(string const& _line) |
|||
{ |
|||
*m_ostream << getIndentation() << _line << endl; |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,141 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/GasEstimator.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/**
|
|||
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable) for debugging purposes. |
|||
*/ |
|||
class ASTPrinter: public ASTConstVisitor |
|||
{ |
|||
public: |
|||
/// Create a printer for the given abstract syntax tree. If the source is specified,
|
|||
/// the corresponding parts of the source are printed with each node.
|
|||
ASTPrinter( |
|||
ASTNode const& _ast, |
|||
std::string const& _source = std::string(), |
|||
GasEstimator::ASTGasConsumption const& _gasCosts = GasEstimator::ASTGasConsumption() |
|||
); |
|||
/// Output the string representation of the AST to _stream.
|
|||
void print(std::ostream& _stream); |
|||
|
|||
bool visit(ImportDirective const& _node) override; |
|||
bool visit(ContractDefinition const& _node) override; |
|||
bool visit(InheritanceSpecifier const& _node) override; |
|||
bool visit(StructDefinition const& _node) override; |
|||
bool visit(EnumDefinition const& _node) override; |
|||
bool visit(EnumValue const& _node) override; |
|||
bool visit(ParameterList const& _node) override; |
|||
bool visit(FunctionDefinition const& _node) override; |
|||
bool visit(VariableDeclaration const& _node) override; |
|||
bool visit(ModifierDefinition const& _node) override; |
|||
bool visit(ModifierInvocation const& _node) override; |
|||
bool visit(EventDefinition const& _node) override; |
|||
bool visit(TypeName const& _node) override; |
|||
bool visit(ElementaryTypeName const& _node) override; |
|||
bool visit(UserDefinedTypeName const& _node) override; |
|||
bool visit(Mapping const& _node) override; |
|||
bool visit(ArrayTypeName const& _node) override; |
|||
bool visit(Block const& _node) override; |
|||
bool visit(PlaceholderStatement const& _node) override; |
|||
bool visit(IfStatement const& _node) override; |
|||
bool visit(WhileStatement const& _node) override; |
|||
bool visit(ForStatement const& _node) override; |
|||
bool visit(Continue const& _node) override; |
|||
bool visit(Break const& _node) override; |
|||
bool visit(Return const& _node) override; |
|||
bool visit(VariableDeclarationStatement const& _node) override; |
|||
bool visit(ExpressionStatement const& _node) override; |
|||
bool visit(Assignment const& _node) override; |
|||
bool visit(UnaryOperation const& _node) override; |
|||
bool visit(BinaryOperation const& _node) override; |
|||
bool visit(FunctionCall const& _node) override; |
|||
bool visit(NewExpression const& _node) override; |
|||
bool visit(MemberAccess const& _node) override; |
|||
bool visit(IndexAccess const& _node) override; |
|||
bool visit(Identifier const& _node) override; |
|||
bool visit(ElementaryTypeNameExpression const& _node) override; |
|||
bool visit(Literal const& _node) override; |
|||
|
|||
void endVisit(ImportDirective const&) override; |
|||
void endVisit(ContractDefinition const&) override; |
|||
void endVisit(InheritanceSpecifier const&) override; |
|||
void endVisit(StructDefinition const&) override; |
|||
void endVisit(EnumDefinition const&) override; |
|||
void endVisit(EnumValue const&) override; |
|||
void endVisit(ParameterList const&) override; |
|||
void endVisit(FunctionDefinition const&) override; |
|||
void endVisit(VariableDeclaration const&) override; |
|||
void endVisit(ModifierDefinition const&) override; |
|||
void endVisit(ModifierInvocation const&) override; |
|||
void endVisit(EventDefinition const&) override; |
|||
void endVisit(TypeName const&) override; |
|||
void endVisit(ElementaryTypeName const&) override; |
|||
void endVisit(UserDefinedTypeName const&) override; |
|||
void endVisit(Mapping const&) override; |
|||
void endVisit(ArrayTypeName const&) override; |
|||
void endVisit(Block const&) override; |
|||
void endVisit(PlaceholderStatement const&) override; |
|||
void endVisit(IfStatement const&) override; |
|||
void endVisit(WhileStatement const&) override; |
|||
void endVisit(ForStatement const&) override; |
|||
void endVisit(Continue const&) override; |
|||
void endVisit(Break const&) override; |
|||
void endVisit(Return const&) override; |
|||
void endVisit(VariableDeclarationStatement const&) override; |
|||
void endVisit(ExpressionStatement const&) override; |
|||
void endVisit(Assignment const&) override; |
|||
void endVisit(UnaryOperation const&) override; |
|||
void endVisit(BinaryOperation const&) override; |
|||
void endVisit(FunctionCall const&) override; |
|||
void endVisit(NewExpression const&) override; |
|||
void endVisit(MemberAccess const&) override; |
|||
void endVisit(IndexAccess const&) override; |
|||
void endVisit(Identifier const&) override; |
|||
void endVisit(ElementaryTypeNameExpression const&) override; |
|||
void endVisit(Literal const&) override; |
|||
|
|||
private: |
|||
void printSourcePart(ASTNode const& _node); |
|||
void printType(Expression const& _expression); |
|||
std::string getIndentation() const; |
|||
void writeLine(std::string const& _line); |
|||
bool goDeeper() { m_indentation++; return true; } |
|||
|
|||
int m_indentation; |
|||
std::string m_source; |
|||
ASTNode const* m_ast; |
|||
GasEstimator::ASTGasConsumption m_gasCosts; |
|||
std::ostream* m_ostream; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,48 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Utilities to work with the AST. |
|||
*/ |
|||
|
|||
#include <libsolidity/ASTUtils.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::solidity; |
|||
|
|||
|
|||
|
|||
ASTNode const* LocationFinder::leastUpperBound() |
|||
{ |
|||
m_bestMatch = nullptr; |
|||
for (ASTNode const* rootNode: m_rootNodes) |
|||
rootNode->accept(*this); |
|||
|
|||
return m_bestMatch; |
|||
} |
|||
|
|||
bool LocationFinder::visitNode(const ASTNode& _node) |
|||
{ |
|||
if (_node.getLocation().contains(m_location)) |
|||
{ |
|||
m_bestMatch = &_node; |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
@ -1,54 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Utilities to work with the AST. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libevmasm/SourceLocation.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class LocationFinder: private ASTConstVisitor |
|||
{ |
|||
public: |
|||
LocationFinder(SourceLocation const& _location, std::vector<ASTNode const*> _rootNodes): |
|||
m_rootNodes(_rootNodes), m_location(_location) |
|||
{ |
|||
} |
|||
|
|||
/// @returns the "closest" (in the sense of most-leafward) AST node which is a descendant of
|
|||
/// _node and whose source location contains _location.
|
|||
ASTNode const* leastUpperBound(); |
|||
|
|||
private: |
|||
bool visitNode(ASTNode const& _node); |
|||
|
|||
std::vector<ASTNode const*> m_rootNodes; |
|||
SourceLocation m_location; |
|||
ASTNode const* m_bestMatch = nullptr; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,286 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* AST visitor base class. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <functional> |
|||
#include <vector> |
|||
#include <libsolidity/AST.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/**
|
|||
* Visitor interface for the abstract syntax tree. This class is tightly bound to the |
|||
* implementation of @ref ASTNode::accept and its overrides. After a call to |
|||
* @ref ASTNode::accept, the function visit for the appropriate parameter is called and then |
|||
* (if it returns true) this continues recursively for all child nodes in document order |
|||
* (there is an exception for contracts). After all child nodes have been visited, endVisit is |
|||
* called for the node. |
|||
*/ |
|||
class ASTVisitor |
|||
{ |
|||
public: |
|||
virtual bool visit(SourceUnit& _node) { return visitNode(_node); } |
|||
virtual bool visit(ImportDirective& _node) { return visitNode(_node); } |
|||
virtual bool visit(ContractDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); } |
|||
virtual bool visit(StructDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(EnumValue& _node) { return visitNode(_node); } |
|||
virtual bool visit(ParameterList& _node) { return visitNode(_node); } |
|||
virtual bool visit(FunctionDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); } |
|||
virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(ModifierInvocation& _node) { return visitNode(_node); } |
|||
virtual bool visit(EventDefinition& _node) { return visitNode(_node); } |
|||
virtual bool visit(TypeName& _node) { return visitNode(_node); } |
|||
virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); } |
|||
virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } |
|||
virtual bool visit(Mapping& _node) { return visitNode(_node); } |
|||
virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } |
|||
virtual bool visit(Block& _node) { return visitNode(_node); } |
|||
virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(IfStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(WhileStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(ForStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(Continue& _node) { return visitNode(_node); } |
|||
virtual bool visit(Break& _node) { return visitNode(_node); } |
|||
virtual bool visit(Return& _node) { return visitNode(_node); } |
|||
virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); } |
|||
virtual bool visit(Assignment& _node) { return visitNode(_node); } |
|||
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); } |
|||
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } |
|||
virtual bool visit(FunctionCall& _node) { return visitNode(_node); } |
|||
virtual bool visit(NewExpression& _node) { return visitNode(_node); } |
|||
virtual bool visit(MemberAccess& _node) { return visitNode(_node); } |
|||
virtual bool visit(IndexAccess& _node) { return visitNode(_node); } |
|||
virtual bool visit(Identifier& _node) { return visitNode(_node); } |
|||
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } |
|||
virtual bool visit(Literal& _node) { return visitNode(_node); } |
|||
|
|||
virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ImportDirective& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ContractDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(StructDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ParameterList& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(FunctionDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ModifierInvocation& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EventDefinition& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(TypeName& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Mapping& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Block& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(IfStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(WhileStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ForStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Continue& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Break& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Return& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(VariableDeclarationStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Assignment& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(NewExpression& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Identifier& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Literal& _node) { endVisitNode(_node); } |
|||
|
|||
protected: |
|||
/// Generic function called by default for each node, to be overridden by derived classes
|
|||
/// if behaviour unspecific to a node type is desired.
|
|||
virtual bool visitNode(ASTNode&) { return true; } |
|||
/// Generic function called by default for each node, to be overridden by derived classes
|
|||
/// if behaviour unspecific to a node type is desired.
|
|||
virtual void endVisitNode(ASTNode&) { } |
|||
}; |
|||
|
|||
class ASTConstVisitor |
|||
{ |
|||
public: |
|||
virtual bool visit(SourceUnit const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ImportDirective const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ContractDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); } |
|||
virtual bool visit(StructDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(EnumValue const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ParameterList const& _node) { return visitNode(_node); } |
|||
virtual bool visit(FunctionDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ModifierInvocation const& _node) { return visitNode(_node); } |
|||
virtual bool visit(EventDefinition const& _node) { return visitNode(_node); } |
|||
virtual bool visit(TypeName const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); } |
|||
virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Mapping const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ArrayTypeName const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Block const& _node) { return visitNode(_node); } |
|||
virtual bool visit(PlaceholderStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(IfStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(WhileStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ForStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Continue const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Break const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Return const& _node) { return visitNode(_node); } |
|||
virtual bool visit(VariableDeclarationStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ExpressionStatement const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Assignment const& _node) { return visitNode(_node); } |
|||
virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); } |
|||
virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } |
|||
virtual bool visit(FunctionCall const& _node) { return visitNode(_node); } |
|||
virtual bool visit(NewExpression const& _node) { return visitNode(_node); } |
|||
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); } |
|||
virtual bool visit(IndexAccess const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Identifier const& _node) { return visitNode(_node); } |
|||
virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } |
|||
virtual bool visit(Literal const& _node) { return visitNode(_node); } |
|||
|
|||
virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ImportDirective const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ContractDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(StructDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ParameterList const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(FunctionDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ModifierInvocation const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(EventDefinition const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(TypeName const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Mapping const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ArrayTypeName const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Block const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(IfStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Continue const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Break const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Return const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(VariableDeclarationStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Assignment const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Identifier const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } |
|||
virtual void endVisit(Literal const& _node) { endVisitNode(_node); } |
|||
|
|||
protected: |
|||
/// Generic function called by default for each node, to be overridden by derived classes
|
|||
/// if behaviour unspecific to a node type is desired.
|
|||
virtual bool visitNode(ASTNode const&) { return true; } |
|||
/// Generic function called by default for each node, to be overridden by derived classes
|
|||
/// if behaviour unspecific to a node type is desired.
|
|||
virtual void endVisitNode(ASTNode const&) { } |
|||
}; |
|||
|
|||
/**
|
|||
* Utility class that accepts std::functions and calls them for visitNode and endVisitNode. |
|||
*/ |
|||
class SimpleASTVisitor: public ASTConstVisitor |
|||
{ |
|||
public: |
|||
SimpleASTVisitor( |
|||
std::function<bool(ASTNode const&)> _onVisit, |
|||
std::function<void(ASTNode const&)> _onEndVisit |
|||
): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {} |
|||
|
|||
protected: |
|||
virtual bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; } |
|||
virtual void endVisitNode(ASTNode const& _n) override { m_onEndVisit(_n); } |
|||
|
|||
private: |
|||
std::function<bool(ASTNode const&)> m_onVisit; |
|||
std::function<void(ASTNode const&)> m_onEndVisit; |
|||
}; |
|||
|
|||
/**
|
|||
* Utility class that visits the AST in depth-first order and calls a function on each node and each edge. |
|||
* Child nodes are only visited if the node callback of the parent returns true. |
|||
* The node callback of a parent is called before any edge or node callback involving the children. |
|||
* The edge callbacks of all children are called before the edge callback of the parent. |
|||
* This way, the node callback can be used as an initializing callback and the edge callbacks can be |
|||
* used to compute a "reduce" function. |
|||
*/ |
|||
class ASTReduce: public ASTConstVisitor |
|||
{ |
|||
public: |
|||
/**
|
|||
* Constructs a new ASTReduce object with the given callback functions. |
|||
* @param _onNode called for each node, before its child edges and nodes, should return true to descend deeper |
|||
* @param _onEdge called for each edge with (parent, child) |
|||
*/ |
|||
ASTReduce( |
|||
std::function<bool(ASTNode const&)> _onNode, |
|||
std::function<void(ASTNode const&, ASTNode const&)> _onEdge |
|||
): m_onNode(_onNode), m_onEdge(_onEdge) |
|||
{ |
|||
} |
|||
|
|||
protected: |
|||
bool visitNode(ASTNode const& _node) override |
|||
{ |
|||
m_parents.push_back(&_node); |
|||
return m_onNode(_node); |
|||
} |
|||
void endVisitNode(ASTNode const& _node) override |
|||
{ |
|||
m_parents.pop_back(); |
|||
if (!m_parents.empty()) |
|||
m_onEdge(*m_parents.back(), _node); |
|||
} |
|||
|
|||
private: |
|||
std::vector<ASTNode const*> m_parents; |
|||
std::function<bool(ASTNode const&)> m_onNode; |
|||
std::function<void(ASTNode const&, ASTNode const&)> m_onEdge; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,685 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Implementation of the accept functions of AST nodes, included by AST.cpp to not clutter that |
|||
* file with these mechanical implementations. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
void SourceUnit::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_nodes, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void SourceUnit::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_nodes, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ImportDirective::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ImportDirective::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ContractDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
listAccept(m_baseContracts, _visitor); |
|||
listAccept(m_definedStructs, _visitor); |
|||
listAccept(m_definedEnums, _visitor); |
|||
listAccept(m_stateVariables, _visitor); |
|||
listAccept(m_events, _visitor); |
|||
listAccept(m_functionModifiers, _visitor); |
|||
listAccept(m_definedFunctions, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ContractDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
listAccept(m_baseContracts, _visitor); |
|||
listAccept(m_definedStructs, _visitor); |
|||
listAccept(m_definedEnums, _visitor); |
|||
listAccept(m_stateVariables, _visitor); |
|||
listAccept(m_events, _visitor); |
|||
listAccept(m_functionModifiers, _visitor); |
|||
listAccept(m_definedFunctions, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void InheritanceSpecifier::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_baseName->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_baseName->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EnumDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_members, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EnumDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_members, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EnumValue::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EnumValue::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void StructDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_members, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void StructDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_members, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void StructDefinition::checkValidityOfMembers() const |
|||
{ |
|||
checkMemberTypes(); |
|||
checkRecursion(); |
|||
} |
|||
|
|||
void ParameterList::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_parameters, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ParameterList::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_parameters, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void FunctionDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_parameters->accept(_visitor); |
|||
if (m_returnParameters) |
|||
m_returnParameters->accept(_visitor); |
|||
listAccept(m_functionModifiers, _visitor); |
|||
if (m_body) |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void FunctionDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_parameters->accept(_visitor); |
|||
if (m_returnParameters) |
|||
m_returnParameters->accept(_visitor); |
|||
listAccept(m_functionModifiers, _visitor); |
|||
if (m_body) |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void VariableDeclaration::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
if (m_typeName) |
|||
m_typeName->accept(_visitor); |
|||
if (m_value) |
|||
m_value->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void VariableDeclaration::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
if (m_typeName) |
|||
m_typeName->accept(_visitor); |
|||
if (m_value) |
|||
m_value->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ModifierDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_parameters->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ModifierDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_parameters->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ModifierInvocation::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_modifierName->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ModifierInvocation::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_modifierName->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EventDefinition::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_parameters->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void EventDefinition::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_parameters->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void TypeName::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void TypeName::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ElementaryTypeName::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ElementaryTypeName::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void UserDefinedTypeName::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void UserDefinedTypeName::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Mapping::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_keyType->accept(_visitor); |
|||
m_valueType->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Mapping::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_keyType->accept(_visitor); |
|||
m_valueType->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ArrayTypeName::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_baseType->accept(_visitor); |
|||
if (m_length) |
|||
m_length->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ArrayTypeName::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_baseType->accept(_visitor); |
|||
if (m_length) |
|||
m_length->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Block::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_statements, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Block::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
listAccept(m_statements, _visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void PlaceholderStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void PlaceholderStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void IfStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_condition->accept(_visitor); |
|||
m_trueBody->accept(_visitor); |
|||
if (m_falseBody) |
|||
m_falseBody->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void IfStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_condition->accept(_visitor); |
|||
m_trueBody->accept(_visitor); |
|||
if (m_falseBody) |
|||
m_falseBody->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void WhileStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_condition->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void WhileStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_condition->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ForStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
if (m_initExpression) |
|||
m_initExpression->accept(_visitor); |
|||
if (m_condExpression) |
|||
m_condExpression->accept(_visitor); |
|||
if (m_loopExpression) |
|||
m_loopExpression->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ForStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
if (m_initExpression) |
|||
m_initExpression->accept(_visitor); |
|||
if (m_condExpression) |
|||
m_condExpression->accept(_visitor); |
|||
if (m_loopExpression) |
|||
m_loopExpression->accept(_visitor); |
|||
m_body->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Continue::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Continue::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Break::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Break::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Return::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
if (m_expression) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Return::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
if (m_expression) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ExpressionStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
if (m_expression) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ExpressionStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
if (m_expression) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void VariableDeclarationStatement::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_variable->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void VariableDeclarationStatement::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_variable->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Assignment::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_leftHandSide->accept(_visitor); |
|||
m_rightHandSide->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Assignment::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_leftHandSide->accept(_visitor); |
|||
m_rightHandSide->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void UnaryOperation::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_subExpression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void UnaryOperation::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_subExpression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void BinaryOperation::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_left->accept(_visitor); |
|||
m_right->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void BinaryOperation::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_left->accept(_visitor); |
|||
m_right->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void FunctionCall::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_expression->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void FunctionCall::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_expression->accept(_visitor); |
|||
listAccept(m_arguments, _visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void NewExpression::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_contractName->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void NewExpression::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_contractName->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void MemberAccess::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void MemberAccess::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
m_expression->accept(_visitor); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void IndexAccess::accept(ASTVisitor& _visitor) |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_base->accept(_visitor); |
|||
if (m_index) |
|||
m_index->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void IndexAccess::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
if (_visitor.visit(*this)) |
|||
{ |
|||
m_base->accept(_visitor); |
|||
if (m_index) |
|||
m_index->accept(_visitor); |
|||
} |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Identifier::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Identifier::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ElementaryTypeNameExpression::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void ElementaryTypeNameExpression::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Literal::accept(ASTVisitor& _visitor) |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
void Literal::accept(ASTConstVisitor& _visitor) const |
|||
{ |
|||
_visitor.visit(*this); |
|||
_visitor.endVisit(*this); |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,786 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Code generation utils that handle arrays. |
|||
*/ |
|||
|
|||
#include <libsolidity/ArrayUtils.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libsolidity/CompilerContext.h> |
|||
#include <libsolidity/CompilerUtils.h> |
|||
#include <libsolidity/Types.h> |
|||
#include <libsolidity/Utils.h> |
|||
#include <libsolidity/LValue.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace solidity; |
|||
|
|||
void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const |
|||
{ |
|||
// this copies source to target and also clears target if it was larger
|
|||
// need to leave "target_ref target_byte_off" on the stack at the end
|
|||
|
|||
// stack layout: [source_ref] [source length] target_ref (top)
|
|||
solAssert(_targetType.location() == DataLocation::Storage, ""); |
|||
|
|||
IntegerType uint256(256); |
|||
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); |
|||
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); |
|||
|
|||
// TODO unroll loop for small sizes
|
|||
|
|||
bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; |
|||
bool fromCalldata = _sourceType.location() == DataLocation::CallData; |
|||
bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; |
|||
bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16; |
|||
bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16; |
|||
unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0); |
|||
|
|||
// stack: source_ref [source_length] target_ref
|
|||
// store target_ref
|
|||
for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) |
|||
m_context << eth::swapInstruction(i); |
|||
// stack: target_ref source_ref [source_length]
|
|||
// stack: target_ref source_ref [source_length]
|
|||
// retrieve source length
|
|||
if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) |
|||
retrieveLength(_sourceType); // otherwise, length is already there
|
|||
if (_sourceType.location() == DataLocation::Memory && _sourceType.isDynamicallySized()) |
|||
{ |
|||
// increment source pointer to point to data
|
|||
m_context << eth::Instruction::SWAP1 << u256(0x20); |
|||
m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; |
|||
} |
|||
|
|||
// stack: target_ref source_ref source_length
|
|||
m_context << eth::Instruction::DUP3; |
|||
// stack: target_ref source_ref source_length target_ref
|
|||
retrieveLength(_targetType); |
|||
// stack: target_ref source_ref source_length target_ref target_length
|
|||
if (_targetType.isDynamicallySized()) |
|||
// store new target length
|
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3 << eth::Instruction::SSTORE; |
|||
if (sourceBaseType->getCategory() == Type::Category::Mapping) |
|||
{ |
|||
solAssert(targetBaseType->getCategory() == Type::Category::Mapping, ""); |
|||
solAssert(_sourceType.location() == DataLocation::Storage, ""); |
|||
// nothing to copy
|
|||
m_context |
|||
<< eth::Instruction::POP << eth::Instruction::POP |
|||
<< eth::Instruction::POP << eth::Instruction::POP; |
|||
return; |
|||
} |
|||
// compute hashes (data positions)
|
|||
m_context << eth::Instruction::SWAP1; |
|||
if (_targetType.isDynamicallySized()) |
|||
CompilerUtils(m_context).computeHashStatic(); |
|||
// stack: target_ref source_ref source_length target_length target_data_pos
|
|||
m_context << eth::Instruction::SWAP1; |
|||
convertLengthToSize(_targetType); |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::ADD; |
|||
// stack: target_ref source_ref source_length target_data_pos target_data_end
|
|||
m_context << eth::Instruction::SWAP3; |
|||
// stack: target_ref target_data_end source_length target_data_pos source_ref
|
|||
// skip copying if source length is zero
|
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); |
|||
m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); |
|||
|
|||
if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) |
|||
CompilerUtils(m_context).computeHashStatic(); |
|||
// stack: target_ref target_data_end source_length target_data_pos source_data_pos
|
|||
m_context << eth::Instruction::SWAP2; |
|||
convertLengthToSize(_sourceType); |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
|
|||
if (haveByteOffsetTarget) |
|||
m_context << u256(0); |
|||
if (haveByteOffsetSource) |
|||
m_context << u256(0); |
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
|||
eth::AssemblyItem copyLoopStart = m_context.newTag(); |
|||
m_context << copyLoopStart; |
|||
// check for loop condition
|
|||
m_context |
|||
<< eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize) |
|||
<< eth::Instruction::GT << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem copyLoopEnd = m_context.newTag(); |
|||
m_context.appendConditionalJumpTo(copyLoopEnd); |
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
|||
// copy
|
|||
if (sourceBaseType->getCategory() == Type::Category::Array) |
|||
{ |
|||
solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); |
|||
auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); |
|||
m_context << eth::Instruction::DUP3; |
|||
if (sourceBaseArrayType.location() == DataLocation::Memory) |
|||
m_context << eth::Instruction::MLOAD; |
|||
m_context << eth::Instruction::DUP3; |
|||
copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
else if (directCopy) |
|||
{ |
|||
solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); |
|||
m_context |
|||
<< eth::Instruction::DUP3 << eth::Instruction::SLOAD |
|||
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE; |
|||
} |
|||
else |
|||
{ |
|||
// Note that we have to copy each element on its own in case conversion is involved.
|
|||
// We might copy too much if there is padding at the last element, but this way end
|
|||
// checking is easier.
|
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
|||
m_context << eth::dupInstruction(3 + byteOffsetSize); |
|||
if (_sourceType.location() == DataLocation::Storage) |
|||
{ |
|||
if (haveByteOffsetSource) |
|||
m_context << eth::Instruction::DUP2; |
|||
else |
|||
m_context << u256(0); |
|||
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); |
|||
} |
|||
else if (sourceBaseType->isValueType()) |
|||
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); |
|||
else |
|||
solAssert(false, "Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); |
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
|||
solAssert( |
|||
2 + byteOffsetSize + sourceBaseType->getSizeOnStack() <= 16, |
|||
"Stack too deep, try removing local variables." |
|||
); |
|||
// fetch target storage reference
|
|||
m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->getSizeOnStack()); |
|||
if (haveByteOffsetTarget) |
|||
m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->getSizeOnStack()); |
|||
else |
|||
m_context << u256(0); |
|||
StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); |
|||
} |
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
|||
// increment source
|
|||
if (haveByteOffsetSource) |
|||
incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4); |
|||
else |
|||
{ |
|||
m_context << eth::swapInstruction(2 + byteOffsetSize); |
|||
if (sourceIsStorage) |
|||
m_context << sourceBaseType->getStorageSize(); |
|||
else if (_sourceType.location() == DataLocation::Memory) |
|||
m_context << sourceBaseType->memoryHeadSize(); |
|||
else |
|||
m_context << sourceBaseType->getCalldataEncodedSize(true); |
|||
m_context |
|||
<< eth::Instruction::ADD |
|||
<< eth::swapInstruction(2 + byteOffsetSize); |
|||
} |
|||
// increment target
|
|||
if (haveByteOffsetTarget) |
|||
incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); |
|||
else |
|||
m_context |
|||
<< eth::swapInstruction(1 + byteOffsetSize) |
|||
<< targetBaseType->getStorageSize() |
|||
<< eth::Instruction::ADD |
|||
<< eth::swapInstruction(1 + byteOffsetSize); |
|||
m_context.appendJumpTo(copyLoopStart); |
|||
m_context << copyLoopEnd; |
|||
if (haveByteOffsetTarget) |
|||
{ |
|||
// clear elements that might be left over in the current slot in target
|
|||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
|
|||
m_context << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); |
|||
m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize); |
|||
StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); |
|||
incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); |
|||
m_context.appendJumpTo(copyLoopEnd); |
|||
|
|||
m_context << copyCleanupLoopEnd; |
|||
m_context << eth::Instruction::POP; // might pop the source, but then target is popped next
|
|||
} |
|||
if (haveByteOffsetSource) |
|||
m_context << eth::Instruction::POP; |
|||
m_context << copyLoopEndWithoutByteOffset; |
|||
|
|||
// zero-out leftovers in target
|
|||
// stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
|
|||
m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
// stack: target_ref target_data_end target_data_pos_updated
|
|||
clearStorageLoop(*targetBaseType); |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
void ArrayUtils::copyArrayToMemory(const ArrayType& _sourceType, bool _padToWordBoundaries) const |
|||
{ |
|||
solAssert( |
|||
_sourceType.getBaseType()->getCalldataEncodedSize() > 0, |
|||
"Nested dynamic arrays not implemented here." |
|||
); |
|||
CompilerUtils utils(m_context); |
|||
unsigned baseSize = 1; |
|||
if (!_sourceType.isByteArray()) |
|||
// We always pad the elements, regardless of _padToWordBoundaries.
|
|||
baseSize = _sourceType.getBaseType()->getCalldataEncodedSize(); |
|||
|
|||
if (_sourceType.location() == DataLocation::CallData) |
|||
{ |
|||
if (!_sourceType.isDynamicallySized()) |
|||
m_context << _sourceType.getLength(); |
|||
if (baseSize > 1) |
|||
m_context << u256(baseSize) << eth::Instruction::MUL; |
|||
// stack: target source_offset source_len
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; |
|||
// stack: target source_offset source_len source_len source_offset target
|
|||
m_context << eth::Instruction::CALLDATACOPY; |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; |
|||
} |
|||
else if (_sourceType.location() == DataLocation::Memory) |
|||
{ |
|||
retrieveLength(_sourceType); |
|||
// stack: target source length
|
|||
if (!_sourceType.getBaseType()->isValueType()) |
|||
{ |
|||
// copy using a loop
|
|||
m_context << u256(0) << eth::Instruction::SWAP3; |
|||
// stack: counter source length target
|
|||
auto repeat = m_context.newTag(); |
|||
m_context << repeat; |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; |
|||
m_context << eth::Instruction::LT << eth::Instruction::ISZERO; |
|||
auto loopEnd = m_context.appendConditionalJump(); |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP5; |
|||
accessIndex(_sourceType, false); |
|||
MemoryItem(m_context, *_sourceType.getBaseType(), true).retrieveValue(SourceLocation(), true); |
|||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.getBaseType().get())) |
|||
copyArrayToMemory(*baseArray, _padToWordBoundaries); |
|||
else |
|||
utils.storeInMemoryDynamic(*_sourceType.getBaseType()); |
|||
m_context << eth::Instruction::SWAP3 << u256(1) << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP3; |
|||
m_context.appendJumpTo(repeat); |
|||
m_context << loopEnd; |
|||
m_context << eth::Instruction::SWAP3; |
|||
utils.popStackSlots(3); |
|||
// stack: updated_target_pos
|
|||
return; |
|||
} |
|||
|
|||
// memcpy using the built-in contract
|
|||
if (_sourceType.isDynamicallySized()) |
|||
{ |
|||
// change pointer to data part
|
|||
m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP1; |
|||
} |
|||
// convert length to size
|
|||
if (baseSize > 1) |
|||
m_context << u256(baseSize) << eth::Instruction::MUL; |
|||
// stack: <target> <source> <size>
|
|||
//@TODO do not use ::CALL if less than 32 bytes?
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::DUP4; |
|||
utils.memoryCopy(); |
|||
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
// stack: <target> <size>
|
|||
|
|||
bool paddingNeeded = false; |
|||
if (_sourceType.isDynamicallySized()) |
|||
paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); |
|||
else |
|||
paddingNeeded = _padToWordBoundaries && (((_sourceType.getLength() * baseSize) % 32) != 0); |
|||
if (paddingNeeded) |
|||
{ |
|||
// stack: <target> <size>
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; |
|||
// stack: <length> <target + size>
|
|||
m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; |
|||
// stack: <target + size> <remainder = size % 32>
|
|||
eth::AssemblyItem skip = m_context.newTag(); |
|||
if (_sourceType.isDynamicallySized()) |
|||
{ |
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; |
|||
m_context.appendConditionalJumpTo(skip); |
|||
} |
|||
// round off, load from there.
|
|||
// stack <target + size> <remainder = size % 32>
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; |
|||
m_context << eth::Instruction::SUB; |
|||
// stack: target+size remainder <target + size - remainder>
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; |
|||
// Now we AND it with ~(2**(8 * (32 - remainder)) - 1)
|
|||
m_context << u256(1); |
|||
m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; |
|||
// stack: ...<v> 1 <32 - remainder>
|
|||
m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; |
|||
m_context << eth::Instruction::NOT << eth::Instruction::AND; |
|||
// stack: target+size remainder target+size-remainder <v & ...>
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; |
|||
// stack: target+size remainder target+size-remainder
|
|||
m_context << u256(32) << eth::Instruction::ADD; |
|||
// stack: target+size remainder <new_padded_end>
|
|||
m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; |
|||
|
|||
if (_sourceType.isDynamicallySized()) |
|||
m_context << skip.tag(); |
|||
// stack <target + "size"> <remainder = size % 32>
|
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
else |
|||
// stack: <target> <size>
|
|||
m_context << eth::Instruction::ADD; |
|||
} |
|||
else |
|||
{ |
|||
solAssert(_sourceType.location() == DataLocation::Storage, ""); |
|||
unsigned storageBytes = _sourceType.getBaseType()->getStorageBytes(); |
|||
u256 storageSize = _sourceType.getBaseType()->getStorageSize(); |
|||
solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); |
|||
|
|||
retrieveLength(_sourceType); |
|||
// stack here: memory_offset storage_offset length
|
|||
// jump to end if length is zero
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem loopEnd = m_context.newTag(); |
|||
m_context.appendConditionalJumpTo(loopEnd); |
|||
// compute memory end offset
|
|||
if (baseSize > 1) |
|||
// convert length to memory size
|
|||
m_context << u256(baseSize) << eth::Instruction::MUL; |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; |
|||
if (_sourceType.isDynamicallySized()) |
|||
{ |
|||
// actual array data is stored at SHA3(storage_offset)
|
|||
m_context << eth::Instruction::SWAP1; |
|||
utils.computeHashStatic(); |
|||
m_context << eth::Instruction::SWAP1; |
|||
} |
|||
|
|||
// stack here: memory_end_offset storage_data_offset memory_offset
|
|||
bool haveByteOffset = !_sourceType.isByteArray() && storageBytes <= 16; |
|||
if (haveByteOffset) |
|||
m_context << u256(0) << eth::Instruction::SWAP1; |
|||
// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
|
|||
eth::AssemblyItem loopStart = m_context.newTag(); |
|||
m_context << loopStart; |
|||
// load and store
|
|||
if (_sourceType.isByteArray()) |
|||
{ |
|||
// Packed both in storage and memory.
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; |
|||
// increment storage_data_offset by 1
|
|||
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; |
|||
// increment memory offset by 32
|
|||
m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; |
|||
} |
|||
else |
|||
{ |
|||
// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
|
|||
if (haveByteOffset) |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; |
|||
else |
|||
m_context << eth::Instruction::DUP2 << u256(0); |
|||
StorageItem(m_context, *_sourceType.getBaseType()).retrieveValue(SourceLocation(), true); |
|||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.getBaseType().get())) |
|||
copyArrayToMemory(*baseArray, _padToWordBoundaries); |
|||
else |
|||
utils.storeInMemoryDynamic(*_sourceType.getBaseType()); |
|||
// increment storage_data_offset and byte offset
|
|||
if (haveByteOffset) |
|||
incrementByteOffset(storageBytes, 2, 3); |
|||
else |
|||
{ |
|||
m_context << eth::Instruction::SWAP1; |
|||
m_context << storageSize << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP1; |
|||
} |
|||
} |
|||
// check for loop condition
|
|||
m_context << eth::Instruction::DUP1 << eth::dupInstruction(haveByteOffset ? 5 : 4); |
|||
m_context << eth::Instruction::GT; |
|||
m_context.appendConditionalJumpTo(loopStart); |
|||
// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
|
|||
if (haveByteOffset) |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
if (_padToWordBoundaries && baseSize % 32 != 0) |
|||
{ |
|||
// memory_end_offset - start is the actual length (we want to compute the ceil of).
|
|||
// memory_offset - start is its next multiple of 32, but it might be off by 32.
|
|||
// so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31
|
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; |
|||
m_context << u256(31) << eth::Instruction::AND; |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP2; |
|||
} |
|||
m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; |
|||
} |
|||
} |
|||
|
|||
void ArrayUtils::clearArray(ArrayType const& _type) const |
|||
{ |
|||
unsigned stackHeightStart = m_context.getStackHeight(); |
|||
solAssert(_type.location() == DataLocation::Storage, ""); |
|||
if (_type.getBaseType()->getStorageBytes() < 32) |
|||
{ |
|||
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); |
|||
solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid storage size for type."); |
|||
} |
|||
if (_type.getBaseType()->isValueType()) |
|||
solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid size for value type."); |
|||
|
|||
m_context << eth::Instruction::POP; // remove byte offset
|
|||
if (_type.isDynamicallySized()) |
|||
clearDynamicArray(_type); |
|||
else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) |
|||
m_context << eth::Instruction::POP; |
|||
else if (_type.getBaseType()->isValueType() && _type.getStorageSize() <= 5) |
|||
{ |
|||
// unroll loop for small arrays @todo choose a good value
|
|||
// Note that we loop over storage slots here, not elements.
|
|||
for (unsigned i = 1; i < _type.getStorageSize(); ++i) |
|||
m_context |
|||
<< u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE |
|||
<< u256(1) << eth::Instruction::ADD; |
|||
m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
} |
|||
else if (!_type.getBaseType()->isValueType() && _type.getLength() <= 4) |
|||
{ |
|||
// unroll loop for small arrays @todo choose a good value
|
|||
solAssert(_type.getBaseType()->getStorageBytes() >= 32, "Invalid storage size."); |
|||
for (unsigned i = 1; i < _type.getLength(); ++i) |
|||
{ |
|||
m_context << u256(0); |
|||
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); |
|||
m_context |
|||
<< eth::Instruction::POP |
|||
<< u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; |
|||
} |
|||
m_context << u256(0); |
|||
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); |
|||
} |
|||
else |
|||
{ |
|||
m_context << eth::Instruction::DUP1 << _type.getLength(); |
|||
convertLengthToSize(_type); |
|||
m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; |
|||
if (_type.getBaseType()->getStorageBytes() < 32) |
|||
clearStorageLoop(IntegerType(256)); |
|||
else |
|||
clearStorageLoop(*_type.getBaseType()); |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); |
|||
} |
|||
|
|||
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const |
|||
{ |
|||
solAssert(_type.location() == DataLocation::Storage, ""); |
|||
solAssert(_type.isDynamicallySized(), ""); |
|||
|
|||
unsigned stackHeightStart = m_context.getStackHeight(); |
|||
// fetch length
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; |
|||
// set length to zero
|
|||
m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; |
|||
// stack: ref old_length
|
|||
convertLengthToSize(_type); |
|||
// compute data positions
|
|||
m_context << eth::Instruction::SWAP1; |
|||
CompilerUtils(m_context).computeHashStatic(); |
|||
// stack: len data_pos
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD |
|||
<< eth::Instruction::SWAP1; |
|||
// stack: data_pos_end data_pos
|
|||
if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) |
|||
clearStorageLoop(IntegerType(256)); |
|||
else |
|||
clearStorageLoop(*_type.getBaseType()); |
|||
// cleanup
|
|||
m_context << eth::Instruction::POP; |
|||
solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); |
|||
} |
|||
|
|||
void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const |
|||
{ |
|||
solAssert(_type.location() == DataLocation::Storage, ""); |
|||
solAssert(_type.isDynamicallySized(), ""); |
|||
if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32) |
|||
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); |
|||
|
|||
unsigned stackHeightStart = m_context.getStackHeight(); |
|||
eth::AssemblyItem resizeEnd = m_context.newTag(); |
|||
|
|||
// stack: ref new_length
|
|||
// fetch old length
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
// stack: ref new_length old_length
|
|||
// store new length
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; |
|||
// skip if size is not reduced
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2 |
|||
<< eth::Instruction::ISZERO << eth::Instruction::GT; |
|||
m_context.appendConditionalJumpTo(resizeEnd); |
|||
|
|||
// size reduced, clear the end of the array
|
|||
// stack: ref new_length old_length
|
|||
convertLengthToSize(_type); |
|||
m_context << eth::Instruction::DUP2; |
|||
convertLengthToSize(_type); |
|||
// stack: ref new_length old_size new_size
|
|||
// compute data positions
|
|||
m_context << eth::Instruction::DUP4; |
|||
CompilerUtils(m_context).computeHashStatic(); |
|||
// stack: ref new_length old_size new_size data_pos
|
|||
m_context << eth::Instruction::SWAP2 << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
// stack: ref new_length data_pos new_size delete_end
|
|||
m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; |
|||
// stack: ref new_length delete_end delete_start
|
|||
if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) |
|||
clearStorageLoop(IntegerType(256)); |
|||
else |
|||
clearStorageLoop(*_type.getBaseType()); |
|||
|
|||
m_context << resizeEnd; |
|||
// cleanup
|
|||
m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; |
|||
solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); |
|||
} |
|||
|
|||
void ArrayUtils::clearStorageLoop(Type const& _type) const |
|||
{ |
|||
unsigned stackHeightStart = m_context.getStackHeight(); |
|||
if (_type.getCategory() == Type::Category::Mapping) |
|||
{ |
|||
m_context << eth::Instruction::POP; |
|||
return; |
|||
} |
|||
// stack: end_pos pos
|
|||
|
|||
// jump to and return from the loop to allow for duplicate code removal
|
|||
eth::AssemblyItem returnTag = m_context.pushNewTag(); |
|||
m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; |
|||
|
|||
// stack: <return tag> end_pos pos
|
|||
eth::AssemblyItem loopStart = m_context.appendJumpToNew(); |
|||
m_context << loopStart; |
|||
// check for loop condition
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 |
|||
<< eth::Instruction::GT << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem zeroLoopEnd = m_context.newTag(); |
|||
m_context.appendConditionalJumpTo(zeroLoopEnd); |
|||
// delete
|
|||
m_context << u256(0); |
|||
StorageItem(m_context, _type).setToZero(SourceLocation(), false); |
|||
m_context << eth::Instruction::POP; |
|||
// increment
|
|||
m_context << u256(1) << eth::Instruction::ADD; |
|||
m_context.appendJumpTo(loopStart); |
|||
// cleanup
|
|||
m_context << zeroLoopEnd; |
|||
m_context << eth::Instruction::POP << eth::Instruction::SWAP1; |
|||
// "return"
|
|||
m_context << eth::Instruction::JUMP; |
|||
|
|||
m_context << returnTag; |
|||
solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); |
|||
} |
|||
|
|||
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const |
|||
{ |
|||
if (_arrayType.location() == DataLocation::Storage) |
|||
{ |
|||
if (_arrayType.getBaseType()->getStorageSize() <= 1) |
|||
{ |
|||
unsigned baseBytes = _arrayType.getBaseType()->getStorageBytes(); |
|||
if (baseBytes == 0) |
|||
m_context << eth::Instruction::POP << u256(1); |
|||
else if (baseBytes <= 16) |
|||
{ |
|||
unsigned itemsPerSlot = 32 / baseBytes; |
|||
m_context |
|||
<< u256(itemsPerSlot - 1) << eth::Instruction::ADD |
|||
<< u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV; |
|||
} |
|||
} |
|||
else |
|||
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; |
|||
} |
|||
else |
|||
{ |
|||
if (!_arrayType.isByteArray()) |
|||
{ |
|||
if (_arrayType.location() == DataLocation::Memory) |
|||
m_context << _arrayType.getBaseType()->memoryHeadSize(); |
|||
else |
|||
m_context << _arrayType.getBaseType()->getCalldataEncodedSize(); |
|||
m_context << eth::Instruction::MUL; |
|||
} |
|||
else if (_pad) |
|||
m_context << u256(31) << eth::Instruction::ADD |
|||
<< u256(32) << eth::Instruction::DUP1 |
|||
<< eth::Instruction::SWAP2 << eth::Instruction::DIV << eth::Instruction::MUL; |
|||
} |
|||
} |
|||
|
|||
void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const |
|||
{ |
|||
if (!_arrayType.isDynamicallySized()) |
|||
m_context << _arrayType.getLength(); |
|||
else |
|||
{ |
|||
m_context << eth::Instruction::DUP1; |
|||
switch (_arrayType.location()) |
|||
{ |
|||
case DataLocation::CallData: |
|||
// length is stored on the stack
|
|||
break; |
|||
case DataLocation::Memory: |
|||
m_context << eth::Instruction::MLOAD; |
|||
break; |
|||
case DataLocation::Storage: |
|||
m_context << eth::Instruction::SLOAD; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const |
|||
{ |
|||
DataLocation location = _arrayType.location(); |
|||
eth::Instruction load = |
|||
location == DataLocation::Storage ? eth::Instruction::SLOAD : |
|||
location == DataLocation::Memory ? eth::Instruction::MLOAD : |
|||
eth::Instruction::CALLDATALOAD; |
|||
|
|||
if (_doBoundsCheck) |
|||
{ |
|||
// retrieve length
|
|||
if (!_arrayType.isDynamicallySized()) |
|||
m_context << _arrayType.getLength(); |
|||
else if (location == DataLocation::CallData) |
|||
// length is stored on the stack
|
|||
m_context << eth::Instruction::SWAP1; |
|||
else |
|||
m_context << eth::Instruction::DUP2 << load; |
|||
// stack: <base_ref> <index> <length>
|
|||
// check out-of-bounds access
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::LT << eth::Instruction::ISZERO; |
|||
// out-of-bounds access throws exception
|
|||
m_context.appendConditionalJumpTo(m_context.errorTag()); |
|||
} |
|||
else if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) |
|||
// remove length if present
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
|
|||
// stack: <base_ref> <index>
|
|||
m_context << eth::Instruction::SWAP1; |
|||
if (_arrayType.isDynamicallySized()) |
|||
{ |
|||
if (location == DataLocation::Storage) |
|||
CompilerUtils(m_context).computeHashStatic(); |
|||
else if (location == DataLocation::Memory) |
|||
m_context << u256(32) << eth::Instruction::ADD; |
|||
} |
|||
// stack: <index> <data_ref>
|
|||
switch (location) |
|||
{ |
|||
case DataLocation::CallData: |
|||
case DataLocation::Memory: |
|||
if (!_arrayType.isByteArray()) |
|||
{ |
|||
m_context << eth::Instruction::SWAP1; |
|||
if (location == DataLocation::CallData) |
|||
m_context << _arrayType.getBaseType()->getCalldataEncodedSize(); |
|||
else |
|||
m_context << u256(_arrayType.memoryHeadSize()); |
|||
m_context << eth::Instruction::MUL; |
|||
} |
|||
m_context << eth::Instruction::ADD; |
|||
break; |
|||
case DataLocation::Storage: |
|||
m_context << eth::Instruction::SWAP1; |
|||
if (_arrayType.getBaseType()->getStorageBytes() <= 16) |
|||
{ |
|||
// stack: <data_ref> <index>
|
|||
// goal:
|
|||
// <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize>
|
|||
unsigned byteSize = _arrayType.getBaseType()->getStorageBytes(); |
|||
solAssert(byteSize != 0, ""); |
|||
unsigned itemsPerSlot = 32 / byteSize; |
|||
m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2; |
|||
// stack: itemsPerSlot index data_ref
|
|||
m_context |
|||
<< eth::Instruction::DUP3 << eth::Instruction::DUP3 |
|||
<< eth::Instruction::DIV << eth::Instruction::ADD |
|||
// stack: itemsPerSlot index (data_ref + index / itemsPerSlot)
|
|||
<< eth::Instruction::SWAP2 << eth::Instruction::SWAP1 |
|||
<< eth::Instruction::MOD; |
|||
if (byteSize != 1) |
|||
m_context << u256(byteSize) << eth::Instruction::MUL; |
|||
} |
|||
else |
|||
{ |
|||
if (_arrayType.getBaseType()->getStorageSize() != 1) |
|||
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; |
|||
m_context << eth::Instruction::ADD << u256(0); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const |
|||
{ |
|||
solAssert(_byteSize < 32, ""); |
|||
solAssert(_byteSize != 0, ""); |
|||
// We do the following, but avoiding jumps:
|
|||
// byteOffset += byteSize
|
|||
// if (byteOffset + byteSize > 32)
|
|||
// {
|
|||
// storageOffset++;
|
|||
// byteOffset = 0;
|
|||
// }
|
|||
if (_byteOffsetPosition > 1) |
|||
m_context << eth::swapInstruction(_byteOffsetPosition - 1); |
|||
m_context << u256(_byteSize) << eth::Instruction::ADD; |
|||
if (_byteOffsetPosition > 1) |
|||
m_context << eth::swapInstruction(_byteOffsetPosition - 1); |
|||
// compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32
|
|||
m_context |
|||
<< u256(32) << eth::dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) |
|||
<< eth::Instruction::ADD << eth::Instruction::DIV; |
|||
// increment storage offset if X == 1 (just add X to it)
|
|||
// stack: X
|
|||
m_context |
|||
<< eth::swapInstruction(_storageOffsetPosition) << eth::dupInstruction(_storageOffsetPosition + 1) |
|||
<< eth::Instruction::ADD << eth::swapInstruction(_storageOffsetPosition); |
|||
// stack: X
|
|||
// set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X)
|
|||
m_context << u256(1) << eth::Instruction::SUB; |
|||
// stack: 1 - X
|
|||
if (_byteOffsetPosition == 1) |
|||
m_context << eth::Instruction::MUL; |
|||
else |
|||
m_context |
|||
<< eth::dupInstruction(_byteOffsetPosition + 1) << eth::Instruction::MUL |
|||
<< eth::swapInstruction(_byteOffsetPosition) << eth::Instruction::POP; |
|||
} |
@ -1,98 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Code generation utils that handle arrays. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class CompilerContext; |
|||
class Type; |
|||
class ArrayType; |
|||
|
|||
/**
|
|||
* Class that provides code generation for handling arrays. |
|||
*/ |
|||
class ArrayUtils |
|||
{ |
|||
public: |
|||
ArrayUtils(CompilerContext& _context): m_context(_context) {} |
|||
|
|||
/// Copies an array to an array in storage. The arrays can be of different types only if
|
|||
/// their storage representation is the same.
|
|||
/// Stack pre: source_reference [source_length] target_reference
|
|||
/// Stack post: target_reference
|
|||
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; |
|||
/// Copies the data part of an array (which cannot be dynamically nested) from anywhere
|
|||
/// to a given position in memory.
|
|||
/// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in
|
|||
/// place as required by the ABI encoding). Use CompilerUtils::convertType if you want real
|
|||
/// memory copies of nested arrays.
|
|||
/// Stack pre: memory_offset source_item
|
|||
/// Stack post: memory_offest + length(padded)
|
|||
void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; |
|||
/// Clears the given dynamic or static array.
|
|||
/// Stack pre: storage_ref storage_byte_offset
|
|||
/// Stack post:
|
|||
void clearArray(ArrayType const& _type) const; |
|||
/// Clears the length and data elements of the array referenced on the stack.
|
|||
/// Stack pre: reference (excludes byte offset)
|
|||
/// Stack post:
|
|||
void clearDynamicArray(ArrayType const& _type) const; |
|||
/// Changes the size of a dynamic array and clears the tail if it is shortened.
|
|||
/// Stack pre: reference (excludes byte offset) new_length
|
|||
/// Stack post:
|
|||
void resizeDynamicArray(ArrayType const& _type) const; |
|||
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
|
|||
/// Stack pre: end_ref start_ref
|
|||
/// Stack post: end_ref
|
|||
void clearStorageLoop(Type const& _type) const; |
|||
/// Converts length to size (number of storage slots or calldata/memory bytes).
|
|||
/// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
|
|||
/// Stack pre: length
|
|||
/// Stack post: size
|
|||
void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; |
|||
/// Retrieves the length (number of elements) of the array ref on the stack. This also
|
|||
/// works for statically-sized arrays.
|
|||
/// Stack pre: reference (excludes byte offset for dynamic storage arrays)
|
|||
/// Stack post: reference length
|
|||
void retrieveLength(ArrayType const& _arrayType) const; |
|||
/// Performs bounds checking and returns a reference on the stack.
|
|||
/// Stack pre: reference [length] index
|
|||
/// Stack post (storage): storage_slot byte_offset
|
|||
/// Stack post: memory/calldata_offset
|
|||
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; |
|||
|
|||
private: |
|||
/// Adds the given number of bytes to a storage byte offset counter and also increments
|
|||
/// the storage offset if adding this number again would increase the counter over 32.
|
|||
/// @param byteOffsetPosition the stack offset of the storage byte offset
|
|||
/// @param storageOffsetPosition the stack offset of the storage slot offset
|
|||
void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; |
|||
|
|||
CompilerContext& m_context; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,25 +0,0 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) |
|||
include_directories(BEFORE ..) |
|||
include_directories(${Boost_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE solidity) |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
|
|||
add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) |
|||
add_dependencies(${EXECUTABLE} BuildInfo.h) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} evmasm) |
|||
target_link_libraries(${EXECUTABLE} devcrypto) |
|||
|
|||
install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) |
|||
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) |
|||
|
@ -1,726 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity compiler. |
|||
*/ |
|||
|
|||
#include <libsolidity/Compiler.h> |
|||
#include <algorithm> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libevmasm/Assembly.h> |
|||
#include <libevmcore/Params.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/ExpressionCompiler.h> |
|||
#include <libsolidity/CompilerUtils.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::solidity; |
|||
|
|||
/**
|
|||
* Simple helper class to ensure that the stack height is the same at certain places in the code. |
|||
*/ |
|||
class StackHeightChecker |
|||
{ |
|||
public: |
|||
StackHeightChecker(CompilerContext const& _context): |
|||
m_context(_context), stackHeight(m_context.getStackHeight()) {} |
|||
void check() { solAssert(m_context.getStackHeight() == stackHeight, "I sense a disturbance in the stack."); } |
|||
private: |
|||
CompilerContext const& m_context; |
|||
unsigned stackHeight; |
|||
}; |
|||
|
|||
void Compiler::compileContract(ContractDefinition const& _contract, |
|||
map<ContractDefinition const*, bytes const*> const& _contracts) |
|||
{ |
|||
m_context = CompilerContext(); // clear it just in case
|
|||
{ |
|||
CompilerContext::LocationSetter locationSetterRunTime(m_context, _contract); |
|||
initializeContext(_contract, _contracts); |
|||
appendFunctionSelector(_contract); |
|||
appendFunctionsWithoutCode(); |
|||
} |
|||
|
|||
// Swap the runtime context with the creation-time context
|
|||
swap(m_context, m_runtimeContext); |
|||
CompilerContext::LocationSetter locationSetterCreationTime(m_context, _contract); |
|||
initializeContext(_contract, _contracts); |
|||
packIntoContractCreator(_contract, m_runtimeContext); |
|||
if (m_optimize) |
|||
m_context.optimise(m_optimizeRuns); |
|||
} |
|||
|
|||
void Compiler::compileClone( |
|||
ContractDefinition const& _contract, |
|||
map<ContractDefinition const*, bytes const*> const& _contracts |
|||
) |
|||
{ |
|||
m_context = CompilerContext(); // clear it just in case
|
|||
initializeContext(_contract, _contracts); |
|||
|
|||
appendInitAndConstructorCode(_contract); |
|||
|
|||
//@todo determine largest return size of all runtime functions
|
|||
eth::AssemblyItem runtimeSub = m_context.addSubroutine(getCloneRuntime()); |
|||
solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); |
|||
m_runtimeSub = size_t(runtimeSub.data()); |
|||
|
|||
// stack contains sub size
|
|||
m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; |
|||
m_context << u256(0) << eth::Instruction::RETURN; |
|||
|
|||
appendFunctionsWithoutCode(); |
|||
|
|||
if (m_optimize) |
|||
m_context.optimise(m_optimizeRuns); |
|||
} |
|||
|
|||
eth::AssemblyItem Compiler::getFunctionEntryLabel(FunctionDefinition const& _function) const |
|||
{ |
|||
return m_runtimeContext.getFunctionEntryLabelIfExists(_function); |
|||
} |
|||
|
|||
void Compiler::initializeContext(ContractDefinition const& _contract, |
|||
map<ContractDefinition const*, bytes const*> const& _contracts) |
|||
{ |
|||
CompilerUtils(m_context).initialiseFreeMemoryPointer(); |
|||
m_context.setCompiledContracts(_contracts); |
|||
m_context.setInheritanceHierarchy(_contract.getLinearizedBaseContracts()); |
|||
registerStateVariables(_contract); |
|||
m_context.resetVisitedNodes(&_contract); |
|||
} |
|||
|
|||
void Compiler::appendInitAndConstructorCode(ContractDefinition const& _contract) |
|||
{ |
|||
// Determine the arguments that are used for the base constructors.
|
|||
std::vector<ContractDefinition const*> const& bases = _contract.getLinearizedBaseContracts(); |
|||
for (ContractDefinition const* contract: bases) |
|||
{ |
|||
if (FunctionDefinition const* constructor = contract->getConstructor()) |
|||
for (auto const& modifier: constructor->getModifiers()) |
|||
{ |
|||
auto baseContract = dynamic_cast<ContractDefinition const*>( |
|||
&modifier->getName()->getReferencedDeclaration()); |
|||
if (baseContract) |
|||
if (m_baseArguments.count(baseContract->getConstructor()) == 0) |
|||
m_baseArguments[baseContract->getConstructor()] = &modifier->getArguments(); |
|||
} |
|||
|
|||
for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts()) |
|||
{ |
|||
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>( |
|||
&base->getName()->getReferencedDeclaration()); |
|||
solAssert(baseContract, ""); |
|||
|
|||
if (m_baseArguments.count(baseContract->getConstructor()) == 0) |
|||
m_baseArguments[baseContract->getConstructor()] = &base->getArguments(); |
|||
} |
|||
} |
|||
// Initialization of state variables in base-to-derived order.
|
|||
for (ContractDefinition const* contract: boost::adaptors::reverse(bases)) |
|||
initializeStateVariables(*contract); |
|||
|
|||
if (FunctionDefinition const* constructor = _contract.getConstructor()) |
|||
appendConstructor(*constructor); |
|||
else if (auto c = m_context.getNextConstructor(_contract)) |
|||
appendBaseConstructor(*c); |
|||
} |
|||
|
|||
void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) |
|||
{ |
|||
appendInitAndConstructorCode(_contract); |
|||
|
|||
eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.getAssembly()); |
|||
solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); |
|||
m_runtimeSub = size_t(runtimeSub.data()); |
|||
|
|||
// stack contains sub size
|
|||
m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; |
|||
m_context << u256(0) << eth::Instruction::RETURN; |
|||
|
|||
// note that we have to include the functions again because of absolute jump labels
|
|||
appendFunctionsWithoutCode(); |
|||
} |
|||
|
|||
void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _constructor); |
|||
FunctionType constructorType(_constructor); |
|||
if (!constructorType.getParameterTypes().empty()) |
|||
{ |
|||
solAssert(m_baseArguments.count(&_constructor), ""); |
|||
std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor]; |
|||
solAssert(arguments, ""); |
|||
for (unsigned i = 0; i < arguments->size(); ++i) |
|||
compileExpression(*(arguments->at(i)), constructorType.getParameterTypes()[i]); |
|||
} |
|||
_constructor.accept(*this); |
|||
} |
|||
|
|||
void Compiler::appendConstructor(FunctionDefinition const& _constructor) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _constructor); |
|||
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
|
|||
if (!_constructor.getParameters().empty()) |
|||
{ |
|||
unsigned argumentSize = 0; |
|||
for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters()) |
|||
if (var->getType()->isDynamicallySized()) |
|||
{ |
|||
argumentSize = 0; |
|||
break; |
|||
} |
|||
else |
|||
argumentSize += var->getType()->getCalldataEncodedSize(); |
|||
|
|||
CompilerUtils(m_context).fetchFreeMemoryPointer(); |
|||
if (argumentSize == 0) |
|||
{ |
|||
// argument size is dynamic, use CODESIZE to determine it
|
|||
m_context.appendProgramSize(); // program itself
|
|||
// CODESIZE is program plus manually added arguments
|
|||
m_context << eth::Instruction::CODESIZE << eth::Instruction::SUB; |
|||
} |
|||
else |
|||
m_context << u256(argumentSize); |
|||
// stack: <memptr> <argument size>
|
|||
m_context << eth::Instruction::DUP1; |
|||
m_context.appendProgramSize(); |
|||
m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; |
|||
m_context << eth::Instruction::ADD; |
|||
CompilerUtils(m_context).storeFreeMemoryPointer(); |
|||
appendCalldataUnpacker( |
|||
FunctionType(_constructor).getParameterTypes(), |
|||
true, |
|||
CompilerUtils::freeMemoryPointer + 0x20 |
|||
); |
|||
} |
|||
_constructor.accept(*this); |
|||
} |
|||
|
|||
void Compiler::appendFunctionSelector(ContractDefinition const& _contract) |
|||
{ |
|||
map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.getInterfaceFunctions(); |
|||
map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints; |
|||
|
|||
FunctionDefinition const* fallback = _contract.getFallbackFunction(); |
|||
eth::AssemblyItem notFound = m_context.newTag(); |
|||
// shortcut messages without data if we have many functions in order to be able to receive
|
|||
// ether with constant gas
|
|||
if (interfaceFunctions.size() > 5 || fallback) |
|||
{ |
|||
m_context << eth::Instruction::CALLDATASIZE << eth::Instruction::ISZERO; |
|||
m_context.appendConditionalJumpTo(notFound); |
|||
} |
|||
|
|||
// retrieve the function signature hash from the calldata
|
|||
if (!interfaceFunctions.empty()) |
|||
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); |
|||
|
|||
// stack now is: 1 0 <funhash>
|
|||
for (auto const& it: interfaceFunctions) |
|||
{ |
|||
callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); |
|||
m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; |
|||
m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); |
|||
} |
|||
m_context.appendJumpTo(notFound); |
|||
|
|||
m_context << notFound; |
|||
if (fallback) |
|||
{ |
|||
eth::AssemblyItem returnTag = m_context.pushNewTag(); |
|||
fallback->accept(*this); |
|||
m_context << returnTag; |
|||
appendReturnValuePacker(FunctionType(*fallback).getReturnParameterTypes()); |
|||
} |
|||
else |
|||
m_context << eth::Instruction::STOP; // function not found
|
|||
|
|||
for (auto const& it: interfaceFunctions) |
|||
{ |
|||
FunctionTypePointer const& functionType = it.second; |
|||
solAssert(functionType->hasDeclaration(), ""); |
|||
CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration()); |
|||
m_context << callDataUnpackerEntryPoints.at(it.first); |
|||
eth::AssemblyItem returnTag = m_context.pushNewTag(); |
|||
appendCalldataUnpacker(functionType->getParameterTypes()); |
|||
m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration())); |
|||
m_context << returnTag; |
|||
appendReturnValuePacker(functionType->getReturnParameterTypes()); |
|||
} |
|||
} |
|||
|
|||
void Compiler::appendCalldataUnpacker( |
|||
TypePointers const& _typeParameters, |
|||
bool _fromMemory, |
|||
u256 _startOffset |
|||
) |
|||
{ |
|||
// We do not check the calldata size, everything is zero-paddedd
|
|||
|
|||
//@todo this does not yet support nested dynamic arrays
|
|||
|
|||
if (_startOffset == u256(-1)) |
|||
_startOffset = u256(CompilerUtils::dataStartOffset); |
|||
|
|||
m_context << _startOffset; |
|||
for (TypePointer const& type: _typeParameters) |
|||
{ |
|||
// stack: v1 v2 ... v(k-1) mem_offset
|
|||
switch (type->getCategory()) |
|||
{ |
|||
case Type::Category::Array: |
|||
{ |
|||
auto const& arrayType = dynamic_cast<ArrayType const&>(*type); |
|||
solAssert(arrayType.location() != DataLocation::Storage, ""); |
|||
solAssert(!arrayType.getBaseType()->isDynamicallySized(), "Nested arrays not yet implemented."); |
|||
if (_fromMemory) |
|||
{ |
|||
solAssert( |
|||
arrayType.getBaseType()->isValueType(), |
|||
"Nested memory arrays not yet implemented here." |
|||
); |
|||
// @todo If base type is an array or struct, it is still calldata-style encoded, so
|
|||
// we would have to convert it like below.
|
|||
solAssert(arrayType.location() == DataLocation::Memory, ""); |
|||
// compute data pointer
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; |
|||
//@todo once we support nested arrays, this offset needs to be dynamic.
|
|||
m_context << _startOffset << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP1 << u256(0x20) << eth::Instruction::ADD; |
|||
} |
|||
else |
|||
{ |
|||
// first load from calldata and potentially convert to memory if arrayType is memory
|
|||
TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); |
|||
if (calldataType->isDynamicallySized()) |
|||
{ |
|||
// put on stack: data_pointer length
|
|||
CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); |
|||
// stack: data_offset next_pointer
|
|||
//@todo once we support nested arrays, this offset needs to be dynamic.
|
|||
m_context << eth::Instruction::SWAP1 << _startOffset << eth::Instruction::ADD; |
|||
// stack: next_pointer data_pointer
|
|||
// retrieve length
|
|||
CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); |
|||
// stack: next_pointer length data_pointer
|
|||
m_context << eth::Instruction::SWAP2; |
|||
} |
|||
else |
|||
{ |
|||
// leave the pointer on the stack
|
|||
m_context << eth::Instruction::DUP1; |
|||
m_context << u256(calldataType->getCalldataEncodedSize()) << eth::Instruction::ADD; |
|||
} |
|||
if (arrayType.location() == DataLocation::Memory) |
|||
{ |
|||
// stack: calldata_ref [length] next_calldata
|
|||
// copy to memory
|
|||
// move calldata type up again
|
|||
CompilerUtils(m_context).moveIntoStack(calldataType->getSizeOnStack()); |
|||
CompilerUtils(m_context).convertType(*calldataType, arrayType); |
|||
// fetch next pointer again
|
|||
CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack()); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
default: |
|||
solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); |
|||
CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true); |
|||
} |
|||
} |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) |
|||
{ |
|||
CompilerUtils utils(m_context); |
|||
if (_typeParameters.empty()) |
|||
m_context << eth::Instruction::STOP; |
|||
else |
|||
{ |
|||
utils.fetchFreeMemoryPointer(); |
|||
//@todo optimization: if we return a single memory array, there should be enough space before
|
|||
// its data to add the needed parts and we avoid a memory copy.
|
|||
utils.encodeToMemory(_typeParameters, _typeParameters); |
|||
utils.toSizeAfterFreeMemoryPointer(); |
|||
m_context << eth::Instruction::RETURN; |
|||
} |
|||
} |
|||
|
|||
void Compiler::registerStateVariables(ContractDefinition const& _contract) |
|||
{ |
|||
for (auto const& var: ContractType(_contract).getStateVariables()) |
|||
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); |
|||
} |
|||
|
|||
void Compiler::initializeStateVariables(ContractDefinition const& _contract) |
|||
{ |
|||
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) |
|||
if (variable->getValue() && !variable->isConstant()) |
|||
ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); |
|||
} |
|||
|
|||
bool Compiler::visit(VariableDeclaration const& _variableDeclaration) |
|||
{ |
|||
solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration); |
|||
|
|||
m_context.startFunction(_variableDeclaration); |
|||
m_breakTags.clear(); |
|||
m_continueTags.clear(); |
|||
|
|||
ExpressionCompiler(m_context, m_optimize).appendStateVariableAccessor(_variableDeclaration); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(FunctionDefinition const& _function) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _function); |
|||
|
|||
m_context.startFunction(_function); |
|||
|
|||
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
|
|||
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
|
|||
|
|||
unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); |
|||
if (!_function.isConstructor()) |
|||
// adding 1 for return address.
|
|||
m_context.adjustStackOffset(parametersSize + 1); |
|||
for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters()) |
|||
{ |
|||
m_context.addVariable(*variable, parametersSize); |
|||
parametersSize -= variable->getType()->getSizeOnStack(); |
|||
} |
|||
|
|||
for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters()) |
|||
appendStackVariableInitialisation(*variable); |
|||
for (VariableDeclaration const* localVariable: _function.getLocalVariables()) |
|||
appendStackVariableInitialisation(*localVariable); |
|||
|
|||
if (_function.isConstructor()) |
|||
if (auto c = m_context.getNextConstructor(dynamic_cast<ContractDefinition const&>(*_function.getScope()))) |
|||
appendBaseConstructor(*c); |
|||
|
|||
m_returnTag = m_context.newTag(); |
|||
m_breakTags.clear(); |
|||
m_continueTags.clear(); |
|||
m_stackCleanupForReturn = 0; |
|||
m_currentFunction = &_function; |
|||
m_modifierDepth = 0; |
|||
|
|||
appendModifierOrFunctionCode(); |
|||
|
|||
m_context << m_returnTag; |
|||
|
|||
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
|
|||
// that shows the target positions of the elements, where "-1" denotes that this element needs
|
|||
// to be removed from the stack.
|
|||
// Note that the fact that the return arguments are of increasing index is vital for this
|
|||
// algorithm to work.
|
|||
|
|||
unsigned const c_argumentsSize = CompilerUtils::getSizeOnStack(_function.getParameters()); |
|||
unsigned const c_returnValuesSize = CompilerUtils::getSizeOnStack(_function.getReturnParameters()); |
|||
unsigned const c_localVariablesSize = CompilerUtils::getSizeOnStack(_function.getLocalVariables()); |
|||
|
|||
vector<int> stackLayout; |
|||
stackLayout.push_back(c_returnValuesSize); // target of return address
|
|||
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
|
|||
for (unsigned i = 0; i < c_returnValuesSize; ++i) |
|||
stackLayout.push_back(i); |
|||
stackLayout += vector<int>(c_localVariablesSize, -1); |
|||
|
|||
solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables."); |
|||
while (stackLayout.back() != int(stackLayout.size() - 1)) |
|||
if (stackLayout.back() < 0) |
|||
{ |
|||
m_context << eth::Instruction::POP; |
|||
stackLayout.pop_back(); |
|||
} |
|||
else |
|||
{ |
|||
m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1); |
|||
swap(stackLayout[stackLayout.back()], stackLayout.back()); |
|||
} |
|||
//@todo assert that everything is in place now
|
|||
|
|||
for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters() + _function.getReturnParameters()) |
|||
m_context.removeVariable(*variable); |
|||
for (VariableDeclaration const* localVariable: _function.getLocalVariables()) |
|||
m_context.removeVariable(*localVariable); |
|||
|
|||
m_context.adjustStackOffset(-(int)c_returnValuesSize); |
|||
|
|||
if (!_function.isConstructor()) |
|||
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(IfStatement const& _ifStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); |
|||
compileExpression(_ifStatement.getCondition()); |
|||
m_context << eth::Instruction::ISZERO; |
|||
eth::AssemblyItem falseTag = m_context.appendConditionalJump(); |
|||
eth::AssemblyItem endTag = falseTag; |
|||
_ifStatement.getTrueStatement().accept(*this); |
|||
if (_ifStatement.getFalseStatement()) |
|||
{ |
|||
endTag = m_context.appendJumpToNew(); |
|||
m_context << falseTag; |
|||
_ifStatement.getFalseStatement()->accept(*this); |
|||
} |
|||
m_context << endTag; |
|||
|
|||
checker.check(); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(WhileStatement const& _whileStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); |
|||
eth::AssemblyItem loopStart = m_context.newTag(); |
|||
eth::AssemblyItem loopEnd = m_context.newTag(); |
|||
m_continueTags.push_back(loopStart); |
|||
m_breakTags.push_back(loopEnd); |
|||
|
|||
m_context << loopStart; |
|||
compileExpression(_whileStatement.getCondition()); |
|||
m_context << eth::Instruction::ISZERO; |
|||
m_context.appendConditionalJumpTo(loopEnd); |
|||
|
|||
_whileStatement.getBody().accept(*this); |
|||
|
|||
m_context.appendJumpTo(loopStart); |
|||
m_context << loopEnd; |
|||
|
|||
m_continueTags.pop_back(); |
|||
m_breakTags.pop_back(); |
|||
|
|||
checker.check(); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(ForStatement const& _forStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _forStatement); |
|||
eth::AssemblyItem loopStart = m_context.newTag(); |
|||
eth::AssemblyItem loopEnd = m_context.newTag(); |
|||
eth::AssemblyItem loopNext = m_context.newTag(); |
|||
m_continueTags.push_back(loopNext); |
|||
m_breakTags.push_back(loopEnd); |
|||
|
|||
if (_forStatement.getInitializationExpression()) |
|||
_forStatement.getInitializationExpression()->accept(*this); |
|||
|
|||
m_context << loopStart; |
|||
|
|||
// if there is no terminating condition in for, default is to always be true
|
|||
if (_forStatement.getCondition()) |
|||
{ |
|||
compileExpression(*_forStatement.getCondition()); |
|||
m_context << eth::Instruction::ISZERO; |
|||
m_context.appendConditionalJumpTo(loopEnd); |
|||
} |
|||
|
|||
_forStatement.getBody().accept(*this); |
|||
|
|||
m_context << loopNext; |
|||
|
|||
// for's loop expression if existing
|
|||
if (_forStatement.getLoopExpression()) |
|||
_forStatement.getLoopExpression()->accept(*this); |
|||
|
|||
m_context.appendJumpTo(loopStart); |
|||
m_context << loopEnd; |
|||
|
|||
m_continueTags.pop_back(); |
|||
m_breakTags.pop_back(); |
|||
|
|||
checker.check(); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(Continue const& _continueStatement) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); |
|||
if (!m_continueTags.empty()) |
|||
m_context.appendJumpTo(m_continueTags.back()); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(Break const& _breakStatement) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); |
|||
if (!m_breakTags.empty()) |
|||
m_context.appendJumpTo(m_breakTags.back()); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(Return const& _return) |
|||
{ |
|||
CompilerContext::LocationSetter locationSetter(m_context, _return); |
|||
//@todo modifications are needed to make this work with functions returning multiple values
|
|||
if (Expression const* expression = _return.getExpression()) |
|||
{ |
|||
solAssert(_return.getFunctionReturnParameters(), "Invalid return parameters pointer."); |
|||
VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters()->getParameters().front(); |
|||
compileExpression(*expression, firstVariable.getType()); |
|||
CompilerUtils(m_context).moveToStackVariable(firstVariable); |
|||
} |
|||
for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) |
|||
m_context << eth::Instruction::POP; |
|||
m_context.appendJumpTo(m_returnTag); |
|||
m_context.adjustStackOffset(m_stackCleanupForReturn); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); |
|||
if (Expression const* expression = _variableDeclarationStatement.getExpression()) |
|||
{ |
|||
compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); |
|||
CompilerUtils(m_context).moveToStackVariable(_variableDeclarationStatement.getDeclaration()); |
|||
} |
|||
checker.check(); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(ExpressionStatement const& _expressionStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); |
|||
Expression const& expression = _expressionStatement.getExpression(); |
|||
compileExpression(expression); |
|||
CompilerUtils(m_context).popStackElement(*expression.getType()); |
|||
checker.check(); |
|||
return false; |
|||
} |
|||
|
|||
bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) |
|||
{ |
|||
StackHeightChecker checker(m_context); |
|||
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); |
|||
++m_modifierDepth; |
|||
appendModifierOrFunctionCode(); |
|||
--m_modifierDepth; |
|||
checker.check(); |
|||
return true; |
|||
} |
|||
|
|||
void Compiler::appendFunctionsWithoutCode() |
|||
{ |
|||
set<Declaration const*> functions = m_context.getFunctionsWithoutCode(); |
|||
while (!functions.empty()) |
|||
{ |
|||
for (Declaration const* function: functions) |
|||
{ |
|||
m_context.setStackOffset(0); |
|||
function->accept(*this); |
|||
} |
|||
functions = m_context.getFunctionsWithoutCode(); |
|||
} |
|||
} |
|||
|
|||
void Compiler::appendModifierOrFunctionCode() |
|||
{ |
|||
solAssert(m_currentFunction, ""); |
|||
if (m_modifierDepth >= m_currentFunction->getModifiers().size()) |
|||
m_currentFunction->getBody().accept(*this); |
|||
else |
|||
{ |
|||
ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; |
|||
|
|||
// constructor call should be excluded
|
|||
if (dynamic_cast<ContractDefinition const*>(&modifierInvocation->getName()->getReferencedDeclaration())) |
|||
{ |
|||
++m_modifierDepth; |
|||
appendModifierOrFunctionCode(); |
|||
--m_modifierDepth; |
|||
return; |
|||
} |
|||
|
|||
ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); |
|||
CompilerContext::LocationSetter locationSetter(m_context, modifier); |
|||
solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); |
|||
for (unsigned i = 0; i < modifier.getParameters().size(); ++i) |
|||
{ |
|||
m_context.addVariable(*modifier.getParameters()[i]); |
|||
compileExpression(*modifierInvocation->getArguments()[i], |
|||
modifier.getParameters()[i]->getType()); |
|||
} |
|||
for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) |
|||
appendStackVariableInitialisation(*localVariable); |
|||
|
|||
unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + |
|||
CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); |
|||
m_stackCleanupForReturn += c_stackSurplus; |
|||
|
|||
modifier.getBody().accept(*this); |
|||
|
|||
for (unsigned i = 0; i < c_stackSurplus; ++i) |
|||
m_context << eth::Instruction::POP; |
|||
m_stackCleanupForReturn -= c_stackSurplus; |
|||
} |
|||
} |
|||
|
|||
void Compiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) |
|||
{ |
|||
CompilerContext::LocationSetter location(m_context, _variable); |
|||
m_context.addVariable(_variable); |
|||
CompilerUtils(m_context).pushZeroValue(*_variable.getType()); |
|||
} |
|||
|
|||
void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) |
|||
{ |
|||
ExpressionCompiler expressionCompiler(m_context, m_optimize); |
|||
expressionCompiler.compile(_expression); |
|||
if (_targetType) |
|||
CompilerUtils(m_context).convertType(*_expression.getType(), *_targetType); |
|||
} |
|||
|
|||
eth::Assembly Compiler::getCloneRuntime() |
|||
{ |
|||
eth::Assembly a; |
|||
a << eth::Instruction::CALLDATASIZE; |
|||
a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY; |
|||
//@todo adjust for larger return values, make this dynamic.
|
|||
a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE; |
|||
// unfortunately, we have to send the value again, so that CALLVALUE returns the correct value
|
|||
// in the callcoded contract.
|
|||
a << u256(0) << eth::Instruction::CALLVALUE; |
|||
// this is the address which has to be substituted by the linker.
|
|||
//@todo implement as special "marker" AssemblyItem.
|
|||
a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); |
|||
a << u256(eth::c_callGas + eth::c_callValueTransferGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB; |
|||
a << eth::Instruction::CALLCODE; |
|||
//@todo adjust for larger return values, make this dynamic.
|
|||
a << u256(0x20) << u256(0) << eth::Instruction::RETURN; |
|||
return a; |
|||
} |
@ -1,143 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity AST to EVM bytecode compiler. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <functional> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/CompilerContext.h> |
|||
#include <libevmasm/Assembly.h> |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
class Compiler: private ASTConstVisitor |
|||
{ |
|||
public: |
|||
explicit Compiler(bool _optimize = false, unsigned _runs = 200): |
|||
m_optimize(_optimize), |
|||
m_optimizeRuns(_runs), |
|||
m_context(), |
|||
m_returnTag(m_context.newTag()) |
|||
{ |
|||
} |
|||
|
|||
void compileContract(ContractDefinition const& _contract, |
|||
std::map<ContractDefinition const*, bytes const*> const& _contracts); |
|||
/// Compiles a contract that uses CALLCODE to call into a pre-deployed version of the given
|
|||
/// contract at runtime, but contains the full creation-time code.
|
|||
void compileClone( |
|||
ContractDefinition const& _contract, |
|||
std::map<ContractDefinition const*, bytes const*> const& _contracts |
|||
); |
|||
bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } |
|||
bytes getRuntimeBytecode() { return m_context.getAssembledRuntimeBytecode(m_runtimeSub); } |
|||
/// @arg _sourceCodes is the map of input files to source code strings
|
|||
/// @arg _inJsonFromat shows whether the out should be in Json format
|
|||
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const |
|||
{ |
|||
return m_context.streamAssembly(_stream, _sourceCodes, _inJsonFormat); |
|||
} |
|||
/// @returns Assembly items of the normal compiler context
|
|||
eth::AssemblyItems const& getAssemblyItems() const { return m_context.getAssembly().getItems(); } |
|||
/// @returns Assembly items of the runtime compiler context
|
|||
eth::AssemblyItems const& getRuntimeAssemblyItems() const { return m_context.getAssembly().getSub(m_runtimeSub).getItems(); } |
|||
|
|||
/// @returns the entry label of the given function. Might return an AssemblyItem of type
|
|||
/// UndefinedItem if it does not exist yet.
|
|||
eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; |
|||
|
|||
private: |
|||
/// Registers the non-function objects inside the contract with the context.
|
|||
void initializeContext(ContractDefinition const& _contract, |
|||
std::map<ContractDefinition const*, bytes const*> const& _contracts); |
|||
/// Adds the code that is run at creation time. Should be run after exchanging the run-time context
|
|||
/// with a new and initialized context. Adds the constructor code.
|
|||
void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); |
|||
/// Appends state variable initialisation and constructor code.
|
|||
void appendInitAndConstructorCode(ContractDefinition const& _contract); |
|||
void appendBaseConstructor(FunctionDefinition const& _constructor); |
|||
void appendConstructor(FunctionDefinition const& _constructor); |
|||
void appendFunctionSelector(ContractDefinition const& _contract); |
|||
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
|
|||
/// From memory if @a _fromMemory is true, otherwise from call data.
|
|||
/// Expects source offset on the stack.
|
|||
void appendCalldataUnpacker( |
|||
TypePointers const& _typeParameters, |
|||
bool _fromMemory = false, |
|||
u256 _startOffset = u256(-1) |
|||
); |
|||
void appendReturnValuePacker(TypePointers const& _typeParameters); |
|||
|
|||
void registerStateVariables(ContractDefinition const& _contract); |
|||
void initializeStateVariables(ContractDefinition const& _contract); |
|||
|
|||
/// Initialises all memory arrays in the local variables to point to an empty location.
|
|||
void initialiseMemoryArrays(std::vector<VariableDeclaration const*> _variables); |
|||
/// Pushes the initialised value of the given type to the stack. If the type is a memory
|
|||
/// reference type, allocates memory and pushes the memory pointer.
|
|||
/// Not to be used for storage references.
|
|||
void initialiseInMemory(Type const& _type); |
|||
|
|||
virtual bool visit(VariableDeclaration const& _variableDeclaration) override; |
|||
virtual bool visit(FunctionDefinition const& _function) override; |
|||
virtual bool visit(IfStatement const& _ifStatement) override; |
|||
virtual bool visit(WhileStatement const& _whileStatement) override; |
|||
virtual bool visit(ForStatement const& _forStatement) override; |
|||
virtual bool visit(Continue const& _continue) override; |
|||
virtual bool visit(Break const& _break) override; |
|||
virtual bool visit(Return const& _return) override; |
|||
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; |
|||
virtual bool visit(ExpressionStatement const& _expressionStatement) override; |
|||
virtual bool visit(PlaceholderStatement const&) override; |
|||
|
|||
/// Repeatedly visits all function which are referenced but which are not compiled yet.
|
|||
void appendFunctionsWithoutCode(); |
|||
|
|||
/// Appends one layer of function modifier code of the current function, or the function
|
|||
/// body itself if the last modifier was reached.
|
|||
void appendModifierOrFunctionCode(); |
|||
|
|||
void appendStackVariableInitialisation(VariableDeclaration const& _variable); |
|||
void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); |
|||
|
|||
/// @returns the runtime assembly for clone contracts.
|
|||
static eth::Assembly getCloneRuntime(); |
|||
|
|||
bool const m_optimize; |
|||
unsigned const m_optimizeRuns; |
|||
CompilerContext m_context; |
|||
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly
|
|||
CompilerContext m_runtimeContext; |
|||
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
|
|||
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
|
|||
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
|
|||
unsigned m_modifierDepth = 0; |
|||
FunctionDefinition const* m_currentFunction = nullptr; |
|||
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
|
|||
// arguments for base constructors, filled in derived-to-base order
|
|||
std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,214 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Utilities for the solidity compiler. |
|||
*/ |
|||
|
|||
#include <utility> |
|||
#include <numeric> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Compiler.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration) |
|||
{ |
|||
m_magicGlobals.insert(&_declaration); |
|||
} |
|||
|
|||
void CompilerContext::addStateVariable( |
|||
VariableDeclaration const& _declaration, |
|||
u256 const& _storageOffset, |
|||
unsigned _byteOffset |
|||
) |
|||
{ |
|||
m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); |
|||
} |
|||
|
|||
void CompilerContext::startFunction(Declaration const& _function) |
|||
{ |
|||
m_functionsWithCode.insert(&_function); |
|||
*this << getFunctionEntryLabel(_function); |
|||
} |
|||
|
|||
void CompilerContext::addVariable(VariableDeclaration const& _declaration, |
|||
unsigned _offsetToCurrent) |
|||
{ |
|||
solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); |
|||
m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; |
|||
} |
|||
|
|||
void CompilerContext::removeVariable(VariableDeclaration const& _declaration) |
|||
{ |
|||
solAssert(!!m_localVariables.count(&_declaration), ""); |
|||
m_localVariables.erase(&_declaration); |
|||
} |
|||
|
|||
bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const |
|||
{ |
|||
auto ret = m_compiledContracts.find(&_contract); |
|||
solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); |
|||
return *ret->second; |
|||
} |
|||
|
|||
bool CompilerContext::isLocalVariable(Declaration const* _declaration) const |
|||
{ |
|||
return !!m_localVariables.count(_declaration); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _declaration) |
|||
{ |
|||
auto res = m_functionEntryLabels.find(&_declaration); |
|||
if (res == m_functionEntryLabels.end()) |
|||
{ |
|||
eth::AssemblyItem tag(m_asm.newTag()); |
|||
m_functionEntryLabels.insert(make_pair(&_declaration, tag)); |
|||
return tag.tag(); |
|||
} |
|||
else |
|||
return res->second.tag(); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getFunctionEntryLabelIfExists(Declaration const& _declaration) const |
|||
{ |
|||
auto res = m_functionEntryLabels.find(&_declaration); |
|||
return res == m_functionEntryLabels.end() ? eth::AssemblyItem(eth::UndefinedItem) : res->second.tag(); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) |
|||
{ |
|||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); |
|||
return getVirtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) |
|||
{ |
|||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); |
|||
return getVirtualFunctionEntryLabel(_function, getSuperContract(_base)); |
|||
} |
|||
|
|||
FunctionDefinition const* CompilerContext::getNextConstructor(ContractDefinition const& _contract) const |
|||
{ |
|||
vector<ContractDefinition const*>::const_iterator it = getSuperContract(_contract); |
|||
for (; it != m_inheritanceHierarchy.end(); ++it) |
|||
if ((*it)->getConstructor()) |
|||
return (*it)->getConstructor(); |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
set<Declaration const*> CompilerContext::getFunctionsWithoutCode() |
|||
{ |
|||
set<Declaration const*> functions; |
|||
for (auto const& it: m_functionEntryLabels) |
|||
if (m_functionsWithCode.count(it.first) == 0) |
|||
functions.insert(it.first); |
|||
return functions; |
|||
} |
|||
|
|||
ModifierDefinition const& CompilerContext::getFunctionModifier(string const& _name) const |
|||
{ |
|||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); |
|||
for (ContractDefinition const* contract: m_inheritanceHierarchy) |
|||
for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers()) |
|||
if (modifier->getName() == _name) |
|||
return *modifier.get(); |
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() |
|||
<< errinfo_comment("Function modifier " + _name + " not found.")); |
|||
} |
|||
|
|||
unsigned CompilerContext::getBaseStackOffsetOfVariable(Declaration const& _declaration) const |
|||
{ |
|||
auto res = m_localVariables.find(&_declaration); |
|||
solAssert(res != m_localVariables.end(), "Variable not found on stack."); |
|||
return res->second; |
|||
} |
|||
|
|||
unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const |
|||
{ |
|||
return m_asm.deposit() - _baseOffset - 1; |
|||
} |
|||
|
|||
unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const |
|||
{ |
|||
return m_asm.deposit() - _offset - 1; |
|||
} |
|||
|
|||
pair<u256, unsigned> CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const |
|||
{ |
|||
auto it = m_stateVariables.find(&_declaration); |
|||
solAssert(it != m_stateVariables.end(), "Variable not found in storage."); |
|||
return it->second; |
|||
} |
|||
|
|||
CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpType) |
|||
{ |
|||
eth::AssemblyItem item(eth::Instruction::JUMP); |
|||
item.setJumpType(_jumpType); |
|||
return *this << item; |
|||
} |
|||
|
|||
void CompilerContext::resetVisitedNodes(ASTNode const* _node) |
|||
{ |
|||
stack<ASTNode const*> newStack; |
|||
newStack.push(_node); |
|||
std::swap(m_visitedNodes, newStack); |
|||
updateSourceLocation(); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel( |
|||
FunctionDefinition const& _function, |
|||
vector<ContractDefinition const*>::const_iterator _searchStart |
|||
) |
|||
{ |
|||
string name = _function.getName(); |
|||
FunctionType functionType(_function); |
|||
auto it = _searchStart; |
|||
for (; it != m_inheritanceHierarchy.end(); ++it) |
|||
for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions()) |
|||
if ( |
|||
function->getName() == name && |
|||
!function->isConstructor() && |
|||
FunctionType(*function).hasEqualArgumentTypes(functionType) |
|||
) |
|||
return getFunctionEntryLabel(*function); |
|||
solAssert(false, "Super function " + name + " not found."); |
|||
return m_asm.newTag(); // not reached
|
|||
} |
|||
|
|||
vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const |
|||
{ |
|||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); |
|||
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract); |
|||
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); |
|||
return ++it; |
|||
} |
|||
|
|||
void CompilerContext::updateSourceLocation() |
|||
{ |
|||
m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation()); |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,183 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Utilities for the solidity compiler. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <stack> |
|||
#include <utility> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libevmasm/Assembly.h> |
|||
#include <libsolidity/ASTForward.h> |
|||
#include <libsolidity/Types.h> |
|||
#include <libdevcore/Common.h> |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
|
|||
/**
|
|||
* Context to be shared by all units that compile the same contract. |
|||
* It stores the generated bytecode and the position of identifiers in memory and on the stack. |
|||
*/ |
|||
class CompilerContext |
|||
{ |
|||
public: |
|||
void addMagicGlobal(MagicVariableDeclaration const& _declaration); |
|||
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); |
|||
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); |
|||
void removeVariable(VariableDeclaration const& _declaration); |
|||
|
|||
void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; } |
|||
bytes const& getCompiledContract(ContractDefinition const& _contract) const; |
|||
|
|||
void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } |
|||
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } |
|||
unsigned getStackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } |
|||
|
|||
bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } |
|||
bool isLocalVariable(Declaration const* _declaration) const; |
|||
bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } |
|||
|
|||
/// @returns the entry label of the given function and creates it if it does not exist yet.
|
|||
eth::AssemblyItem getFunctionEntryLabel(Declaration const& _declaration); |
|||
/// @returns the entry label of the given function. Might return an AssemblyItem of type
|
|||
/// UndefinedItem if it does not exist yet.
|
|||
eth::AssemblyItem getFunctionEntryLabelIfExists(Declaration const& _declaration) const; |
|||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } |
|||
/// @returns the entry label of the given function and takes overrides into account.
|
|||
eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function); |
|||
/// @returns the entry label of a function that overrides the given declaration from the most derived class just
|
|||
/// above _base in the current inheritance hierarchy.
|
|||
eth::AssemblyItem getSuperFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); |
|||
FunctionDefinition const* getNextConstructor(ContractDefinition const& _contract) const; |
|||
|
|||
/// @returns the set of functions for which we still need to generate code
|
|||
std::set<Declaration const*> getFunctionsWithoutCode(); |
|||
/// Resets function specific members, inserts the function entry label and marks the function
|
|||
/// as "having code".
|
|||
void startFunction(Declaration const& _function); |
|||
|
|||
ModifierDefinition const& getFunctionModifier(std::string const& _name) const; |
|||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
|||
unsigned getBaseStackOffsetOfVariable(Declaration const& _declaration) const; |
|||
/// If supplied by a value returned by @ref getBaseStackOffsetOfVariable(variable), returns
|
|||
/// the distance of that variable from the current top of the stack.
|
|||
unsigned baseToCurrentStackOffset(unsigned _baseOffset) const; |
|||
/// Converts an offset relative to the current stack height to a value that can be used later
|
|||
/// with baseToCurrentStackOffset to point to the same stack element.
|
|||
unsigned currentToBaseStackOffset(unsigned _offset) const; |
|||
/// @returns pair of slot and byte offset of the value inside this slot.
|
|||
std::pair<u256, unsigned> getStorageLocationOfVariable(Declaration const& _declaration) const; |
|||
|
|||
/// Appends a JUMPI instruction to a new tag and @returns the tag
|
|||
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } |
|||
/// Appends a JUMPI instruction to @a _tag
|
|||
CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } |
|||
/// Appends a JUMP to a new tag and @returns the tag
|
|||
eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } |
|||
/// Appends a JUMP to a tag already on the stack
|
|||
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); |
|||
/// Returns an "ErrorTag"
|
|||
eth::AssemblyItem errorTag() { return m_asm.errorTag(); } |
|||
/// Appends a JUMP to a specific tag
|
|||
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } |
|||
/// Appends pushing of a new tag and @returns the new tag.
|
|||
eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } |
|||
/// @returns a new tag without pushing any opcodes or data
|
|||
eth::AssemblyItem newTag() { return m_asm.newTag(); } |
|||
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
|||
/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
|
|||
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } |
|||
/// Pushes the size of the final program
|
|||
void appendProgramSize() { return m_asm.appendProgramSize(); } |
|||
/// Adds data to the data section, pushes a reference to the stack
|
|||
eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } |
|||
/// Resets the stack of visited nodes with a new stack having only @c _node
|
|||
void resetVisitedNodes(ASTNode const* _node); |
|||
/// Pops the stack of visited nodes
|
|||
void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); } |
|||
/// Pushes an ASTNode to the stack of visited nodes
|
|||
void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } |
|||
|
|||
/// Append elements to the current instruction list and adjust @a m_stackOffset.
|
|||
CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } |
|||
CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } |
|||
CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } |
|||
CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } |
|||
|
|||
void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } |
|||
|
|||
eth::Assembly const& getAssembly() const { return m_asm; } |
|||
/// @arg _sourceCodes is the map of input files to source code strings
|
|||
/// @arg _inJsonFormat shows whether the out should be in Json format
|
|||
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const |
|||
{ |
|||
return m_asm.stream(_stream, "", _sourceCodes, _inJsonFormat); |
|||
} |
|||
|
|||
bytes getAssembledBytecode() { return m_asm.assemble(); } |
|||
bytes getAssembledRuntimeBytecode(size_t _subIndex) { m_asm.assemble(); return m_asm.data(u256(_subIndex)); } |
|||
|
|||
/**
|
|||
* Helper class to pop the visited nodes stack when a scope closes |
|||
*/ |
|||
class LocationSetter: public ScopeGuard |
|||
{ |
|||
public: |
|||
LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): |
|||
ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } |
|||
}; |
|||
|
|||
private: |
|||
/// @returns the entry label of the given function - searches the inheritance hierarchy
|
|||
/// startig from the given point towards the base.
|
|||
eth::AssemblyItem getVirtualFunctionEntryLabel( |
|||
FunctionDefinition const& _function, |
|||
std::vector<ContractDefinition const*>::const_iterator _searchStart |
|||
); |
|||
/// @returns an iterator to the contract directly above the given contract.
|
|||
std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const; |
|||
/// Updates source location set in the assembly.
|
|||
void updateSourceLocation(); |
|||
|
|||
eth::Assembly m_asm; |
|||
/// Magic global variables like msg, tx or this, distinguished by type.
|
|||
std::set<Declaration const*> m_magicGlobals; |
|||
/// Other already compiled contracts to be used in contract creation calls.
|
|||
std::map<ContractDefinition const*, bytes const*> m_compiledContracts; |
|||
/// Storage offsets of state variables
|
|||
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; |
|||
/// Offsets of local variables on the stack (relative to stack base).
|
|||
std::map<Declaration const*, unsigned> m_localVariables; |
|||
/// Labels pointing to the entry points of functions.
|
|||
std::map<Declaration const*, eth::AssemblyItem> m_functionEntryLabels; |
|||
/// Set of functions for which we did not yet generate code.
|
|||
std::set<Declaration const*> m_functionsWithCode; |
|||
/// List of current inheritance hierarchy from derived to base.
|
|||
std::vector<ContractDefinition const*> m_inheritanceHierarchy; |
|||
/// Stack of current visited AST nodes, used for location attachment
|
|||
std::stack<ASTNode const*> m_visitedNodes; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,391 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @author Gav Wood <g@ethdev.com> |
|||
* @date 2014 |
|||
* Full-stack compiler that converts a source code string to bytecode. |
|||
*/ |
|||
|
|||
#include <boost/algorithm/string.hpp> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/GlobalContext.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/Compiler.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/InterfaceHandler.h> |
|||
|
|||
#include <libdevcore/SHA3.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
const map<string, string> StandardSources = map<string, string>{ |
|||
{"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, |
|||
{"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, |
|||
{"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,bytes3 name,uint256 denom){}function register(bytes3 name,uint256 denom){}function unregister(){}})"}, |
|||
{"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, |
|||
{"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, |
|||
{"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, |
|||
{"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(bytes32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, |
|||
{"NameReg", R"(contract NameReg{function register(bytes32 name){}function addressOf(bytes32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(bytes32 name){}})"}, |
|||
{"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, |
|||
{"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, |
|||
{"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} |
|||
}; |
|||
|
|||
CompilerStack::CompilerStack(bool _addStandardSources): |
|||
m_parseSuccessful(false) |
|||
{ |
|||
if (_addStandardSources) |
|||
addSources(StandardSources, true); // add them as libraries
|
|||
} |
|||
|
|||
void CompilerStack::reset(bool _keepSources, bool _addStandardSources) |
|||
{ |
|||
m_parseSuccessful = false; |
|||
if (_keepSources) |
|||
for (auto sourcePair: m_sources) |
|||
sourcePair.second.reset(); |
|||
else |
|||
{ |
|||
m_sources.clear(); |
|||
if (_addStandardSources) |
|||
addSources(StandardSources, true); |
|||
} |
|||
m_globalContext.reset(); |
|||
m_sourceOrder.clear(); |
|||
m_contracts.clear(); |
|||
} |
|||
|
|||
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) |
|||
{ |
|||
bool existed = m_sources.count(_name) != 0; |
|||
reset(true); |
|||
m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); |
|||
m_sources[_name].isLibrary = _isLibrary; |
|||
return existed; |
|||
} |
|||
|
|||
void CompilerStack::setSource(string const& _sourceCode) |
|||
{ |
|||
reset(); |
|||
addSource("", _sourceCode); |
|||
} |
|||
|
|||
void CompilerStack::parse() |
|||
{ |
|||
for (auto& sourcePair: m_sources) |
|||
{ |
|||
sourcePair.second.scanner->reset(); |
|||
sourcePair.second.ast = Parser().parse(sourcePair.second.scanner); |
|||
} |
|||
resolveImports(); |
|||
|
|||
m_globalContext = make_shared<GlobalContext>(); |
|||
NameAndTypeResolver resolver(m_globalContext->getDeclarations()); |
|||
for (Source const* source: m_sourceOrder) |
|||
resolver.registerDeclarations(*source->ast); |
|||
for (Source const* source: m_sourceOrder) |
|||
for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
m_globalContext->setCurrentContract(*contract); |
|||
resolver.updateDeclaration(*m_globalContext->getCurrentThis()); |
|||
resolver.updateDeclaration(*m_globalContext->getCurrentSuper()); |
|||
resolver.resolveNamesAndTypes(*contract); |
|||
m_contracts[contract->getName()].contract = contract; |
|||
} |
|||
InterfaceHandler interfaceHandler; |
|||
for (Source const* source: m_sourceOrder) |
|||
for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
m_globalContext->setCurrentContract(*contract); |
|||
resolver.updateDeclaration(*m_globalContext->getCurrentThis()); |
|||
resolver.checkTypeRequirements(*contract); |
|||
contract->setDevDocumentation(interfaceHandler.devDocumentation(*contract)); |
|||
contract->setUserDocumentation(interfaceHandler.userDocumentation(*contract)); |
|||
m_contracts[contract->getName()].contract = contract; |
|||
} |
|||
m_parseSuccessful = true; |
|||
} |
|||
|
|||
void CompilerStack::parse(string const& _sourceCode) |
|||
{ |
|||
setSource(_sourceCode); |
|||
parse(); |
|||
} |
|||
|
|||
vector<string> CompilerStack::getContractNames() const |
|||
{ |
|||
if (!m_parseSuccessful) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); |
|||
vector<string> contractNames; |
|||
for (auto const& contract: m_contracts) |
|||
contractNames.push_back(contract.first); |
|||
return contractNames; |
|||
} |
|||
|
|||
|
|||
void CompilerStack::compile(bool _optimize, unsigned _runs) |
|||
{ |
|||
if (!m_parseSuccessful) |
|||
parse(); |
|||
|
|||
map<ContractDefinition const*, bytes const*> contractBytecode; |
|||
for (Source const* source: m_sourceOrder) |
|||
for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
if (!contract->isFullyImplemented()) |
|||
continue; |
|||
shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize, _runs); |
|||
compiler->compileContract(*contract, contractBytecode); |
|||
Contract& compiledContract = m_contracts.at(contract->getName()); |
|||
compiledContract.bytecode = compiler->getAssembledBytecode(); |
|||
compiledContract.runtimeBytecode = compiler->getRuntimeBytecode(); |
|||
compiledContract.compiler = move(compiler); |
|||
compiler = make_shared<Compiler>(_optimize, _runs); |
|||
compiler->compileContract(*contract, contractBytecode); |
|||
contractBytecode[compiledContract.contract] = &compiledContract.bytecode; |
|||
|
|||
Compiler cloneCompiler(_optimize, _runs); |
|||
cloneCompiler.compileClone(*contract, contractBytecode); |
|||
compiledContract.cloneBytecode = cloneCompiler.getAssembledBytecode(); |
|||
} |
|||
} |
|||
|
|||
bytes const& CompilerStack::compile(string const& _sourceCode, bool _optimize) |
|||
{ |
|||
parse(_sourceCode); |
|||
compile(_optimize); |
|||
return getBytecode(); |
|||
} |
|||
|
|||
eth::AssemblyItems const* CompilerStack::getAssemblyItems(string const& _contractName) const |
|||
{ |
|||
Contract const& contract = getContract(_contractName); |
|||
return contract.compiler ? &getContract(_contractName).compiler->getAssemblyItems() : nullptr; |
|||
} |
|||
|
|||
eth::AssemblyItems const* CompilerStack::getRuntimeAssemblyItems(string const& _contractName) const |
|||
{ |
|||
Contract const& contract = getContract(_contractName); |
|||
return contract.compiler ? &getContract(_contractName).compiler->getRuntimeAssemblyItems() : nullptr; |
|||
} |
|||
|
|||
bytes const& CompilerStack::getBytecode(string const& _contractName) const |
|||
{ |
|||
return getContract(_contractName).bytecode; |
|||
} |
|||
|
|||
bytes const& CompilerStack::getRuntimeBytecode(string const& _contractName) const |
|||
{ |
|||
return getContract(_contractName).runtimeBytecode; |
|||
} |
|||
|
|||
bytes const& CompilerStack::getCloneBytecode(string const& _contractName) const |
|||
{ |
|||
return getContract(_contractName).cloneBytecode; |
|||
} |
|||
|
|||
dev::h256 CompilerStack::getContractCodeHash(string const& _contractName) const |
|||
{ |
|||
return dev::sha3(getRuntimeBytecode(_contractName)); |
|||
} |
|||
|
|||
Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _contractName, StringMap _sourceCodes, bool _inJsonFormat) const |
|||
{ |
|||
Contract const& contract = getContract(_contractName); |
|||
if (contract.compiler) |
|||
return contract.compiler->streamAssembly(_outStream, _sourceCodes, _inJsonFormat); |
|||
else |
|||
{ |
|||
_outStream << "Contract not fully implemented" << endl; |
|||
return Json::Value(); |
|||
} |
|||
} |
|||
|
|||
string const& CompilerStack::getInterface(string const& _contractName) const |
|||
{ |
|||
return getMetadata(_contractName, DocumentationType::ABIInterface); |
|||
} |
|||
|
|||
string const& CompilerStack::getSolidityInterface(string const& _contractName) const |
|||
{ |
|||
return getMetadata(_contractName, DocumentationType::ABISolidityInterface); |
|||
} |
|||
|
|||
string const& CompilerStack::getMetadata(string const& _contractName, DocumentationType _type) const |
|||
{ |
|||
if (!m_parseSuccessful) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); |
|||
|
|||
Contract const& contract = getContract(_contractName); |
|||
|
|||
std::unique_ptr<string const>* doc; |
|||
|
|||
// checks wheather we already have the documentation
|
|||
switch (_type) |
|||
{ |
|||
case DocumentationType::NatspecUser: |
|||
doc = &contract.userDocumentation; |
|||
break; |
|||
case DocumentationType::NatspecDev: |
|||
doc = &contract.devDocumentation; |
|||
break; |
|||
case DocumentationType::ABIInterface: |
|||
doc = &contract.interface; |
|||
break; |
|||
case DocumentationType::ABISolidityInterface: |
|||
doc = &contract.solidityInterface; |
|||
break; |
|||
default: |
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); |
|||
} |
|||
|
|||
// caches the result
|
|||
if (!*doc) |
|||
doc->reset(new string(contract.interfaceHandler->getDocumentation(*contract.contract, _type))); |
|||
|
|||
return *(*doc); |
|||
} |
|||
|
|||
Scanner const& CompilerStack::getScanner(string const& _sourceName) const |
|||
{ |
|||
return *getSource(_sourceName).scanner; |
|||
} |
|||
|
|||
SourceUnit const& CompilerStack::getAST(string const& _sourceName) const |
|||
{ |
|||
return *getSource(_sourceName).ast; |
|||
} |
|||
|
|||
ContractDefinition const& CompilerStack::getContractDefinition(string const& _contractName) const |
|||
{ |
|||
return *getContract(_contractName).contract; |
|||
} |
|||
|
|||
size_t CompilerStack::getFunctionEntryPoint( |
|||
std::string const& _contractName, |
|||
FunctionDefinition const& _function |
|||
) const |
|||
{ |
|||
shared_ptr<Compiler> const& compiler = getContract(_contractName).compiler; |
|||
if (!compiler) |
|||
return 0; |
|||
eth::AssemblyItem tag = compiler->getFunctionEntryLabel(_function); |
|||
if (tag.type() == eth::UndefinedItem) |
|||
return 0; |
|||
eth::AssemblyItems const& items = compiler->getRuntimeAssemblyItems(); |
|||
for (size_t i = 0; i < items.size(); ++i) |
|||
if (items.at(i).type() == eth::Tag && items.at(i).data() == tag.data()) |
|||
return i; |
|||
return 0; |
|||
} |
|||
|
|||
bytes CompilerStack::staticCompile(std::string const& _sourceCode, bool _optimize) |
|||
{ |
|||
CompilerStack stack; |
|||
return stack.compile(_sourceCode, _optimize); |
|||
} |
|||
|
|||
tuple<int, int, int, int> CompilerStack::positionFromSourceLocation(SourceLocation const& _sourceLocation) const |
|||
{ |
|||
int startLine; |
|||
int startColumn; |
|||
int endLine; |
|||
int endColumn; |
|||
tie(startLine, startColumn) = getScanner(*_sourceLocation.sourceName).translatePositionToLineColumn(_sourceLocation.start); |
|||
tie(endLine, endColumn) = getScanner(*_sourceLocation.sourceName).translatePositionToLineColumn(_sourceLocation.end); |
|||
|
|||
return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn); |
|||
} |
|||
|
|||
void CompilerStack::resolveImports() |
|||
{ |
|||
// topological sorting (depth first search) of the import graph, cutting potential cycles
|
|||
vector<Source const*> sourceOrder; |
|||
set<Source const*> sourcesSeen; |
|||
|
|||
function<void(Source const*)> toposort = [&](Source const* _source) |
|||
{ |
|||
if (sourcesSeen.count(_source)) |
|||
return; |
|||
sourcesSeen.insert(_source); |
|||
for (ASTPointer<ASTNode> const& node: _source->ast->getNodes()) |
|||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get())) |
|||
{ |
|||
string const& id = import->getIdentifier(); |
|||
if (!m_sources.count(id)) |
|||
BOOST_THROW_EXCEPTION(ParserError() |
|||
<< errinfo_sourceLocation(import->getLocation()) |
|||
<< errinfo_comment("Source not found.")); |
|||
toposort(&m_sources[id]); |
|||
} |
|||
sourceOrder.push_back(_source); |
|||
}; |
|||
|
|||
for (auto const& sourcePair: m_sources) |
|||
if (!sourcePair.second.isLibrary) |
|||
toposort(&sourcePair.second); |
|||
|
|||
swap(m_sourceOrder, sourceOrder); |
|||
} |
|||
|
|||
std::string CompilerStack::defaultContractName() const |
|||
{ |
|||
return getContract("").contract->getName(); |
|||
} |
|||
|
|||
CompilerStack::Contract const& CompilerStack::getContract(string const& _contractName) const |
|||
{ |
|||
if (m_contracts.empty()) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); |
|||
string contractName = _contractName; |
|||
if (_contractName.empty()) |
|||
// try to find some user-supplied contract
|
|||
for (auto const& it: m_sources) |
|||
if (!StandardSources.count(it.first)) |
|||
for (ASTPointer<ASTNode> const& node: it.second.ast->getNodes()) |
|||
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) |
|||
contractName = contract->getName(); |
|||
auto it = m_contracts.find(contractName); |
|||
if (it == m_contracts.end()) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); |
|||
return it->second; |
|||
} |
|||
|
|||
CompilerStack::Source const& CompilerStack::getSource(string const& _sourceName) const |
|||
{ |
|||
auto it = m_sources.find(_sourceName); |
|||
if (it == m_sources.end()) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Given source file not found.")); |
|||
return it->second; |
|||
} |
|||
|
|||
CompilerStack::Contract::Contract(): interfaceHandler(make_shared<InterfaceHandler>()) {} |
|||
|
|||
} |
|||
} |
@ -1,198 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @author Gav Wood <g@ethdev.com> |
|||
* @date 2014 |
|||
* Full-stack compiler that converts a source code string to bytecode. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <string> |
|||
#include <memory> |
|||
#include <vector> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <json/json.h> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/FixedHash.h> |
|||
#include <libevmasm/SourceLocation.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace eth |
|||
{ |
|||
class AssemblyItem; |
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
} |
|||
|
|||
namespace solidity |
|||
{ |
|||
|
|||
// forward declarations
|
|||
class Scanner; |
|||
class ContractDefinition; |
|||
class FunctionDefinition; |
|||
class SourceUnit; |
|||
class Compiler; |
|||
class GlobalContext; |
|||
class InterfaceHandler; |
|||
|
|||
enum class DocumentationType: uint8_t |
|||
{ |
|||
NatspecUser = 1, |
|||
NatspecDev, |
|||
ABIInterface, |
|||
ABISolidityInterface |
|||
}; |
|||
|
|||
/**
|
|||
* Easy to use and self-contained Solidity compiler with as few header dependencies as possible. |
|||
* It holds state and can be used to either step through the compilation stages (and abort e.g. |
|||
* before compilation to bytecode) or run the whole compilation in one call. |
|||
*/ |
|||
class CompilerStack: boost::noncopyable |
|||
{ |
|||
public: |
|||
/// Creates a new compiler stack. Adds standard sources if @a _addStandardSources.
|
|||
explicit CompilerStack(bool _addStandardSources = true); |
|||
|
|||
/// Resets the compiler to a state where the sources are not parsed or even removed.
|
|||
void reset(bool _keepSources = false, bool _addStandardSources = true); |
|||
|
|||
/// Adds a source object (e.g. file) to the parser. After this, parse has to be called again.
|
|||
/// @returns true if a source object by the name already existed and was replaced.
|
|||
void addSources(StringMap const& _nameContents, bool _isLibrary = false) { for (auto const& i: _nameContents) addSource(i.first, i.second, _isLibrary); } |
|||
bool addSource(std::string const& _name, std::string const& _content, bool _isLibrary = false); |
|||
void setSource(std::string const& _sourceCode); |
|||
/// Parses all source units that were added
|
|||
void parse(); |
|||
/// Sets the given source code as the only source unit apart from standard sources and parses it.
|
|||
void parse(std::string const& _sourceCode); |
|||
/// Returns a list of the contract names in the sources.
|
|||
std::vector<std::string> getContractNames() const; |
|||
std::string defaultContractName() const; |
|||
|
|||
/// Compiles the source units that were previously added and parsed.
|
|||
void compile(bool _optimize = false, unsigned _runs = 200); |
|||
/// Parses and compiles the given source code.
|
|||
/// @returns the compiled bytecode
|
|||
bytes const& compile(std::string const& _sourceCode, bool _optimize = false); |
|||
|
|||
/// @returns the assembled bytecode for a contract.
|
|||
bytes const& getBytecode(std::string const& _contractName = "") const; |
|||
/// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
|
|||
bytes const& getRuntimeBytecode(std::string const& _contractName = "") const; |
|||
/// @returns the bytecode of a contract that uses an already deployed contract via CALLCODE.
|
|||
/// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to
|
|||
/// substituted by the actual address. Note that this sequence starts end ends in three X
|
|||
/// characters but can contain anything in between.
|
|||
bytes const& getCloneBytecode(std::string const& _contractName = "") const; |
|||
/// @returns normal contract assembly items
|
|||
eth::AssemblyItems const* getAssemblyItems(std::string const& _contractName = "") const; |
|||
/// @returns runtime contract assembly items
|
|||
eth::AssemblyItems const* getRuntimeAssemblyItems(std::string const& _contractName = "") const; |
|||
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
|
|||
dev::h256 getContractCodeHash(std::string const& _contractName = "") const; |
|||
|
|||
/// Streams a verbose version of the assembly to @a _outStream.
|
|||
/// @arg _sourceCodes is the map of input files to source code strings
|
|||
/// @arg _inJsonFromat shows whether the out should be in Json format
|
|||
/// Prerequisite: Successful compilation.
|
|||
Json::Value streamAssembly(std::ostream& _outStream, std::string const& _contractName = "", StringMap _sourceCodes = StringMap(), bool _inJsonFormat = false) const; |
|||
|
|||
/// Returns a string representing the contract interface in JSON.
|
|||
/// Prerequisite: Successful call to parse or compile.
|
|||
std::string const& getInterface(std::string const& _contractName = "") const; |
|||
/// Returns a string representing the contract interface in Solidity.
|
|||
/// Prerequisite: Successful call to parse or compile.
|
|||
std::string const& getSolidityInterface(std::string const& _contractName = "") const; |
|||
/// Returns a string representing the contract's documentation in JSON.
|
|||
/// Prerequisite: Successful call to parse or compile.
|
|||
/// @param type The type of the documentation to get.
|
|||
/// Can be one of 4 types defined at @c DocumentationType
|
|||
std::string const& getMetadata(std::string const& _contractName, DocumentationType _type) const; |
|||
|
|||
/// @returns the previously used scanner, useful for counting lines during error reporting.
|
|||
Scanner const& getScanner(std::string const& _sourceName = "") const; |
|||
/// @returns the parsed source unit with the supplied name.
|
|||
SourceUnit const& getAST(std::string const& _sourceName = "") const; |
|||
/// @returns the parsed contract with the supplied name. Throws an exception if the contract
|
|||
/// does not exist.
|
|||
ContractDefinition const& getContractDefinition(std::string const& _contractName) const; |
|||
|
|||
/// @returns the offset of the entry point of the given function into the list of assembly items
|
|||
/// or zero if it is not found or does not exist.
|
|||
size_t getFunctionEntryPoint( |
|||
std::string const& _contractName, |
|||
FunctionDefinition const& _function |
|||
) const; |
|||
|
|||
/// Compile the given @a _sourceCode to bytecode. If a scanner is provided, it is used for
|
|||
/// scanning the source code - this is useful for printing exception information.
|
|||
static bytes staticCompile(std::string const& _sourceCode, bool _optimize = false); |
|||
|
|||
/// Helper function for logs printing. Do only use in error cases, it's quite expensive.
|
|||
/// line and columns are numbered starting from 1 with following order:
|
|||
/// start line, start column, end line, end column
|
|||
std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const; |
|||
|
|||
private: |
|||
/**
|
|||
* Information pertaining to one source unit, filled gradually during parsing and compilation. |
|||
*/ |
|||
struct Source |
|||
{ |
|||
std::shared_ptr<Scanner> scanner; |
|||
std::shared_ptr<SourceUnit> ast; |
|||
std::string interface; |
|||
bool isLibrary = false; |
|||
void reset() { scanner.reset(); ast.reset(); interface.clear(); } |
|||
}; |
|||
|
|||
struct Contract |
|||
{ |
|||
ContractDefinition const* contract = nullptr; |
|||
std::shared_ptr<Compiler> compiler; |
|||
bytes bytecode; |
|||
bytes runtimeBytecode; |
|||
bytes cloneBytecode; |
|||
std::shared_ptr<InterfaceHandler> interfaceHandler; |
|||
mutable std::unique_ptr<std::string const> interface; |
|||
mutable std::unique_ptr<std::string const> solidityInterface; |
|||
mutable std::unique_ptr<std::string const> userDocumentation; |
|||
mutable std::unique_ptr<std::string const> devDocumentation; |
|||
|
|||
Contract(); |
|||
}; |
|||
|
|||
void resolveImports(); |
|||
|
|||
Contract const& getContract(std::string const& _contractName = "") const; |
|||
Source const& getSource(std::string const& _sourceName = "") const; |
|||
|
|||
bool m_parseSuccessful; |
|||
std::map<std::string const, Source> m_sources; |
|||
std::shared_ptr<GlobalContext> m_globalContext; |
|||
std::vector<Source const*> m_sourceOrder; |
|||
std::map<std::string const, Contract> m_contracts; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,734 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Routines used by both the compiler and the expression compiler. |
|||
*/ |
|||
|
|||
#include <libsolidity/CompilerUtils.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libevmcore/Params.h> |
|||
#include <libsolidity/ArrayUtils.h> |
|||
#include <libsolidity/LValue.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
const unsigned CompilerUtils::dataStartOffset = 4; |
|||
const size_t CompilerUtils::freeMemoryPointer = 64; |
|||
const unsigned CompilerUtils::identityContractAddress = 4; |
|||
|
|||
void CompilerUtils::initialiseFreeMemoryPointer() |
|||
{ |
|||
m_context << u256(freeMemoryPointer + 32); |
|||
storeFreeMemoryPointer(); |
|||
} |
|||
|
|||
void CompilerUtils::fetchFreeMemoryPointer() |
|||
{ |
|||
m_context << u256(freeMemoryPointer) << eth::Instruction::MLOAD; |
|||
} |
|||
|
|||
void CompilerUtils::storeFreeMemoryPointer() |
|||
{ |
|||
m_context << u256(freeMemoryPointer) << eth::Instruction::MSTORE; |
|||
} |
|||
|
|||
void CompilerUtils::allocateMemory() |
|||
{ |
|||
fetchFreeMemoryPointer(); |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; |
|||
storeFreeMemoryPointer(); |
|||
} |
|||
|
|||
void CompilerUtils::toSizeAfterFreeMemoryPointer() |
|||
{ |
|||
fetchFreeMemoryPointer(); |
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::SUB; |
|||
m_context << eth::Instruction::SWAP1; |
|||
} |
|||
|
|||
unsigned CompilerUtils::loadFromMemory( |
|||
unsigned _offset, |
|||
Type const& _type, |
|||
bool _fromCalldata, |
|||
bool _padToWordBoundaries |
|||
) |
|||
{ |
|||
solAssert(_type.getCategory() != Type::Category::Array, "Unable to statically load dynamic type."); |
|||
m_context << u256(_offset); |
|||
return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); |
|||
} |
|||
|
|||
void CompilerUtils::loadFromMemoryDynamic( |
|||
Type const& _type, |
|||
bool _fromCalldata, |
|||
bool _padToWordBoundaries, |
|||
bool _keepUpdatedMemoryOffset |
|||
) |
|||
{ |
|||
solAssert(_type.getCategory() != Type::Category::Array, "Arrays not yet implemented."); |
|||
if (_keepUpdatedMemoryOffset) |
|||
m_context << eth::Instruction::DUP1; |
|||
unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); |
|||
if (_keepUpdatedMemoryOffset) |
|||
{ |
|||
// update memory counter
|
|||
moveToStackTop(_type.getSizeOnStack()); |
|||
m_context << u256(numBytes) << eth::Instruction::ADD; |
|||
} |
|||
} |
|||
|
|||
void CompilerUtils::storeInMemory(unsigned _offset) |
|||
{ |
|||
unsigned numBytes = prepareMemoryStore(IntegerType(256), true); |
|||
if (numBytes > 0) |
|||
m_context << u256(_offset) << eth::Instruction::MSTORE; |
|||
} |
|||
|
|||
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) |
|||
{ |
|||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type)) |
|||
{ |
|||
solAssert(ref->location() == DataLocation::Memory, ""); |
|||
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); |
|||
} |
|||
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type)) |
|||
{ |
|||
m_context << eth::Instruction::DUP1; |
|||
storeStringData(bytesConstRef(str->value())); |
|||
if (_padToWordBoundaries) |
|||
m_context << u256(((str->value().size() + 31) / 32) * 32); |
|||
else |
|||
m_context << u256(str->value().size()); |
|||
m_context << eth::Instruction::ADD; |
|||
} |
|||
else |
|||
{ |
|||
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); |
|||
if (numBytes > 0) |
|||
{ |
|||
solAssert( |
|||
_type.getSizeOnStack() == 1, |
|||
"Memory store of types with stack size != 1 not implemented." |
|||
); |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; |
|||
m_context << u256(numBytes) << eth::Instruction::ADD; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CompilerUtils::encodeToMemory( |
|||
TypePointers const& _givenTypes, |
|||
TypePointers const& _targetTypes, |
|||
bool _padToWordBoundaries, |
|||
bool _copyDynamicDataInPlace |
|||
) |
|||
{ |
|||
// stack: <v1> <v2> ... <vn> <mem>
|
|||
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; |
|||
solAssert(targetTypes.size() == _givenTypes.size(), ""); |
|||
for (TypePointer& t: targetTypes) |
|||
t = t->mobileType()->externalType(); |
|||
|
|||
// Stack during operation:
|
|||
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
|
|||
// The values dyn_head_i are added during the first loop and they point to the head part
|
|||
// of the ith dynamic parameter, which is filled once the dynamic parts are processed.
|
|||
|
|||
// store memory start pointer
|
|||
m_context << eth::Instruction::DUP1; |
|||
|
|||
unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); |
|||
unsigned stackPos = 0; // advances through the argument values
|
|||
unsigned dynPointers = 0; // number of dynamic head pointers on the stack
|
|||
for (size_t i = 0; i < _givenTypes.size(); ++i) |
|||
{ |
|||
TypePointer targetType = targetTypes[i]; |
|||
solAssert(!!targetType, "Externalable type expected."); |
|||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) |
|||
{ |
|||
// leave end_of_mem as dyn head pointer
|
|||
m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; |
|||
dynPointers++; |
|||
} |
|||
else |
|||
{ |
|||
copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->getSizeOnStack()); |
|||
solAssert(!!targetType, "Externalable type expected."); |
|||
TypePointer type = targetType; |
|||
if ( |
|||
_givenTypes[i]->dataStoredIn(DataLocation::Storage) || |
|||
_givenTypes[i]->dataStoredIn(DataLocation::CallData) || |
|||
_givenTypes[i]->getCategory() == Type::Category::StringLiteral |
|||
) |
|||
type = _givenTypes[i]; // delay conversion
|
|||
else |
|||
convertType(*_givenTypes[i], *targetType, true); |
|||
if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) |
|||
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); |
|||
else |
|||
storeInMemoryDynamic(*type, _padToWordBoundaries); |
|||
} |
|||
stackPos += _givenTypes[i]->getSizeOnStack(); |
|||
} |
|||
|
|||
// now copy the dynamic part
|
|||
// Stack: <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
|
|||
stackPos = 0; |
|||
unsigned thisDynPointer = 0; |
|||
for (size_t i = 0; i < _givenTypes.size(); ++i) |
|||
{ |
|||
TypePointer targetType = targetTypes[i]; |
|||
solAssert(!!targetType, "Externalable type expected."); |
|||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) |
|||
{ |
|||
// copy tail pointer (=mem_end - mem_start) to memory
|
|||
m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; |
|||
m_context << eth::Instruction::SUB; |
|||
m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); |
|||
m_context << eth::Instruction::MSTORE; |
|||
// stack: ... <end_of_mem>
|
|||
if (_givenTypes[i]->getCategory() == Type::Category::StringLiteral) |
|||
{ |
|||
auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]); |
|||
m_context << u256(strType.value().size()); |
|||
storeInMemoryDynamic(IntegerType(256), true); |
|||
// stack: ... <end_of_mem'>
|
|||
storeInMemoryDynamic(strType, _padToWordBoundaries); |
|||
} |
|||
else |
|||
{ |
|||
solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); |
|||
auto const& arrayType = dynamic_cast<ArrayType const&>(*_givenTypes[i]); |
|||
// now copy the array
|
|||
copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack()); |
|||
// stack: ... <end_of_mem> <value...>
|
|||
// copy length to memory
|
|||
m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); |
|||
if (arrayType.location() == DataLocation::CallData) |
|||
m_context << eth::Instruction::DUP2; // length is on stack
|
|||
else if (arrayType.location() == DataLocation::Storage) |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
else |
|||
{ |
|||
solAssert(arrayType.location() == DataLocation::Memory, ""); |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; |
|||
} |
|||
// stack: ... <end_of_mem> <value...> <end_of_mem'> <length>
|
|||
storeInMemoryDynamic(IntegerType(256), true); |
|||
// stack: ... <end_of_mem> <value...> <end_of_mem''>
|
|||
// copy the new memory pointer
|
|||
m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; |
|||
// stack: ... <end_of_mem''> <value...>
|
|||
// copy data part
|
|||
ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries); |
|||
// stack: ... <end_of_mem'''>
|
|||
} |
|||
|
|||
thisDynPointer++; |
|||
} |
|||
stackPos += _givenTypes[i]->getSizeOnStack(); |
|||
} |
|||
|
|||
// remove unneeded stack elements (and retain memory pointer)
|
|||
m_context << eth::swapInstruction(argSize + dynPointers + 1); |
|||
popStackSlots(argSize + dynPointers + 1); |
|||
} |
|||
|
|||
void CompilerUtils::memoryCopy() |
|||
{ |
|||
// Stack here: size target source
|
|||
// stack for call: outsize target size source value contract gas
|
|||
//@TODO do not use ::CALL if less than 32 bytes?
|
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1; |
|||
m_context << u256(0) << u256(identityContractAddress); |
|||
// compute gas costs
|
|||
m_context << u256(32) << eth::Instruction::DUP5 << u256(31) << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::DIV << u256(eth::c_identityWordGas) << eth::Instruction::MUL; |
|||
m_context << u256(eth::c_identityGas) << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::CALL; |
|||
m_context << eth::Instruction::POP; // ignore return value
|
|||
} |
|||
|
|||
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) |
|||
{ |
|||
// For a type extension, we need to remove all higher-order bits that we might have ignored in
|
|||
// previous operations.
|
|||
// @todo: store in the AST whether the operand might have "dirty" higher order bits
|
|||
|
|||
if (_typeOnStack == _targetType && !_cleanupNeeded) |
|||
return; |
|||
Type::Category stackTypeCategory = _typeOnStack.getCategory(); |
|||
Type::Category targetTypeCategory = _targetType.getCategory(); |
|||
|
|||
switch (stackTypeCategory) |
|||
{ |
|||
case Type::Category::FixedBytes: |
|||
{ |
|||
FixedBytesType const& typeOnStack = dynamic_cast<FixedBytesType const&>(_typeOnStack); |
|||
if (targetTypeCategory == Type::Category::Integer) |
|||
{ |
|||
// conversion from bytes to integer. no need to clean the high bit
|
|||
// only to shift right because of opposite alignment
|
|||
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); |
|||
m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; |
|||
if (targetIntegerType.getNumBits() < typeOnStack.numBytes() * 8) |
|||
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); |
|||
} |
|||
else |
|||
{ |
|||
// clear lower-order bytes for conversion to shorter bytes - we always clean
|
|||
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); |
|||
FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType); |
|||
if (targetType.numBytes() < typeOnStack.numBytes()) |
|||
{ |
|||
if (targetType.numBytes() == 0) |
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; |
|||
else |
|||
{ |
|||
m_context << (u256(1) << (256 - targetType.numBytes() * 8)); |
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2; |
|||
m_context << eth::Instruction::DIV << eth::Instruction::MUL; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
break; |
|||
case Type::Category::Enum: |
|||
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); |
|||
break; |
|||
case Type::Category::Integer: |
|||
case Type::Category::Contract: |
|||
case Type::Category::IntegerConstant: |
|||
if (targetTypeCategory == Type::Category::FixedBytes) |
|||
{ |
|||
solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, |
|||
"Invalid conversion to FixedBytesType requested."); |
|||
// conversion from bytes to string. no need to clean the high bit
|
|||
// only to shift left because of opposite alignment
|
|||
FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType); |
|||
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) |
|||
if (targetBytesType.numBytes() * 8 > typeOnStack->getNumBits()) |
|||
cleanHigherOrderBits(*typeOnStack); |
|||
m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << eth::Instruction::MUL; |
|||
} |
|||
else if (targetTypeCategory == Type::Category::Enum) |
|||
// just clean
|
|||
convertType(_typeOnStack, *_typeOnStack.mobileType(), true); |
|||
else |
|||
{ |
|||
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); |
|||
IntegerType addressType(0, IntegerType::Modifier::Address); |
|||
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer |
|||
? dynamic_cast<IntegerType const&>(_targetType) : addressType; |
|||
if (stackTypeCategory == Type::Category::IntegerConstant) |
|||
{ |
|||
IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); |
|||
// We know that the stack is clean, we only have to clean for a narrowing conversion
|
|||
// where cleanup is forced.
|
|||
if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) |
|||
cleanHigherOrderBits(targetType); |
|||
} |
|||
else |
|||
{ |
|||
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer |
|||
? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; |
|||
// Widening: clean up according to source type width
|
|||
// Non-widening and force: clean up according to target type bits
|
|||
if (targetType.getNumBits() > typeOnStack.getNumBits()) |
|||
cleanHigherOrderBits(typeOnStack); |
|||
else if (_cleanupNeeded) |
|||
cleanHigherOrderBits(targetType); |
|||
} |
|||
} |
|||
break; |
|||
case Type::Category::StringLiteral: |
|||
{ |
|||
auto const& literalType = dynamic_cast<StringLiteralType const&>(_typeOnStack); |
|||
string const& value = literalType.value(); |
|||
bytesConstRef data(value); |
|||
if (targetTypeCategory == Type::Category::FixedBytes) |
|||
{ |
|||
solAssert(data.size() <= 32, ""); |
|||
m_context << h256::Arith(h256(data, h256::AlignLeft)); |
|||
} |
|||
else if (targetTypeCategory == Type::Category::Array) |
|||
{ |
|||
auto const& arrayType = dynamic_cast<ArrayType const&>(_targetType); |
|||
solAssert(arrayType.isByteArray(), ""); |
|||
u256 storageSize(32 + ((data.size() + 31) / 32) * 32); |
|||
m_context << storageSize; |
|||
allocateMemory(); |
|||
// stack: mempos
|
|||
m_context << eth::Instruction::DUP1 << u256(data.size()); |
|||
storeInMemoryDynamic(IntegerType(256)); |
|||
// stack: mempos datapos
|
|||
storeStringData(data); |
|||
break; |
|||
} |
|||
else |
|||
solAssert( |
|||
false, |
|||
"Invalid conversion from string literal to " + _targetType.toString(false) + " requested." |
|||
); |
|||
break; |
|||
} |
|||
case Type::Category::Array: |
|||
{ |
|||
solAssert(targetTypeCategory == stackTypeCategory, ""); |
|||
ArrayType const& typeOnStack = dynamic_cast<ArrayType const&>(_typeOnStack); |
|||
ArrayType const& targetType = dynamic_cast<ArrayType const&>(_targetType); |
|||
switch (targetType.location()) |
|||
{ |
|||
case DataLocation::Storage: |
|||
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
|
|||
solAssert( |
|||
(targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) && |
|||
typeOnStack.location() == DataLocation::Storage, |
|||
"Invalid conversion to storage type." |
|||
); |
|||
break; |
|||
case DataLocation::Memory: |
|||
{ |
|||
// Copy the array to a free position in memory, unless it is already in memory.
|
|||
if (typeOnStack.location() != DataLocation::Memory) |
|||
{ |
|||
// stack: <source ref> (variably sized)
|
|||
unsigned stackSize = typeOnStack.getSizeOnStack(); |
|||
ArrayUtils(m_context).retrieveLength(typeOnStack); |
|||
|
|||
// allocate memory
|
|||
// stack: <source ref> (variably sized) <length>
|
|||
m_context << eth::Instruction::DUP1; |
|||
ArrayUtils(m_context).convertLengthToSize(targetType, true); |
|||
// stack: <source ref> (variably sized) <length> <size>
|
|||
if (targetType.isDynamicallySized()) |
|||
m_context << u256(0x20) << eth::Instruction::ADD; |
|||
allocateMemory(); |
|||
// stack: <source ref> (variably sized) <length> <mem start>
|
|||
m_context << eth::Instruction::DUP1; |
|||
moveIntoStack(2 + stackSize); |
|||
if (targetType.isDynamicallySized()) |
|||
{ |
|||
m_context << eth::Instruction::DUP2; |
|||
storeInMemoryDynamic(IntegerType(256)); |
|||
} |
|||
// stack: <mem start> <source ref> (variably sized) <length> <mem data pos>
|
|||
if (targetType.getBaseType()->isValueType()) |
|||
{ |
|||
solAssert(typeOnStack.getBaseType()->isValueType(), ""); |
|||
copyToStackTop(2 + stackSize, stackSize); |
|||
ArrayUtils(m_context).copyArrayToMemory(typeOnStack); |
|||
} |
|||
else |
|||
{ |
|||
m_context << u256(0) << eth::Instruction::SWAP1; |
|||
// stack: <mem start> <source ref> (variably sized) <length> <counter> <mem data pos>
|
|||
auto repeat = m_context.newTag(); |
|||
m_context << repeat; |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; |
|||
m_context << eth::Instruction::LT << eth::Instruction::ISZERO; |
|||
auto loopEnd = m_context.appendConditionalJump(); |
|||
copyToStackTop(3 + stackSize, stackSize); |
|||
copyToStackTop(2 + stackSize, 1); |
|||
ArrayUtils(m_context).accessIndex(typeOnStack, false); |
|||
if (typeOnStack.location() == DataLocation::Storage) |
|||
StorageItem(m_context, *typeOnStack.getBaseType()).retrieveValue(SourceLocation(), true); |
|||
convertType(*typeOnStack.getBaseType(), *targetType.getBaseType(), _cleanupNeeded); |
|||
storeInMemoryDynamic(*targetType.getBaseType(), true); |
|||
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; |
|||
m_context << eth::Instruction::SWAP1; |
|||
m_context.appendJumpTo(repeat); |
|||
m_context << loopEnd; |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
// stack: <mem start> <source ref> (variably sized) <length> <mem data pos updated>
|
|||
popStackSlots(2 + stackSize); |
|||
// Stack: <mem start>
|
|||
} |
|||
break; |
|||
} |
|||
case DataLocation::CallData: |
|||
solAssert( |
|||
targetType.isByteArray() && |
|||
typeOnStack.isByteArray() && |
|||
typeOnStack.location() == DataLocation::CallData, |
|||
"Invalid conversion to calldata type."); |
|||
break; |
|||
default: |
|||
solAssert( |
|||
false, |
|||
"Invalid type conversion " + |
|||
_typeOnStack.toString(false) + |
|||
" to " + |
|||
_targetType.toString(false) + |
|||
" requested." |
|||
); |
|||
} |
|||
break; |
|||
} |
|||
case Type::Category::Struct: |
|||
{ |
|||
solAssert(targetTypeCategory == stackTypeCategory, ""); |
|||
auto& targetType = dynamic_cast<StructType const&>(_targetType); |
|||
auto& typeOnStack = dynamic_cast<StructType const&>(_typeOnStack); |
|||
solAssert( |
|||
targetType.location() != DataLocation::CallData && |
|||
typeOnStack.location() != DataLocation::CallData |
|||
, ""); |
|||
switch (targetType.location()) |
|||
{ |
|||
case DataLocation::Storage: |
|||
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
|
|||
solAssert( |
|||
targetType.isPointer() && |
|||
typeOnStack.location() == DataLocation::Storage, |
|||
"Invalid conversion to storage type." |
|||
); |
|||
break; |
|||
case DataLocation::Memory: |
|||
// Copy the array to a free position in memory, unless it is already in memory.
|
|||
if (typeOnStack.location() != DataLocation::Memory) |
|||
{ |
|||
solAssert(typeOnStack.location() == DataLocation::Storage, ""); |
|||
// stack: <source ref>
|
|||
m_context << typeOnStack.memorySize(); |
|||
allocateMemory(); |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; |
|||
// stack: <memory ptr> <source ref> <memory ptr>
|
|||
for (auto const& member: typeOnStack.getMembers()) |
|||
{ |
|||
if (!member.type->canLiveOutsideStorage()) |
|||
continue; |
|||
pair<u256, unsigned> const& offsets = typeOnStack.getStorageOffsetsOfMember(member.name); |
|||
m_context << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
m_context << u256(offsets.second); |
|||
StorageItem(m_context, *member.type).retrieveValue(SourceLocation(), true); |
|||
TypePointer targetMemberType = targetType.getMemberType(member.name); |
|||
solAssert(!!targetMemberType, "Member not found in target type."); |
|||
convertType(*member.type, *targetMemberType, true); |
|||
storeInMemoryDynamic(*targetMemberType, true); |
|||
} |
|||
m_context << eth::Instruction::POP << eth::Instruction::POP; |
|||
} |
|||
break; |
|||
case DataLocation::CallData: |
|||
solAssert(false, "Invalid type conversion target location CallData."); |
|||
break; |
|||
} |
|||
break; |
|||
} |
|||
default: |
|||
// All other types should not be convertible to non-equal types.
|
|||
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void CompilerUtils::pushZeroValue(const Type& _type) |
|||
{ |
|||
auto const* referenceType = dynamic_cast<ReferenceType const*>(&_type); |
|||
if (!referenceType || referenceType->location() == DataLocation::Storage) |
|||
{ |
|||
for (size_t i = 0; i < _type.getSizeOnStack(); ++i) |
|||
m_context << u256(0); |
|||
return; |
|||
} |
|||
solAssert(referenceType->location() == DataLocation::Memory, ""); |
|||
|
|||
m_context << u256(max(32u, _type.getCalldataEncodedSize())); |
|||
allocateMemory(); |
|||
m_context << eth::Instruction::DUP1; |
|||
|
|||
if (auto structType = dynamic_cast<StructType const*>(&_type)) |
|||
for (auto const& member: structType->getMembers()) |
|||
{ |
|||
pushZeroValue(*member.type); |
|||
storeInMemoryDynamic(*member.type); |
|||
} |
|||
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) |
|||
{ |
|||
if (arrayType->isDynamicallySized()) |
|||
{ |
|||
// zero length
|
|||
m_context << u256(0); |
|||
storeInMemoryDynamic(IntegerType(256)); |
|||
} |
|||
else if (arrayType->getLength() > 0) |
|||
{ |
|||
m_context << arrayType->getLength() << eth::Instruction::SWAP1; |
|||
// stack: items_to_do memory_pos
|
|||
auto repeat = m_context.newTag(); |
|||
m_context << repeat; |
|||
pushZeroValue(*arrayType->getBaseType()); |
|||
storeInMemoryDynamic(*arrayType->getBaseType()); |
|||
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1; |
|||
m_context << eth::Instruction::SUB << eth::Instruction::SWAP1; |
|||
m_context << eth::Instruction::DUP2; |
|||
m_context.appendConditionalJumpTo(repeat); |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
} |
|||
} |
|||
else |
|||
solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); |
|||
|
|||
// remove the updated memory pointer
|
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) |
|||
{ |
|||
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); |
|||
unsigned const size = _variable.getType()->getSizeOnStack(); |
|||
solAssert(stackPosition >= size, "Variable size and position mismatch."); |
|||
// move variable starting from its top end in the stack
|
|||
if (stackPosition - size + 1 > 16) |
|||
BOOST_THROW_EXCEPTION( |
|||
CompilerError() << |
|||
errinfo_sourceLocation(_variable.getLocation()) << |
|||
errinfo_comment("Stack too deep, try removing local variables.") |
|||
); |
|||
for (unsigned i = 0; i < size; ++i) |
|||
m_context << eth::swapInstruction(stackPosition - size + 1) << eth::Instruction::POP; |
|||
} |
|||
|
|||
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) |
|||
{ |
|||
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); |
|||
for (unsigned i = 0; i < _itemSize; ++i) |
|||
m_context << eth::dupInstruction(_stackDepth); |
|||
} |
|||
|
|||
void CompilerUtils::moveToStackTop(unsigned _stackDepth) |
|||
{ |
|||
solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); |
|||
for (unsigned i = 0; i < _stackDepth; ++i) |
|||
m_context << eth::swapInstruction(1 + i); |
|||
} |
|||
|
|||
void CompilerUtils::moveIntoStack(unsigned _stackDepth) |
|||
{ |
|||
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); |
|||
for (unsigned i = _stackDepth; i > 0; --i) |
|||
m_context << eth::swapInstruction(i); |
|||
} |
|||
|
|||
void CompilerUtils::popStackElement(Type const& _type) |
|||
{ |
|||
popStackSlots(_type.getSizeOnStack()); |
|||
} |
|||
|
|||
void CompilerUtils::popStackSlots(size_t _amount) |
|||
{ |
|||
for (size_t i = 0; i < _amount; ++i) |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
unsigned CompilerUtils::getSizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes) |
|||
{ |
|||
unsigned size = 0; |
|||
for (shared_ptr<Type const> const& type: _variableTypes) |
|||
size += type->getSizeOnStack(); |
|||
return size; |
|||
} |
|||
|
|||
void CompilerUtils::computeHashStatic() |
|||
{ |
|||
storeInMemory(0); |
|||
m_context << u256(32) << u256(0) << eth::Instruction::SHA3; |
|||
} |
|||
|
|||
void CompilerUtils::storeStringData(bytesConstRef _data) |
|||
{ |
|||
//@todo provide both alternatives to the optimiser
|
|||
// stack: mempos
|
|||
if (_data.size() <= 128) |
|||
{ |
|||
for (unsigned i = 0; i < _data.size(); i += 32) |
|||
{ |
|||
m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft)); |
|||
storeInMemoryDynamic(IntegerType(256)); |
|||
} |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
else |
|||
{ |
|||
// stack: mempos mempos_data
|
|||
m_context.appendData(_data.toBytes()); |
|||
m_context << u256(_data.size()) << eth::Instruction::SWAP2; |
|||
m_context << eth::Instruction::CODECOPY; |
|||
} |
|||
} |
|||
|
|||
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) |
|||
{ |
|||
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); |
|||
bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; |
|||
if (numBytes == 0) |
|||
m_context << eth::Instruction::POP << u256(0); |
|||
else |
|||
{ |
|||
solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); |
|||
m_context << (_fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD); |
|||
if (numBytes != 32) |
|||
{ |
|||
// add leading or trailing zeros by dividing/multiplying depending on alignment
|
|||
u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); |
|||
m_context << shiftFactor << eth::Instruction::SWAP1 << eth::Instruction::DIV; |
|||
if (leftAligned) |
|||
m_context << shiftFactor << eth::Instruction::MUL; |
|||
} |
|||
} |
|||
|
|||
return numBytes; |
|||
} |
|||
|
|||
void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) |
|||
{ |
|||
if (_typeOnStack.getNumBits() == 256) |
|||
return; |
|||
else if (_typeOnStack.isSigned()) |
|||
m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; |
|||
else |
|||
m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; |
|||
} |
|||
|
|||
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const |
|||
{ |
|||
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); |
|||
bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; |
|||
if (numBytes == 0) |
|||
m_context << eth::Instruction::POP; |
|||
else |
|||
{ |
|||
solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested."); |
|||
if (numBytes != 32 && !leftAligned && !_padToWordBoundaries) |
|||
// shift the value accordingly before storing
|
|||
m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; |
|||
} |
|||
return numBytes; |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,178 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Routines used by both the compiler and the expression compiler. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libsolidity/CompilerContext.h> |
|||
#include <libsolidity/ASTForward.h> |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
class Type; // forward
|
|||
|
|||
class CompilerUtils |
|||
{ |
|||
public: |
|||
CompilerUtils(CompilerContext& _context): m_context(_context) {} |
|||
|
|||
/// Stores the initial value of the free-memory-pointer at its position;
|
|||
void initialiseFreeMemoryPointer(); |
|||
/// Copies the free memory pointer to the stack.
|
|||
void fetchFreeMemoryPointer(); |
|||
/// Stores the free memory pointer from the stack.
|
|||
void storeFreeMemoryPointer(); |
|||
/// Allocates a number of bytes in memory as given on the stack.
|
|||
/// Stack pre: <size>
|
|||
/// Stack post: <mem_start>
|
|||
void allocateMemory(); |
|||
/// Appends code that transforms memptr to (memptr - free_memptr) memptr
|
|||
void toSizeAfterFreeMemoryPointer(); |
|||
|
|||
/// Loads data from memory to the stack.
|
|||
/// @param _offset offset in memory (or calldata)
|
|||
/// @param _type data type to load
|
|||
/// @param _fromCalldata if true, load from calldata, not from memory
|
|||
/// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries
|
|||
/// @returns the number of bytes consumed in memory.
|
|||
unsigned loadFromMemory( |
|||
unsigned _offset, |
|||
Type const& _type = IntegerType(256), |
|||
bool _fromCalldata = false, |
|||
bool _padToWordBoundaries = false |
|||
); |
|||
/// Dynamic version of @see loadFromMemory, expects the memory offset on the stack.
|
|||
/// Stack pre: memory_offset
|
|||
/// Stack post: value... (memory_offset+length)
|
|||
void loadFromMemoryDynamic( |
|||
Type const& _type, |
|||
bool _fromCalldata = false, |
|||
bool _padToWordBoundaries = true, |
|||
bool _keepUpdatedMemoryOffset = true |
|||
); |
|||
/// Stores a 256 bit integer from stack in memory.
|
|||
/// @param _offset offset in memory
|
|||
/// @param _type type of the data on the stack
|
|||
void storeInMemory(unsigned _offset); |
|||
/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
|
|||
/// and also updates that. For reference types, only copies the data pointer. Fails for
|
|||
/// non-memory-references.
|
|||
/// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements
|
|||
/// are always padded (except for byte arrays), regardless of this parameter.
|
|||
/// Stack pre: memory_offset value...
|
|||
/// Stack post: (memory_offset+length)
|
|||
void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); |
|||
|
|||
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
|
|||
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
|
|||
/// Removes the values from the stack and leaves the updated memory pointer.
|
|||
/// Stack pre: <v1> <v2> ... <vn> <memptr>
|
|||
/// Stack post: <memptr_updated>
|
|||
/// Does not touch the memory-free pointer.
|
|||
/// @param _padToWordBoundaries if false, all values are concatenated without padding.
|
|||
/// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length)
|
|||
/// together with fixed-length data.
|
|||
/// @note the locations of target reference types are ignored, because it will always be
|
|||
/// memory.
|
|||
void encodeToMemory( |
|||
TypePointers const& _givenTypes = {}, |
|||
TypePointers const& _targetTypes = {}, |
|||
bool _padToWordBoundaries = true, |
|||
bool _copyDynamicDataInPlace = false |
|||
); |
|||
|
|||
/// Uses a CALL to the identity contract to perform a memory-to-memory copy.
|
|||
/// Stack pre: <size> <target> <source>
|
|||
/// Stack post:
|
|||
void memoryCopy(); |
|||
|
|||
/// Appends code for an implicit or explicit type conversion. This includes erasing higher
|
|||
/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
|
|||
/// if a reference type is converted from calldata or storage to memory.
|
|||
/// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be
|
|||
/// necessary.
|
|||
void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); |
|||
|
|||
/// Creates a zero-value for the given type and puts it onto the stack. This might allocate
|
|||
/// memory for memory references.
|
|||
void pushZeroValue(Type const& _type); |
|||
|
|||
/// Moves the value that is at the top of the stack to a stack variable.
|
|||
void moveToStackVariable(VariableDeclaration const& _variable); |
|||
/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
|
|||
/// to the top of the stack.
|
|||
void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); |
|||
/// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack.
|
|||
void moveToStackTop(unsigned _stackDepth); |
|||
/// Moves a single stack element past @a _stackDepth other stack elements
|
|||
void moveIntoStack(unsigned _stackDepth); |
|||
/// Removes the current value from the top of the stack.
|
|||
void popStackElement(Type const& _type); |
|||
/// Removes element from the top of the stack _amount times.
|
|||
void popStackSlots(size_t _amount); |
|||
|
|||
template <class T> |
|||
static unsigned getSizeOnStack(std::vector<T> const& _variables); |
|||
static unsigned getSizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes); |
|||
|
|||
/// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type.
|
|||
void computeHashStatic(); |
|||
|
|||
/// Bytes we need to the start of call data.
|
|||
/// - The size in bytes of the function (hash) identifier.
|
|||
static const unsigned dataStartOffset; |
|||
|
|||
/// Position of the free-memory-pointer in memory;
|
|||
static const size_t freeMemoryPointer; |
|||
|
|||
private: |
|||
/// Address of the precompiled identity contract.
|
|||
static const unsigned identityContractAddress; |
|||
|
|||
/// Stores the given string in memory.
|
|||
/// Stack pre: mempos
|
|||
/// Stack post:
|
|||
void storeStringData(bytesConstRef _data); |
|||
|
|||
/// Appends code that cleans higher-order bits for integer types.
|
|||
void cleanHigherOrderBits(IntegerType const& _typeOnStack); |
|||
|
|||
/// Prepares the given type for storing in memory by shifting it if necessary.
|
|||
unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; |
|||
/// Loads type from memory assuming memory offset is on stack top.
|
|||
unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); |
|||
|
|||
CompilerContext& m_context; |
|||
}; |
|||
|
|||
|
|||
template <class T> |
|||
unsigned CompilerUtils::getSizeOnStack(std::vector<T> const& _variables) |
|||
{ |
|||
unsigned size = 0; |
|||
for (T const& variable: _variables) |
|||
size += variable->getType()->getSizeOnStack(); |
|||
return size; |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,85 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Scope - object that holds declaration of names. |
|||
*/ |
|||
|
|||
#include <libsolidity/DeclarationContainer.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Types.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::solidity; |
|||
|
|||
Declaration const* DeclarationContainer::conflictingDeclaration(Declaration const& _declaration) const |
|||
{ |
|||
ASTString const& name(_declaration.getName()); |
|||
solAssert(!name.empty(), ""); |
|||
vector<Declaration const*> declarations; |
|||
if (m_declarations.count(name)) |
|||
declarations += m_declarations.at(name); |
|||
if (m_invisibleDeclarations.count(name)) |
|||
declarations += m_invisibleDeclarations.at(name); |
|||
|
|||
if (dynamic_cast<FunctionDefinition const*>(&_declaration)) |
|||
{ |
|||
// check that all other declarations with the same name are functions
|
|||
for (Declaration const* declaration: declarations) |
|||
if (!dynamic_cast<FunctionDefinition const*>(declaration)) |
|||
return declaration; |
|||
} |
|||
else if (!declarations.empty()) |
|||
return declarations.front(); |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update) |
|||
{ |
|||
ASTString const& name(_declaration.getName()); |
|||
if (name.empty()) |
|||
return true; |
|||
|
|||
if (_update) |
|||
{ |
|||
solAssert(!dynamic_cast<FunctionDefinition const*>(&_declaration), "Attempt to update function definition."); |
|||
m_declarations.erase(name); |
|||
m_invisibleDeclarations.erase(name); |
|||
} |
|||
else if (conflictingDeclaration(_declaration)) |
|||
return false; |
|||
|
|||
if (_invisible) |
|||
m_invisibleDeclarations[name].push_back(&_declaration); |
|||
else |
|||
m_declarations[name].push_back(&_declaration); |
|||
return true; |
|||
} |
|||
|
|||
std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const |
|||
{ |
|||
solAssert(!_name.empty(), "Attempt to resolve empty name."); |
|||
auto result = m_declarations.find(_name); |
|||
if (result != m_declarations.end()) |
|||
return result->second; |
|||
if (_recursive && m_enclosingContainer) |
|||
return m_enclosingContainer->resolveName(_name, true); |
|||
return vector<Declaration const*>({}); |
|||
} |
@ -1,65 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Scope - object that holds declaration of names. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <set> |
|||
#include <boost/noncopyable.hpp> |
|||
|
|||
#include <libsolidity/ASTForward.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/**
|
|||
* Container that stores mappings between names and declarations. It also contains a link to the |
|||
* enclosing scope. |
|||
*/ |
|||
class DeclarationContainer |
|||
{ |
|||
public: |
|||
explicit DeclarationContainer(Declaration const* _enclosingDeclaration = nullptr, |
|||
DeclarationContainer const* _enclosingContainer = nullptr): |
|||
m_enclosingDeclaration(_enclosingDeclaration), m_enclosingContainer(_enclosingContainer) {} |
|||
/// Registers the declaration in the scope unless its name is already declared or the name is empty.
|
|||
/// @param _invisible if true, registers the declaration, reports name clashes but does not return it in @a resolveName
|
|||
/// @param _update if true, replaces a potential declaration that is already present
|
|||
/// @returns false if the name was already declared.
|
|||
bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false); |
|||
std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const; |
|||
Declaration const* getEnclosingDeclaration() const { return m_enclosingDeclaration; } |
|||
std::map<ASTString, std::vector<Declaration const*>> const& getDeclarations() const { return m_declarations; } |
|||
/// @returns whether declaration is valid, and if not also returns previous declaration.
|
|||
Declaration const* conflictingDeclaration(Declaration const& _declaration) const; |
|||
|
|||
private: |
|||
Declaration const* m_enclosingDeclaration; |
|||
DeclarationContainer const* m_enclosingContainer; |
|||
std::map<ASTString, std::vector<Declaration const*>> m_declarations; |
|||
std::map<ASTString, std::vector<Declaration const*>> m_invisibleDeclarations; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,60 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity exception hierarchy. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <utility> |
|||
#include <libdevcore/Exceptions.h> |
|||
#include <libevmasm/SourceLocation.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
struct ParserError: virtual Exception {}; |
|||
struct TypeError: virtual Exception {}; |
|||
struct DeclarationError: virtual Exception {}; |
|||
struct CompilerError: virtual Exception {}; |
|||
struct InternalCompilerError: virtual Exception {}; |
|||
struct DocstringParsingError: virtual Exception {}; |
|||
|
|||
using errorSourceLocationInfo = std::pair<std::string, SourceLocation>; |
|||
|
|||
class SecondarySourceLocation |
|||
{ |
|||
public: |
|||
SecondarySourceLocation& append(std::string const& _errMsg, SourceLocation const& _sourceLocation) |
|||
{ |
|||
infos.push_back(std::make_pair(_errMsg, _sourceLocation)); |
|||
return *this; |
|||
} |
|||
|
|||
std::vector<errorSourceLocationInfo> infos; |
|||
}; |
|||
|
|||
using errinfo_sourceLocation = boost::error_info<struct tag_sourceLocation, SourceLocation>; |
|||
using errinfo_secondarySourceLocation = boost::error_info<struct tag_secondarySourceLocation, SecondarySourceLocation>; |
|||
|
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,134 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @author Gav Wood <g@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity AST to EVM bytecode compiler for expressions. |
|||
*/ |
|||
|
|||
#include <functional> |
|||
#include <memory> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <libdevcore/Common.h> |
|||
#include <libevmasm/SourceLocation.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/LValue.h> |
|||
#include <libsolidity/Utils.h> |
|||
|
|||
namespace dev { |
|||
namespace eth |
|||
{ |
|||
class AssemblyItem; // forward
|
|||
} |
|||
namespace solidity { |
|||
|
|||
// forward declarations
|
|||
class CompilerContext; |
|||
class CompilerUtils; |
|||
class Type; |
|||
class IntegerType; |
|||
class ArrayType; |
|||
|
|||
/**
|
|||
* Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream |
|||
* of EVM instructions. It needs a compiler context that is the same for the whole compilation |
|||
* unit. |
|||
*/ |
|||
class ExpressionCompiler: private ASTConstVisitor |
|||
{ |
|||
public: |
|||
/// Appends code for a State Variable accessor function
|
|||
static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); |
|||
|
|||
explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): |
|||
m_optimize(_optimize), m_context(_compilerContext) {} |
|||
|
|||
/// Compile the given @a _expression and leave its value on the stack.
|
|||
void compile(Expression const& _expression); |
|||
|
|||
/// Appends code to set a state variable to its initial value/expression.
|
|||
void appendStateVariableInitialization(VariableDeclaration const& _varDecl); |
|||
|
|||
/// Appends code for a State Variable accessor function
|
|||
void appendStateVariableAccessor(VariableDeclaration const& _varDecl); |
|||
|
|||
private: |
|||
virtual bool visit(Assignment const& _assignment) override; |
|||
virtual bool visit(UnaryOperation const& _unaryOperation) override; |
|||
virtual bool visit(BinaryOperation const& _binaryOperation) override; |
|||
virtual bool visit(FunctionCall const& _functionCall) override; |
|||
virtual bool visit(NewExpression const& _newExpression) override; |
|||
virtual void endVisit(MemberAccess const& _memberAccess) override; |
|||
virtual bool visit(IndexAccess const& _indexAccess) override; |
|||
virtual void endVisit(Identifier const& _identifier) override; |
|||
virtual void endVisit(Literal const& _literal) override; |
|||
|
|||
///@{
|
|||
///@name Append code for various operator types
|
|||
void appendAndOrOperatorCode(BinaryOperation const& _binaryOperation); |
|||
void appendCompareOperatorCode(Token::Value _operator, Type const& _type); |
|||
void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); |
|||
|
|||
void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); |
|||
void appendBitOperatorCode(Token::Value _operator); |
|||
void appendShiftOperatorCode(Token::Value _operator); |
|||
/// @}
|
|||
|
|||
/// Appends code to call a function of the given type with the given arguments.
|
|||
void appendExternalFunctionCall( |
|||
FunctionType const& _functionType, |
|||
std::vector<ASTPointer<Expression const>> const& _arguments |
|||
); |
|||
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
|
|||
/// expected to be on the stack and is updated by this call.
|
|||
void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression); |
|||
|
|||
/// Sets the current LValue to a new one (of the appropriate type) from the given declaration.
|
|||
/// Also retrieves the value if it was not requested by @a _expression.
|
|||
void setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression); |
|||
/// Sets the current LValue to a StorageItem holding the type of @a _expression. The reference is assumed
|
|||
/// to be on the stack.
|
|||
/// Also retrieves the value if it was not requested by @a _expression.
|
|||
void setLValueToStorageItem(Expression const& _expression); |
|||
/// Sets the current LValue to a new LValue constructed from the arguments.
|
|||
/// Also retrieves the value if it was not requested by @a _expression.
|
|||
template <class _LValueType, class... _Arguments> |
|||
void setLValue(Expression const& _expression, _Arguments const&... _arguments); |
|||
|
|||
/// @returns the CompilerUtils object containing the current context.
|
|||
CompilerUtils utils(); |
|||
|
|||
bool m_optimize; |
|||
CompilerContext& m_context; |
|||
std::unique_ptr<LValue> m_currentLValue; |
|||
|
|||
}; |
|||
|
|||
template <class _LValueType, class... _Arguments> |
|||
void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments const&... _arguments) |
|||
{ |
|||
solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one."); |
|||
std::unique_ptr<_LValueType> lvalue(new _LValueType(m_context, _arguments...)); |
|||
if (_expression.lvalueRequested()) |
|||
m_currentLValue = move(lvalue); |
|||
else |
|||
lvalue->retrieveValue(_expression.getLocation(), true); |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,191 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Gas consumption estimator working alongside the AST. |
|||
*/ |
|||
|
|||
#include "GasEstimator.h" |
|||
#include <map> |
|||
#include <functional> |
|||
#include <memory> |
|||
#include <libdevcore/SHA3.h> |
|||
#include <libevmasm/ControlFlowGraph.h> |
|||
#include <libevmasm/KnownState.h> |
|||
#include <libevmasm/PathGasMeter.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/CompilerUtils.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::solidity; |
|||
|
|||
GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimation( |
|||
AssemblyItems const& _items, |
|||
vector<ASTNode const*> const& _ast |
|||
) |
|||
{ |
|||
solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, ""); |
|||
map<SourceLocation, GasConsumption> particularCosts; |
|||
|
|||
ControlFlowGraph cfg(_items); |
|||
for (BasicBlock const& block: cfg.optimisedBlocks()) |
|||
{ |
|||
assertThrow(!!block.startState, OptimizerException, ""); |
|||
GasMeter meter(block.startState->copy()); |
|||
auto const end = _items.begin() + block.end; |
|||
for (auto iter = _items.begin() + block.begin; iter != end; ++iter) |
|||
particularCosts[iter->getLocation()] += meter.estimateMax(*iter); |
|||
} |
|||
|
|||
set<ASTNode const*> finestNodes = finestNodesAtLocation(_ast); |
|||
ASTGasConsumptionSelfAccumulated gasCosts; |
|||
auto onNode = [&](ASTNode const& _node) |
|||
{ |
|||
if (!finestNodes.count(&_node)) |
|||
return true; |
|||
gasCosts[&_node][0] = gasCosts[&_node][1] = particularCosts[_node.getLocation()]; |
|||
return true; |
|||
}; |
|||
auto onEdge = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
gasCosts[&_parent][1] += gasCosts[&_child][1]; |
|||
}; |
|||
ASTReduce folder(onNode, onEdge); |
|||
for (ASTNode const* ast: _ast) |
|||
ast->accept(folder); |
|||
|
|||
return gasCosts; |
|||
} |
|||
|
|||
map<ASTNode const*, GasMeter::GasConsumption> GasEstimator::breakToStatementLevel( |
|||
ASTGasConsumptionSelfAccumulated const& _gasCosts, |
|||
vector<ASTNode const*> const& _roots |
|||
) |
|||
{ |
|||
solAssert(std::count(_roots.begin(), _roots.end(), nullptr) == 0, ""); |
|||
// first pass: statementDepth[node] is the distance from the deepend statement to node
|
|||
// in direction of the tree root (or undefined if not possible)
|
|||
map<ASTNode const*, int> statementDepth; |
|||
auto onNodeFirstPass = [&](ASTNode const& _node) |
|||
{ |
|||
if (dynamic_cast<Statement const*>(&_node)) |
|||
statementDepth[&_node] = 0; |
|||
return true; |
|||
}; |
|||
auto onEdgeFirstPass = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
if (statementDepth.count(&_child)) |
|||
statementDepth[&_parent] = max(statementDepth[&_parent], statementDepth[&_child] + 1); |
|||
}; |
|||
ASTReduce firstPass(onNodeFirstPass, onEdgeFirstPass); |
|||
for (ASTNode const* node: _roots) |
|||
node->accept(firstPass); |
|||
|
|||
// we use the location of a node if
|
|||
// - its statement depth is 0 or
|
|||
// - its statement depth is undefined but the parent's statement depth is at least 1
|
|||
map<ASTNode const*, GasConsumption> gasCosts; |
|||
auto onNodeSecondPass = [&](ASTNode const& _node) |
|||
{ |
|||
return statementDepth.count(&_node); |
|||
}; |
|||
auto onEdgeSecondPass = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
bool useNode = false; |
|||
if (statementDepth.count(&_child)) |
|||
useNode = statementDepth[&_child] == 0; |
|||
else |
|||
useNode = statementDepth.count(&_parent) && statementDepth.at(&_parent) > 0; |
|||
if (useNode) |
|||
gasCosts[&_child] = _gasCosts.at(&_child)[1]; |
|||
}; |
|||
ASTReduce secondPass(onNodeSecondPass, onEdgeSecondPass); |
|||
for (ASTNode const* node: _roots) |
|||
node->accept(secondPass); |
|||
// gasCosts should only contain non-overlapping locations
|
|||
return gasCosts; |
|||
} |
|||
|
|||
GasEstimator::GasConsumption GasEstimator::functionalEstimation( |
|||
AssemblyItems const& _items, |
|||
string const& _signature |
|||
) |
|||
{ |
|||
auto state = make_shared<KnownState>(); |
|||
|
|||
if (!_signature.empty()) |
|||
{ |
|||
ExpressionClasses& classes = state->expressionClasses(); |
|||
using Id = ExpressionClasses::Id; |
|||
using Ids = vector<Id>; |
|||
Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::sha3(_signature))))); |
|||
Id calldata = classes.find(eth::Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); |
|||
classes.forceEqual(hashValue, eth::Instruction::DIV, Ids{ |
|||
calldata, |
|||
classes.find(u256(1) << (8 * 28)) |
|||
}); |
|||
} |
|||
|
|||
PathGasMeter meter(_items); |
|||
return meter.estimateMax(0, state); |
|||
} |
|||
|
|||
GasEstimator::GasConsumption GasEstimator::functionalEstimation( |
|||
AssemblyItems const& _items, |
|||
size_t const& _offset, |
|||
FunctionDefinition const& _function |
|||
) |
|||
{ |
|||
auto state = make_shared<KnownState>(); |
|||
|
|||
unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); |
|||
if (parametersSize > 16) |
|||
return GasConsumption::infinite(); |
|||
|
|||
// Store an invalid return value on the stack, so that the path estimator breaks upon reaching
|
|||
// the return jump.
|
|||
AssemblyItem invalidTag(PushTag, u256(-0x10)); |
|||
state->feedItem(invalidTag, true); |
|||
if (parametersSize > 0) |
|||
state->feedItem(eth::swapInstruction(parametersSize)); |
|||
|
|||
return PathGasMeter(_items).estimateMax(_offset, state); |
|||
} |
|||
|
|||
set<ASTNode const*> GasEstimator::finestNodesAtLocation( |
|||
vector<ASTNode const*> const& _roots |
|||
) |
|||
{ |
|||
map<SourceLocation, ASTNode const*> locations; |
|||
set<ASTNode const*> nodes; |
|||
SimpleASTVisitor visitor(function<bool(ASTNode const&)>(), [&](ASTNode const& _n) |
|||
{ |
|||
if (!locations.count(_n.getLocation())) |
|||
{ |
|||
locations[_n.getLocation()] = &_n; |
|||
nodes.insert(&_n); |
|||
} |
|||
}); |
|||
|
|||
for (ASTNode const* root: _roots) |
|||
root->accept(visitor); |
|||
return nodes; |
|||
} |
@ -1,83 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Gas consumption estimator working alongside the AST. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <array> |
|||
#include <libsolidity/ASTForward.h> |
|||
#include <libevmasm/GasMeter.h> |
|||
#include <libevmasm/Assembly.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
struct GasEstimator |
|||
{ |
|||
public: |
|||
using GasConsumption = eth::GasMeter::GasConsumption; |
|||
using ASTGasConsumption = std::map<ASTNode const*, GasConsumption>; |
|||
using ASTGasConsumptionSelfAccumulated = |
|||
std::map<ASTNode const*, std::array<GasConsumption, 2>>; |
|||
|
|||
/// Estimates the gas consumption for every assembly item in the given assembly and stores
|
|||
/// it by source location.
|
|||
/// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs.
|
|||
static ASTGasConsumptionSelfAccumulated structuralEstimation( |
|||
eth::AssemblyItems const& _items, |
|||
std::vector<ASTNode const*> const& _ast |
|||
); |
|||
/// @returns a mapping from nodes with non-overlapping source locations to gas consumptions such that
|
|||
/// the following source locations are part of the mapping:
|
|||
/// 1. source locations of statements that do not contain other statements
|
|||
/// 2. maximal source locations that do not overlap locations coming from the first rule
|
|||
static ASTGasConsumption breakToStatementLevel( |
|||
ASTGasConsumptionSelfAccumulated const& _gasCosts, |
|||
std::vector<ASTNode const*> const& _roots |
|||
); |
|||
|
|||
/// @returns the estimated gas consumption by the (public or external) function with the
|
|||
/// given signature. If no signature is given, estimates the maximum gas usage.
|
|||
static GasConsumption functionalEstimation( |
|||
eth::AssemblyItems const& _items, |
|||
std::string const& _signature = "" |
|||
); |
|||
|
|||
/// @returns the estimated gas consumption by the given function which starts at the given
|
|||
/// offset into the list of assembly items.
|
|||
/// @note this does not work correctly for recursive functions.
|
|||
static GasConsumption functionalEstimation( |
|||
eth::AssemblyItems const& _items, |
|||
size_t const& _offset, |
|||
FunctionDefinition const& _function |
|||
); |
|||
|
|||
private: |
|||
/// @returns the set of AST nodes which are the finest nodes at their location.
|
|||
static std::set<ASTNode const*> finestNodesAtLocation(std::vector<ASTNode const*> const& _roots); |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,96 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @author Gav Wood <g@ethdev.com> |
|||
* @date 2014 |
|||
* Container of the (implicit and explicit) global objects. |
|||
*/ |
|||
|
|||
#include <memory> |
|||
#include <libsolidity/GlobalContext.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Types.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
GlobalContext::GlobalContext(): |
|||
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), |
|||
make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)), |
|||
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), |
|||
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), |
|||
make_shared<MagicVariableDeclaration>("suicide", |
|||
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), |
|||
make_shared<MagicVariableDeclaration>("sha3", |
|||
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), |
|||
make_shared<MagicVariableDeclaration>("log0", |
|||
make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), |
|||
make_shared<MagicVariableDeclaration>("log1", |
|||
make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), |
|||
make_shared<MagicVariableDeclaration>("log2", |
|||
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), |
|||
make_shared<MagicVariableDeclaration>("log3", |
|||
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), |
|||
make_shared<MagicVariableDeclaration>("log4", |
|||
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), |
|||
make_shared<MagicVariableDeclaration>("sha256", |
|||
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), |
|||
make_shared<MagicVariableDeclaration>("ecrecover", |
|||
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), |
|||
make_shared<MagicVariableDeclaration>("ripemd160", |
|||
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))}) |
|||
{ |
|||
} |
|||
|
|||
void GlobalContext::setCurrentContract(ContractDefinition const& _contract) |
|||
{ |
|||
m_currentContract = &_contract; |
|||
} |
|||
|
|||
vector<Declaration const*> GlobalContext::getDeclarations() const |
|||
{ |
|||
vector<Declaration const*> declarations; |
|||
declarations.reserve(m_magicVariables.size()); |
|||
for (ASTPointer<Declaration const> const& variable: m_magicVariables) |
|||
declarations.push_back(variable.get()); |
|||
return declarations; |
|||
} |
|||
|
|||
MagicVariableDeclaration const* GlobalContext::getCurrentThis() const |
|||
{ |
|||
if (!m_thisPointer[m_currentContract]) |
|||
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>( |
|||
"this", make_shared<ContractType>(*m_currentContract)); |
|||
return m_thisPointer[m_currentContract].get(); |
|||
|
|||
} |
|||
|
|||
MagicVariableDeclaration const* GlobalContext::getCurrentSuper() const |
|||
{ |
|||
if (!m_superPointer[m_currentContract]) |
|||
m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>( |
|||
"super", make_shared<ContractType>(*m_currentContract, true)); |
|||
return m_superPointer[m_currentContract].get(); |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,64 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Container of the (implicit and explicit) global objects. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
#include <map> |
|||
#include <memory> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <libsolidity/ASTForward.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class Type; // forward
|
|||
|
|||
/**
|
|||
* Container for all global objects which look like AST nodes, but are not part of the AST |
|||
* that is currently being compiled. |
|||
* @note must not be destroyed or moved during compilation as its objects can be referenced from |
|||
* other objects. |
|||
*/ |
|||
class GlobalContext: private boost::noncopyable |
|||
{ |
|||
public: |
|||
GlobalContext(); |
|||
void setCurrentContract(ContractDefinition const& _contract); |
|||
MagicVariableDeclaration const* getCurrentThis() const; |
|||
MagicVariableDeclaration const* getCurrentSuper() const; |
|||
|
|||
/// @returns a vector of all implicit global declarations excluding "this".
|
|||
std::vector<Declaration const*> getDeclarations() const; |
|||
|
|||
private: |
|||
std::vector<std::shared_ptr<MagicVariableDeclaration const>> m_magicVariables; |
|||
ContractDefinition const* m_currentContract = nullptr; |
|||
std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_thisPointer; |
|||
std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_superPointer; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,446 +0,0 @@ |
|||
|
|||
#include <libsolidity/InterfaceHandler.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/* -- public -- */ |
|||
|
|||
InterfaceHandler::InterfaceHandler() |
|||
{ |
|||
m_lastTag = DocTagType::None; |
|||
} |
|||
|
|||
string InterfaceHandler::getDocumentation( |
|||
ContractDefinition const& _contractDef, |
|||
DocumentationType _type |
|||
) |
|||
{ |
|||
switch(_type) |
|||
{ |
|||
case DocumentationType::NatspecUser: |
|||
return userDocumentation(_contractDef); |
|||
case DocumentationType::NatspecDev: |
|||
return devDocumentation(_contractDef); |
|||
case DocumentationType::ABIInterface: |
|||
return getABIInterface(_contractDef); |
|||
case DocumentationType::ABISolidityInterface: |
|||
return getABISolidityInterface(_contractDef); |
|||
} |
|||
|
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); |
|||
return ""; |
|||
} |
|||
|
|||
string InterfaceHandler::getABIInterface(ContractDefinition const& _contractDef) |
|||
{ |
|||
Json::Value abi(Json::arrayValue); |
|||
|
|||
auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes) |
|||
{ |
|||
Json::Value params(Json::arrayValue); |
|||
solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); |
|||
for (unsigned i = 0; i < _paramNames.size(); ++i) |
|||
{ |
|||
Json::Value param; |
|||
param["name"] = _paramNames[i]; |
|||
param["type"] = _paramTypes[i]; |
|||
params.append(param); |
|||
} |
|||
return params; |
|||
}; |
|||
|
|||
for (auto it: _contractDef.getInterfaceFunctions()) |
|||
{ |
|||
auto externalFunctionType = it.second->externalFunctionType(); |
|||
Json::Value method; |
|||
method["type"] = "function"; |
|||
method["name"] = it.second->getDeclaration().getName(); |
|||
method["constant"] = it.second->isConstant(); |
|||
method["inputs"] = populateParameters( |
|||
externalFunctionType->getParameterNames(), |
|||
externalFunctionType->getParameterTypeNames() |
|||
); |
|||
method["outputs"] = populateParameters( |
|||
externalFunctionType->getReturnParameterNames(), |
|||
externalFunctionType->getReturnParameterTypeNames() |
|||
); |
|||
abi.append(method); |
|||
} |
|||
if (_contractDef.getConstructor()) |
|||
{ |
|||
Json::Value method; |
|||
method["type"] = "constructor"; |
|||
auto externalFunction = FunctionType(*_contractDef.getConstructor()).externalFunctionType(); |
|||
solAssert(!!externalFunction, ""); |
|||
method["inputs"] = populateParameters( |
|||
externalFunction->getParameterNames(), |
|||
externalFunction->getParameterTypeNames() |
|||
); |
|||
abi.append(method); |
|||
} |
|||
|
|||
for (auto const& it: _contractDef.getInterfaceEvents()) |
|||
{ |
|||
Json::Value event; |
|||
event["type"] = "event"; |
|||
event["name"] = it->getName(); |
|||
event["anonymous"] = it->isAnonymous(); |
|||
Json::Value params(Json::arrayValue); |
|||
for (auto const& p: it->getParameters()) |
|||
{ |
|||
Json::Value input; |
|||
input["name"] = p->getName(); |
|||
input["type"] = p->getType()->toString(true); |
|||
input["indexed"] = p->isIndexed(); |
|||
params.append(input); |
|||
} |
|||
event["inputs"] = params; |
|||
abi.append(event); |
|||
} |
|||
return Json::FastWriter().write(abi); |
|||
} |
|||
|
|||
string InterfaceHandler::getABISolidityInterface(ContractDefinition const& _contractDef) |
|||
{ |
|||
string ret = "contract " + _contractDef.getName() + "{"; |
|||
|
|||
auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes) |
|||
{ |
|||
string r = ""; |
|||
solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); |
|||
for (unsigned i = 0; i < _paramNames.size(); ++i) |
|||
r += (r.size() ? "," : "(") + _paramTypes[i] + " " + _paramNames[i]; |
|||
return r.size() ? r + ")" : "()"; |
|||
}; |
|||
if (_contractDef.getConstructor()) |
|||
{ |
|||
auto externalFunction = FunctionType(*_contractDef.getConstructor()).externalFunctionType(); |
|||
solAssert(!!externalFunction, ""); |
|||
ret += |
|||
"function " + |
|||
_contractDef.getName() + |
|||
populateParameters(externalFunction->getParameterNames(), externalFunction->getParameterTypeNames()) + |
|||
";"; |
|||
} |
|||
for (auto const& it: _contractDef.getInterfaceFunctions()) |
|||
{ |
|||
ret += "function " + it.second->getDeclaration().getName() + |
|||
populateParameters(it.second->getParameterNames(), it.second->getParameterTypeNames()) + |
|||
(it.second->isConstant() ? "constant " : ""); |
|||
if (it.second->getReturnParameterTypes().size()) |
|||
ret += "returns" + populateParameters(it.second->getReturnParameterNames(), it.second->getReturnParameterTypeNames()); |
|||
else if (ret.back() == ' ') |
|||
ret.pop_back(); |
|||
ret += ";"; |
|||
} |
|||
|
|||
return ret + "}"; |
|||
} |
|||
|
|||
string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef) |
|||
{ |
|||
Json::Value doc; |
|||
Json::Value methods(Json::objectValue); |
|||
|
|||
for (auto const& it: _contractDef.getInterfaceFunctions()) |
|||
{ |
|||
Json::Value user; |
|||
auto strPtr = it.second->getDocumentation(); |
|||
if (strPtr) |
|||
{ |
|||
resetUser(); |
|||
parseDocString(*strPtr, CommentOwner::Function); |
|||
if (!m_notice.empty()) |
|||
{// since @notice is the only user tag if missing function should not appear
|
|||
user["notice"] = Json::Value(m_notice); |
|||
methods[it.second->externalSignature()] = user; |
|||
} |
|||
} |
|||
} |
|||
doc["methods"] = methods; |
|||
|
|||
return Json::StyledWriter().write(doc); |
|||
} |
|||
|
|||
string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) |
|||
{ |
|||
// LTODO: Somewhere in this function warnings for mismatch of param names
|
|||
// should be thrown
|
|||
Json::Value doc; |
|||
Json::Value methods(Json::objectValue); |
|||
|
|||
auto contractDoc = _contractDef.getDocumentation(); |
|||
if (contractDoc) |
|||
{ |
|||
m_contractAuthor.clear(); |
|||
m_title.clear(); |
|||
parseDocString(*contractDoc, CommentOwner::Contract); |
|||
|
|||
if (!m_contractAuthor.empty()) |
|||
doc["author"] = m_contractAuthor; |
|||
|
|||
if (!m_title.empty()) |
|||
doc["title"] = m_title; |
|||
} |
|||
|
|||
for (auto const& it: _contractDef.getInterfaceFunctions()) |
|||
{ |
|||
Json::Value method; |
|||
auto strPtr = it.second->getDocumentation(); |
|||
if (strPtr) |
|||
{ |
|||
resetDev(); |
|||
parseDocString(*strPtr, CommentOwner::Function); |
|||
|
|||
if (!m_dev.empty()) |
|||
method["details"] = Json::Value(m_dev); |
|||
|
|||
if (!m_author.empty()) |
|||
method["author"] = m_author; |
|||
|
|||
Json::Value params(Json::objectValue); |
|||
vector<string> paramNames = it.second->getParameterNames(); |
|||
for (auto const& pair: m_params) |
|||
{ |
|||
if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end()) |
|||
// LTODO: mismatching parameter name, throw some form of warning and not just an exception
|
|||
BOOST_THROW_EXCEPTION( |
|||
DocstringParsingError() << |
|||
errinfo_comment("documented parameter \"" + pair.first + "\" not found in the parameter list of the function.") |
|||
); |
|||
params[pair.first] = pair.second; |
|||
} |
|||
|
|||
if (!m_params.empty()) |
|||
method["params"] = params; |
|||
|
|||
if (!m_return.empty()) |
|||
method["return"] = m_return; |
|||
|
|||
if (!method.empty()) // add the function, only if we have any documentation to add
|
|||
methods[it.second->externalSignature()] = method; |
|||
} |
|||
} |
|||
doc["methods"] = methods; |
|||
|
|||
return Json::StyledWriter().write(doc); |
|||
} |
|||
|
|||
/* -- private -- */ |
|||
void InterfaceHandler::resetUser() |
|||
{ |
|||
m_notice.clear(); |
|||
} |
|||
|
|||
void InterfaceHandler::resetDev() |
|||
{ |
|||
m_dev.clear(); |
|||
m_author.clear(); |
|||
m_return.clear(); |
|||
m_params.clear(); |
|||
} |
|||
|
|||
static inline string::const_iterator skipLineOrEOS( |
|||
string::const_iterator _nlPos, |
|||
string::const_iterator _end |
|||
) |
|||
{ |
|||
return (_nlPos == _end) ? _end : ++_nlPos; |
|||
} |
|||
|
|||
string::const_iterator InterfaceHandler::parseDocTagLine( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end, |
|||
string& _tagString, |
|||
DocTagType _tagType, |
|||
bool _appending |
|||
) |
|||
{ |
|||
auto nlPos = find(_pos, _end, '\n'); |
|||
if (_appending && _pos < _end && *_pos != ' ') |
|||
_tagString += " "; |
|||
copy(_pos, nlPos, back_inserter(_tagString)); |
|||
m_lastTag = _tagType; |
|||
return skipLineOrEOS(nlPos, _end); |
|||
} |
|||
|
|||
string::const_iterator InterfaceHandler::parseDocTagParam( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end |
|||
) |
|||
{ |
|||
// find param name
|
|||
auto currPos = find(_pos, _end, ' '); |
|||
if (currPos == _end) |
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("End of param name not found" + string(_pos, _end))); |
|||
|
|||
|
|||
auto paramName = string(_pos, currPos); |
|||
|
|||
currPos += 1; |
|||
auto nlPos = find(currPos, _end, '\n'); |
|||
auto paramDesc = string(currPos, nlPos); |
|||
m_params.push_back(make_pair(paramName, paramDesc)); |
|||
|
|||
m_lastTag = DocTagType::Param; |
|||
return skipLineOrEOS(nlPos, _end); |
|||
} |
|||
|
|||
string::const_iterator InterfaceHandler::appendDocTagParam( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end |
|||
) |
|||
{ |
|||
// Should never be called with an empty vector
|
|||
solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); |
|||
|
|||
auto pair = m_params.back(); |
|||
if (_pos < _end && *_pos != ' ') |
|||
pair.second += " "; |
|||
auto nlPos = find(_pos, _end, '\n'); |
|||
copy(_pos, nlPos, back_inserter(pair.second)); |
|||
|
|||
m_params.at(m_params.size() - 1) = pair; |
|||
|
|||
return skipLineOrEOS(nlPos, _end); |
|||
} |
|||
|
|||
string::const_iterator InterfaceHandler::parseDocTag( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end, |
|||
string const& _tag, |
|||
CommentOwner _owner |
|||
) |
|||
{ |
|||
// LTODO: need to check for @(start of a tag) between here and the end of line
|
|||
// for all cases. Also somehow automate list of acceptable tags for each
|
|||
// language construct since current way does not scale well.
|
|||
if (m_lastTag == DocTagType::None || _tag != "") |
|||
{ |
|||
if (_tag == "dev") |
|||
return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, false); |
|||
else if (_tag == "notice") |
|||
return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, false); |
|||
else if (_tag == "return") |
|||
return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, false); |
|||
else if (_tag == "author") |
|||
{ |
|||
if (_owner == CommentOwner::Contract) |
|||
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, false); |
|||
else if (_owner == CommentOwner::Function) |
|||
return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, false); |
|||
else |
|||
// LTODO: for now this else makes no sense but later comments will go to more language constructs
|
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag is legal only for contracts")); |
|||
} |
|||
else if (_tag == "title") |
|||
{ |
|||
if (_owner == CommentOwner::Contract) |
|||
return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, false); |
|||
else |
|||
// LTODO: Unknown tag, throw some form of warning and not just an exception
|
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag is legal only for contracts")); |
|||
} |
|||
else if (_tag == "param") |
|||
return parseDocTagParam(_pos, _end); |
|||
else |
|||
// LTODO: Unknown tag, throw some form of warning and not just an exception
|
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("Unknown tag " + _tag + " encountered")); |
|||
} |
|||
else |
|||
return appendDocTag(_pos, _end, _owner); |
|||
} |
|||
|
|||
string::const_iterator InterfaceHandler::appendDocTag( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end, |
|||
CommentOwner _owner |
|||
) |
|||
{ |
|||
switch (m_lastTag) |
|||
{ |
|||
case DocTagType::Dev: |
|||
return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, true); |
|||
case DocTagType::Notice: |
|||
return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, true); |
|||
case DocTagType::Return: |
|||
return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, true); |
|||
case DocTagType::Author: |
|||
if (_owner == CommentOwner::Contract) |
|||
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, true); |
|||
else if (_owner == CommentOwner::Function) |
|||
return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, true); |
|||
else |
|||
// LTODO: Unknown tag, throw some form of warning and not just an exception
|
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag in illegal comment")); |
|||
case DocTagType::Title: |
|||
if (_owner == CommentOwner::Contract) |
|||
return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, true); |
|||
else |
|||
// LTODO: Unknown tag, throw some form of warning and not just an exception
|
|||
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag in illegal comment")); |
|||
case DocTagType::Param: |
|||
return appendDocTagParam(_pos, _end); |
|||
default: |
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type")); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
static inline string::const_iterator getFirstSpaceOrNl( |
|||
string::const_iterator _pos, |
|||
string::const_iterator _end |
|||
) |
|||
{ |
|||
auto spacePos = find(_pos, _end, ' '); |
|||
auto nlPos = find(_pos, _end, '\n'); |
|||
return (spacePos < nlPos) ? spacePos : nlPos; |
|||
} |
|||
|
|||
void InterfaceHandler::parseDocString(string const& _string, CommentOwner _owner) |
|||
{ |
|||
auto currPos = _string.begin(); |
|||
auto end = _string.end(); |
|||
|
|||
while (currPos != end) |
|||
{ |
|||
auto tagPos = find(currPos, end, '@'); |
|||
auto nlPos = find(currPos, end, '\n'); |
|||
|
|||
if (tagPos != end && tagPos < nlPos) |
|||
{ |
|||
// we found a tag
|
|||
auto tagNameEndPos = getFirstSpaceOrNl(tagPos, end); |
|||
if (tagNameEndPos == end) |
|||
BOOST_THROW_EXCEPTION( |
|||
DocstringParsingError() << |
|||
errinfo_comment("End of tag " + string(tagPos, tagNameEndPos) + "not found")); |
|||
|
|||
currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos), _owner); |
|||
} |
|||
else if (m_lastTag != DocTagType::None) // continuation of the previous tag
|
|||
currPos = appendDocTag(currPos, end, _owner); |
|||
else if (currPos != end) |
|||
{ |
|||
// if it begins without a tag then consider it as @notice
|
|||
if (currPos == _string.begin()) |
|||
{ |
|||
currPos = parseDocTag(currPos, end, "notice", CommentOwner::Function); |
|||
continue; |
|||
} |
|||
else if (nlPos == end) //end of text
|
|||
return; |
|||
// else skip the line if a newline was found and we get here
|
|||
currPos = nlPos + 1; |
|||
} |
|||
} |
|||
} |
|||
|
|||
} //solidity NS
|
|||
} // dev NS
|
@ -1,132 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris <lefteris@ethdev.com> |
|||
* @date 2014 |
|||
* Takes the parsed AST and produces the Natspec |
|||
* documentation and the ABI interface |
|||
* https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format
|
|||
* |
|||
* Can generally deal with JSON files |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <memory> |
|||
#include <json/json.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
// Forward declarations
|
|||
class ContractDefinition; |
|||
enum class DocumentationType: uint8_t; |
|||
|
|||
enum class DocTagType: uint8_t |
|||
{ |
|||
None = 0, |
|||
Dev, |
|||
Notice, |
|||
Param, |
|||
Return, |
|||
Author, |
|||
Title |
|||
}; |
|||
|
|||
enum class CommentOwner |
|||
{ |
|||
Contract, |
|||
Function |
|||
}; |
|||
|
|||
class InterfaceHandler |
|||
{ |
|||
public: |
|||
InterfaceHandler(); |
|||
|
|||
/// Get the given type of documentation
|
|||
/// @param _contractDef The contract definition
|
|||
/// @param _type The type of the documentation. Can be one of the
|
|||
/// types provided by @c DocumentationType
|
|||
/// @return A string with the json representation of provided type
|
|||
std::string getDocumentation( |
|||
ContractDefinition const& _contractDef, |
|||
DocumentationType _type |
|||
); |
|||
/// Get the ABI Interface of the contract
|
|||
/// @param _contractDef The contract definition
|
|||
/// @return A string with the json representation of the contract's ABI Interface
|
|||
std::string getABIInterface(ContractDefinition const& _contractDef); |
|||
std::string getABISolidityInterface(ContractDefinition const& _contractDef); |
|||
/// Get the User documentation of the contract
|
|||
/// @param _contractDef The contract definition
|
|||
/// @return A string with the json representation of the contract's user documentation
|
|||
std::string userDocumentation(ContractDefinition const& _contractDef); |
|||
/// Genereates the Developer's documentation of the contract
|
|||
/// @param _contractDef The contract definition
|
|||
/// @return A string with the json representation
|
|||
/// of the contract's developer documentation
|
|||
std::string devDocumentation(ContractDefinition const& _contractDef); |
|||
|
|||
private: |
|||
void resetUser(); |
|||
void resetDev(); |
|||
|
|||
std::string::const_iterator parseDocTagLine( |
|||
std::string::const_iterator _pos, |
|||
std::string::const_iterator _end, |
|||
std::string& _tagString, |
|||
DocTagType _tagType, |
|||
bool _appending |
|||
); |
|||
std::string::const_iterator parseDocTagParam( |
|||
std::string::const_iterator _pos, |
|||
std::string::const_iterator _end |
|||
); |
|||
std::string::const_iterator appendDocTagParam( |
|||
std::string::const_iterator _pos, |
|||
std::string::const_iterator _end |
|||
); |
|||
void parseDocString(std::string const& _string, CommentOwner _owner); |
|||
std::string::const_iterator appendDocTag( |
|||
std::string::const_iterator _pos, |
|||
std::string::const_iterator _end, |
|||
CommentOwner _owner |
|||
); |
|||
std::string::const_iterator parseDocTag( |
|||
std::string::const_iterator _pos, |
|||
std::string::const_iterator _end, |
|||
std::string const& _tag, |
|||
CommentOwner _owner |
|||
); |
|||
|
|||
// internal state
|
|||
DocTagType m_lastTag; |
|||
std::string m_notice; |
|||
std::string m_dev; |
|||
std::string m_return; |
|||
std::string m_contractAuthor; |
|||
std::string m_author; |
|||
std::string m_title; |
|||
std::vector<std::pair<std::string, std::string>> m_params; |
|||
}; |
|||
|
|||
} //solidity NS
|
|||
} // dev NS
|
@ -1,457 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* LValues for use in the expresison compiler. |
|||
*/ |
|||
|
|||
#include <libsolidity/LValue.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libsolidity/Types.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/CompilerUtils.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace solidity; |
|||
|
|||
|
|||
StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration): |
|||
LValue(_compilerContext, *_declaration.getType()), |
|||
m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)), |
|||
m_size(m_dataType.getSizeOnStack()) |
|||
{ |
|||
} |
|||
|
|||
void StackVariable::retrieveValue(SourceLocation const& _location, bool) const |
|||
{ |
|||
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); |
|||
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
|
|||
BOOST_THROW_EXCEPTION( |
|||
CompilerError() << |
|||
errinfo_sourceLocation(_location) << |
|||
errinfo_comment("Stack too deep, try removing local variables.") |
|||
); |
|||
solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch."); |
|||
for (unsigned i = 0; i < m_size; ++i) |
|||
m_context << eth::dupInstruction(stackPos + 1); |
|||
} |
|||
|
|||
void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const |
|||
{ |
|||
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; |
|||
if (stackDiff > 16) |
|||
BOOST_THROW_EXCEPTION( |
|||
CompilerError() << |
|||
errinfo_sourceLocation(_location) << |
|||
errinfo_comment("Stack too deep, try removing local variables.") |
|||
); |
|||
else if (stackDiff > 0) |
|||
for (unsigned i = 0; i < m_size; ++i) |
|||
m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; |
|||
if (!_move) |
|||
retrieveValue(_location); |
|||
} |
|||
|
|||
void StackVariable::setToZero(SourceLocation const& _location, bool) const |
|||
{ |
|||
CompilerUtils(m_context).pushZeroValue(m_dataType); |
|||
storeValue(m_dataType, _location, true); |
|||
} |
|||
|
|||
MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): |
|||
LValue(_compilerContext, _type), |
|||
m_padded(_padded) |
|||
{ |
|||
} |
|||
|
|||
void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const |
|||
{ |
|||
if (m_dataType.isValueType()) |
|||
{ |
|||
if (!_remove) |
|||
m_context << eth::Instruction::DUP1; |
|||
CompilerUtils(m_context).loadFromMemoryDynamic(m_dataType, false, m_padded, false); |
|||
} |
|||
else |
|||
m_context << eth::Instruction::MLOAD; |
|||
} |
|||
|
|||
void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const |
|||
{ |
|||
CompilerUtils utils(m_context); |
|||
if (m_dataType.isValueType()) |
|||
{ |
|||
solAssert(_sourceType.isValueType(), ""); |
|||
utils.moveIntoStack(_sourceType.getSizeOnStack()); |
|||
utils.convertType(_sourceType, m_dataType, true); |
|||
if (!_move) |
|||
{ |
|||
utils.moveToStackTop(m_dataType.getSizeOnStack()); |
|||
utils.copyToStackTop(2, m_dataType.getSizeOnStack()); |
|||
} |
|||
utils.storeInMemoryDynamic(m_dataType, m_padded); |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
else |
|||
{ |
|||
solAssert(_sourceType == m_dataType, "Conversion not implemented for assignment to memory."); |
|||
|
|||
solAssert(m_dataType.getSizeOnStack() == 1, ""); |
|||
if (!_move) |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; |
|||
// stack: [value] value lvalue
|
|||
// only store the reference
|
|||
m_context << eth::Instruction::MSTORE; |
|||
} |
|||
} |
|||
|
|||
void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const |
|||
{ |
|||
CompilerUtils utils(m_context); |
|||
if (!_removeReference) |
|||
m_context << eth::Instruction::DUP1; |
|||
utils.pushZeroValue(m_dataType); |
|||
utils.storeInMemoryDynamic(m_dataType, m_padded); |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): |
|||
StorageItem(_compilerContext, *_declaration.getType()) |
|||
{ |
|||
auto const& location = m_context.getStorageLocationOfVariable(_declaration); |
|||
m_context << location.first << u256(location.second); |
|||
} |
|||
|
|||
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): |
|||
LValue(_compilerContext, _type) |
|||
{ |
|||
if (m_dataType.isValueType()) |
|||
{ |
|||
solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); |
|||
solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); |
|||
} |
|||
} |
|||
|
|||
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const |
|||
{ |
|||
// stack: storage_key storage_offset
|
|||
if (!m_dataType.isValueType()) |
|||
{ |
|||
solAssert(m_dataType.getSizeOnStack() == 1, "Invalid storage ref size."); |
|||
if (_remove) |
|||
m_context << eth::Instruction::POP; // remove byte offset
|
|||
else |
|||
m_context << eth::Instruction::DUP2; |
|||
return; |
|||
} |
|||
if (!_remove) |
|||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); |
|||
if (m_dataType.getStorageBytes() == 32) |
|||
m_context << eth::Instruction::POP << eth::Instruction::SLOAD; |
|||
else |
|||
{ |
|||
m_context |
|||
<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 |
|||
<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; |
|||
if (m_dataType.getCategory() == Type::Category::FixedBytes) |
|||
m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL; |
|||
else if ( |
|||
m_dataType.getCategory() == Type::Category::Integer && |
|||
dynamic_cast<IntegerType const&>(m_dataType).isSigned() |
|||
) |
|||
m_context << u256(m_dataType.getStorageBytes() - 1) << eth::Instruction::SIGNEXTEND; |
|||
else |
|||
m_context << ((u256(0x1) << (8 * m_dataType.getStorageBytes())) - 1) << eth::Instruction::AND; |
|||
} |
|||
} |
|||
|
|||
void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const |
|||
{ |
|||
CompilerUtils utils(m_context); |
|||
// stack: value storage_key storage_offset
|
|||
if (m_dataType.isValueType()) |
|||
{ |
|||
solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size."); |
|||
solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size."); |
|||
if (m_dataType.getStorageBytes() == 32) |
|||
{ |
|||
// offset should be zero
|
|||
m_context << eth::Instruction::POP; |
|||
if (!_move) |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; |
|||
m_context << eth::Instruction::SSTORE; |
|||
} |
|||
else |
|||
{ |
|||
// OR the value into the other values in the storage slot
|
|||
m_context << u256(0x100) << eth::Instruction::EXP; |
|||
// stack: value storage_ref multiplier
|
|||
// fetch old value
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
// stack: value storege_ref multiplier old_full_value
|
|||
// clear bytes in old value
|
|||
m_context |
|||
<< eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) |
|||
<< eth::Instruction::MUL; |
|||
m_context << eth::Instruction::NOT << eth::Instruction::AND; |
|||
// stack: value storage_ref multiplier cleared_value
|
|||
m_context |
|||
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4; |
|||
// stack: value storage_ref cleared_value multiplier value
|
|||
if (m_dataType.getCategory() == Type::Category::FixedBytes) |
|||
m_context |
|||
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).numBytes())) |
|||
<< eth::Instruction::SWAP1 << eth::Instruction::DIV; |
|||
else if ( |
|||
m_dataType.getCategory() == Type::Category::Integer && |
|||
dynamic_cast<IntegerType const&>(m_dataType).isSigned() |
|||
) |
|||
// remove the higher order bits
|
|||
m_context |
|||
<< (u256(1) << (8 * (32 - m_dataType.getStorageBytes()))) |
|||
<< eth::Instruction::SWAP1 |
|||
<< eth::Instruction::DUP2 |
|||
<< eth::Instruction::MUL |
|||
<< eth::Instruction::DIV; |
|||
m_context << eth::Instruction::MUL << eth::Instruction::OR; |
|||
// stack: value storage_ref updated_value
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
if (_move) |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
solAssert( |
|||
_sourceType.getCategory() == m_dataType.getCategory(), |
|||
"Wrong type conversation for assignment."); |
|||
if (m_dataType.getCategory() == Type::Category::Array) |
|||
{ |
|||
m_context << eth::Instruction::POP; // remove byte offset
|
|||
ArrayUtils(m_context).copyArrayToStorage( |
|||
dynamic_cast<ArrayType const&>(m_dataType), |
|||
dynamic_cast<ArrayType const&>(_sourceType)); |
|||
if (_move) |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
else if (m_dataType.getCategory() == Type::Category::Struct) |
|||
{ |
|||
// stack layout: source_ref target_ref target_offset
|
|||
// note that we have structs, so offset should be zero and are ignored
|
|||
m_context << eth::Instruction::POP; |
|||
auto const& structType = dynamic_cast<StructType const&>(m_dataType); |
|||
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType); |
|||
solAssert( |
|||
structType.structDefinition() == sourceType.structDefinition(), |
|||
"Struct assignment with conversion." |
|||
); |
|||
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported."); |
|||
for (auto const& member: structType.getMembers()) |
|||
{ |
|||
// assign each member that is not a mapping
|
|||
TypePointer const& memberType = member.type; |
|||
if (memberType->getCategory() == Type::Category::Mapping) |
|||
continue; |
|||
TypePointer sourceMemberType = sourceType.getMemberType(member.name); |
|||
if (sourceType.location() == DataLocation::Storage) |
|||
{ |
|||
// stack layout: source_ref target_ref
|
|||
pair<u256, unsigned> const& offsets = sourceType.getStorageOffsetsOfMember(member.name); |
|||
m_context << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
m_context << u256(offsets.second); |
|||
// stack: source_ref target_ref source_member_ref source_member_off
|
|||
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true); |
|||
// stack: source_ref target_ref source_value...
|
|||
} |
|||
else |
|||
{ |
|||
solAssert(sourceType.location() == DataLocation::Memory, ""); |
|||
// stack layout: source_ref target_ref
|
|||
TypePointer sourceMemberType = sourceType.getMemberType(member.name); |
|||
m_context << sourceType.memoryOffsetOfMember(member.name); |
|||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |
|||
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true); |
|||
// stack layout: source_ref target_ref source_value...
|
|||
} |
|||
unsigned stackSize = sourceMemberType->getSizeOnStack(); |
|||
pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.name); |
|||
m_context << eth::dupInstruction(1 + stackSize) << offsets.first << eth::Instruction::ADD; |
|||
m_context << u256(offsets.second); |
|||
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
|
|||
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true); |
|||
} |
|||
// stack layout: source_ref target_ref
|
|||
solAssert(sourceType.getSizeOnStack() == 1, "Unexpected source size."); |
|||
if (_move) |
|||
utils.popStackSlots(2); |
|||
else |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |
|||
} |
|||
else |
|||
BOOST_THROW_EXCEPTION( |
|||
InternalCompilerError() |
|||
<< errinfo_sourceLocation(_location) |
|||
<< errinfo_comment("Invalid non-value type for assignment.")); |
|||
} |
|||
} |
|||
|
|||
void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const |
|||
{ |
|||
if (m_dataType.getCategory() == Type::Category::Array) |
|||
{ |
|||
if (!_removeReference) |
|||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); |
|||
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); |
|||
} |
|||
else if (m_dataType.getCategory() == Type::Category::Struct) |
|||
{ |
|||
// stack layout: storage_key storage_offset
|
|||
// @todo this can be improved: use StorageItem for non-value types, and just store 0 in
|
|||
// all slots that contain value types later.
|
|||
auto const& structType = dynamic_cast<StructType const&>(m_dataType); |
|||
for (auto const& member: structType.getMembers()) |
|||
{ |
|||
// zero each member that is not a mapping
|
|||
TypePointer const& memberType = member.type; |
|||
if (memberType->getCategory() == Type::Category::Mapping) |
|||
continue; |
|||
pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.name); |
|||
m_context |
|||
<< offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD |
|||
<< u256(offsets.second); |
|||
StorageItem(m_context, *memberType).setToZero(); |
|||
} |
|||
if (_removeReference) |
|||
m_context << eth::Instruction::POP << eth::Instruction::POP; |
|||
} |
|||
else |
|||
{ |
|||
solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); |
|||
if (!_removeReference) |
|||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); |
|||
if (m_dataType.getStorageBytes() == 32) |
|||
{ |
|||
// offset should be zero
|
|||
m_context |
|||
<< eth::Instruction::POP << u256(0) |
|||
<< eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
} |
|||
else |
|||
{ |
|||
m_context << u256(0x100) << eth::Instruction::EXP; |
|||
// stack: storage_ref multiplier
|
|||
// fetch old value
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
// stack: storege_ref multiplier old_full_value
|
|||
// clear bytes in old value
|
|||
m_context |
|||
<< eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) |
|||
<< eth::Instruction::MUL; |
|||
m_context << eth::Instruction::NOT << eth::Instruction::AND; |
|||
// stack: storage_ref cleared_value
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// Used in StorageByteArrayElement
|
|||
static FixedBytesType byteType(1); |
|||
|
|||
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): |
|||
LValue(_compilerContext, byteType) |
|||
{ |
|||
} |
|||
|
|||
void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove) const |
|||
{ |
|||
// stack: ref byte_number
|
|||
if (_remove) |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SLOAD |
|||
<< eth::Instruction::SWAP1 << eth::Instruction::BYTE; |
|||
else |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD |
|||
<< eth::Instruction::DUP2 << eth::Instruction::BYTE; |
|||
m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL; |
|||
} |
|||
|
|||
void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const |
|||
{ |
|||
// stack: value ref byte_number
|
|||
m_context << u256(31) << eth::Instruction::SUB << u256(0x100) << eth::Instruction::EXP; |
|||
// stack: value ref (1<<(8*(31-byte_number)))
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
// stack: value ref (1<<(8*(31-byte_number))) old_full_value
|
|||
// clear byte in old value
|
|||
m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL |
|||
<< eth::Instruction::NOT << eth::Instruction::AND; |
|||
// stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte
|
|||
m_context << eth::Instruction::SWAP1; |
|||
m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV |
|||
<< eth::Instruction::MUL << eth::Instruction::OR; |
|||
// stack: value ref new_full_value
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
if (_move) |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
|
|||
void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeReference) const |
|||
{ |
|||
// stack: ref byte_number
|
|||
if (!_removeReference) |
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2; |
|||
m_context << u256(31) << eth::Instruction::SUB << u256(0x100) << eth::Instruction::EXP; |
|||
// stack: ref (1<<(8*(31-byte_number)))
|
|||
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |
|||
// stack: ref (1<<(8*(31-byte_number))) old_full_value
|
|||
// clear byte in old value
|
|||
m_context << eth::Instruction::SWAP1 << u256(0xff) << eth::Instruction::MUL; |
|||
m_context << eth::Instruction::NOT << eth::Instruction::AND; |
|||
// stack: ref old_full_value_with_cleared_byte
|
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; |
|||
} |
|||
|
|||
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): |
|||
LValue(_compilerContext, *_arrayType.getMemberType("length")), |
|||
m_arrayType(_arrayType) |
|||
{ |
|||
solAssert(m_arrayType.isDynamicallySized(), ""); |
|||
} |
|||
|
|||
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const |
|||
{ |
|||
if (!_remove) |
|||
m_context << eth::Instruction::DUP1; |
|||
m_context << eth::Instruction::SLOAD; |
|||
} |
|||
|
|||
void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const |
|||
{ |
|||
if (_move) |
|||
m_context << eth::Instruction::SWAP1; |
|||
else |
|||
m_context << eth::Instruction::DUP2; |
|||
ArrayUtils(m_context).resizeDynamicArray(m_arrayType); |
|||
} |
|||
|
|||
void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const |
|||
{ |
|||
if (!_removeReference) |
|||
m_context << eth::Instruction::DUP1; |
|||
ArrayUtils(m_context).clearDynamicArray(m_arrayType); |
|||
} |
@ -1,196 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* LValues for use in the expresison compiler. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <libevmasm/SourceLocation.h> |
|||
#include <libsolidity/ArrayUtils.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class Declaration; |
|||
class Type; |
|||
class ArrayType; |
|||
class CompilerContext; |
|||
|
|||
/**
|
|||
* Abstract class used to retrieve, delete and store data in lvalues/variables. |
|||
*/ |
|||
class LValue |
|||
{ |
|||
protected: |
|||
LValue(CompilerContext& _compilerContext, Type const& _dataType): |
|||
m_context(_compilerContext), m_dataType(_dataType) {} |
|||
|
|||
public: |
|||
/// @returns the number of stack slots occupied by the lvalue reference
|
|||
virtual unsigned sizeOnStack() const { return 1; } |
|||
/// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true,
|
|||
/// also removes the reference from the stack.
|
|||
/// @a _location source location of the current expression, used for error reporting.
|
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const = 0; |
|||
/// Moves a value from the stack to the lvalue. Removes the value if @a _move is true.
|
|||
/// @a _location is the source location of the expression that caused this operation.
|
|||
/// Stack pre: value [lvalue_ref]
|
|||
/// Stack post: if !_move: value_of(lvalue_ref)
|
|||
virtual void storeValue(Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; |
|||
/// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true.
|
|||
/// @a _location is the source location of the requested operation
|
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const = 0; |
|||
|
|||
protected: |
|||
CompilerContext& m_context; |
|||
Type const& m_dataType; |
|||
}; |
|||
|
|||
/**
|
|||
* Local variable that is completely stored on the stack. |
|||
*/ |
|||
class StackVariable: public LValue |
|||
{ |
|||
public: |
|||
StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); |
|||
|
|||
virtual unsigned sizeOnStack() const override { return 0; } |
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; |
|||
virtual void storeValue( |
|||
Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _move = false |
|||
) const override; |
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const override; |
|||
|
|||
private: |
|||
/// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable.
|
|||
unsigned m_baseStackOffset; |
|||
/// Number of stack elements occupied by the value (not the reference).
|
|||
unsigned m_size; |
|||
}; |
|||
|
|||
/**
|
|||
* Reference to some item in memory. |
|||
*/ |
|||
class MemoryItem: public LValue |
|||
{ |
|||
public: |
|||
MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true); |
|||
virtual unsigned sizeOnStack() const override { return 1; } |
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; |
|||
virtual void storeValue( |
|||
Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _move = false |
|||
) const override; |
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const override; |
|||
private: |
|||
/// Special flag to deal with byte array elements.
|
|||
bool m_padded = false; |
|||
}; |
|||
|
|||
/**
|
|||
* Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>, |
|||
* where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied |
|||
* by 2**i before storing it. |
|||
*/ |
|||
class StorageItem: public LValue |
|||
{ |
|||
public: |
|||
/// Constructs the LValue and pushes the location of @a _declaration onto the stack.
|
|||
StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); |
|||
/// Constructs the LValue and assumes that the storage reference is already on the stack.
|
|||
StorageItem(CompilerContext& _compilerContext, Type const& _type); |
|||
virtual unsigned sizeOnStack() const override { return 2; } |
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; |
|||
virtual void storeValue( |
|||
Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _move = false |
|||
) const override; |
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const override; |
|||
}; |
|||
|
|||
/**
|
|||
* Reference to a single byte inside a storage byte array. |
|||
* Stack: <storage_ref> <byte_number> |
|||
*/ |
|||
class StorageByteArrayElement: public LValue |
|||
{ |
|||
public: |
|||
/// Constructs the LValue and assumes that the storage reference is already on the stack.
|
|||
StorageByteArrayElement(CompilerContext& _compilerContext); |
|||
virtual unsigned sizeOnStack() const override { return 2; } |
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; |
|||
virtual void storeValue( |
|||
Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _move = false |
|||
) const override; |
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const override; |
|||
}; |
|||
|
|||
/**
|
|||
* Reference to the "length" member of a dynamically-sized array. This is an LValue with special |
|||
* semantics since assignments to it might reduce its length and thus arrays members have to be |
|||
* deleted. |
|||
*/ |
|||
class StorageArrayLength: public LValue |
|||
{ |
|||
public: |
|||
/// Constructs the LValue, assumes that the reference to the array head is already on the stack.
|
|||
StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); |
|||
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; |
|||
virtual void storeValue( |
|||
Type const& _sourceType, |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _move = false |
|||
) const override; |
|||
virtual void setToZero( |
|||
SourceLocation const& _location = SourceLocation(), |
|||
bool _removeReference = true |
|||
) const override; |
|||
|
|||
private: |
|||
ArrayType const& m_arrayType; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,530 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Parser part that determines the declarations corresponding to names and the types of expressions. |
|||
*/ |
|||
|
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
NameAndTypeResolver::NameAndTypeResolver(vector<Declaration const*> const& _globals) |
|||
{ |
|||
for (Declaration const* declaration: _globals) |
|||
m_scopes[nullptr].registerDeclaration(*declaration); |
|||
} |
|||
|
|||
void NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) |
|||
{ |
|||
// The helper registers all declarations in m_scopes as a side-effect of its construction.
|
|||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit); |
|||
} |
|||
|
|||
void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) |
|||
{ |
|||
m_currentScope = &m_scopes[nullptr]; |
|||
|
|||
for (ASTPointer<InheritanceSpecifier> const& baseContract: _contract.getBaseContracts()) |
|||
ReferencesResolver resolver(*baseContract, *this, &_contract, nullptr); |
|||
|
|||
m_currentScope = &m_scopes[&_contract]; |
|||
|
|||
linearizeBaseContracts(_contract); |
|||
std::vector<ContractDefinition const*> properBases( |
|||
++_contract.getLinearizedBaseContracts().begin(), |
|||
_contract.getLinearizedBaseContracts().end() |
|||
); |
|||
|
|||
for (ContractDefinition const* base: properBases) |
|||
importInheritedScope(*base); |
|||
|
|||
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs()) |
|||
ReferencesResolver resolver(*structDef, *this, &_contract, nullptr); |
|||
for (ASTPointer<EnumDefinition> const& enumDef: _contract.getDefinedEnums()) |
|||
ReferencesResolver resolver(*enumDef, *this, &_contract, nullptr); |
|||
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) |
|||
ReferencesResolver resolver(*variable, *this, &_contract, nullptr); |
|||
for (ASTPointer<EventDefinition> const& event: _contract.getEvents()) |
|||
ReferencesResolver resolver(*event, *this, &_contract, nullptr); |
|||
|
|||
// these can contain code, only resolve parameters for now
|
|||
for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers()) |
|||
{ |
|||
m_currentScope = &m_scopes[modifier.get()]; |
|||
ReferencesResolver resolver(*modifier, *this, &_contract, nullptr); |
|||
} |
|||
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) |
|||
{ |
|||
m_currentScope = &m_scopes[function.get()]; |
|||
ReferencesResolver referencesResolver(*function, *this, &_contract, |
|||
function->getReturnParameterList().get()); |
|||
} |
|||
|
|||
m_currentScope = &m_scopes[&_contract]; |
|||
|
|||
// now resolve references inside the code
|
|||
for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers()) |
|||
{ |
|||
m_currentScope = &m_scopes[modifier.get()]; |
|||
ReferencesResolver resolver(*modifier, *this, &_contract, nullptr, true); |
|||
} |
|||
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) |
|||
{ |
|||
m_currentScope = &m_scopes[function.get()]; |
|||
ReferencesResolver referencesResolver( |
|||
*function, |
|||
*this, |
|||
&_contract, |
|||
function->getReturnParameterList().get(), |
|||
true |
|||
); |
|||
} |
|||
} |
|||
|
|||
void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) |
|||
{ |
|||
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs()) |
|||
structDef->checkValidityOfMembers(); |
|||
_contract.checkTypeRequirements(); |
|||
} |
|||
|
|||
void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) |
|||
{ |
|||
m_scopes[nullptr].registerDeclaration(_declaration, false, true); |
|||
solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope."); |
|||
} |
|||
|
|||
vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const |
|||
{ |
|||
auto iterator = m_scopes.find(_scope); |
|||
if (iterator == end(m_scopes)) |
|||
return vector<Declaration const*>({}); |
|||
return iterator->second.resolveName(_name, false); |
|||
} |
|||
|
|||
vector<Declaration const*> NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) |
|||
{ |
|||
return m_currentScope->resolveName(_name, _recursive); |
|||
} |
|||
|
|||
vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( |
|||
Identifier const& _identifier, |
|||
vector<Declaration const*> const& _declarations |
|||
) |
|||
{ |
|||
solAssert(_declarations.size() > 1, ""); |
|||
vector<Declaration const*> uniqueFunctions; |
|||
|
|||
for (auto it = _declarations.begin(); it != _declarations.end(); ++it) |
|||
{ |
|||
solAssert(*it, ""); |
|||
// the declaration is functionDefinition while declarations > 1
|
|||
FunctionDefinition const& functionDefinition = dynamic_cast<FunctionDefinition const&>(**it); |
|||
FunctionType functionType(functionDefinition); |
|||
for (auto parameter: functionType.getParameterTypes() + functionType.getReturnParameterTypes()) |
|||
if (!parameter) |
|||
BOOST_THROW_EXCEPTION( |
|||
DeclarationError() << |
|||
errinfo_sourceLocation(_identifier.getLocation()) << |
|||
errinfo_comment("Function type can not be used in this context") |
|||
); |
|||
if (uniqueFunctions.end() == find_if( |
|||
uniqueFunctions.begin(), |
|||
uniqueFunctions.end(), |
|||
[&](Declaration const* d) |
|||
{ |
|||
FunctionType newFunctionType(dynamic_cast<FunctionDefinition const&>(*d)); |
|||
return functionType.hasEqualArgumentTypes(newFunctionType); |
|||
} |
|||
)) |
|||
uniqueFunctions.push_back(*it); |
|||
} |
|||
return uniqueFunctions; |
|||
} |
|||
|
|||
void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) |
|||
{ |
|||
auto iterator = m_scopes.find(&_base); |
|||
solAssert(iterator != end(m_scopes), ""); |
|||
for (auto const& nameAndDeclaration: iterator->second.getDeclarations()) |
|||
for (auto const& declaration: nameAndDeclaration.second) |
|||
// Import if it was declared in the base, is not the constructor and is visible in derived classes
|
|||
if (declaration->getScope() == &_base && declaration->isVisibleInDerivedContracts()) |
|||
m_currentScope->registerDeclaration(*declaration); |
|||
} |
|||
|
|||
void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) const |
|||
{ |
|||
// order in the lists is from derived to base
|
|||
// list of lists to linearize, the last element is the list of direct bases
|
|||
list<list<ContractDefinition const*>> input(1, {}); |
|||
for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: _contract.getBaseContracts()) |
|||
{ |
|||
ASTPointer<Identifier> baseName = baseSpecifier->getName(); |
|||
auto base = dynamic_cast<ContractDefinition const*>(&baseName->getReferencedDeclaration()); |
|||
if (!base) |
|||
BOOST_THROW_EXCEPTION(baseName->createTypeError("Contract expected.")); |
|||
// "push_front" has the effect that bases mentioned later can overwrite members of bases
|
|||
// mentioned earlier
|
|||
input.back().push_front(base); |
|||
vector<ContractDefinition const*> const& basesBases = base->getLinearizedBaseContracts(); |
|||
if (basesBases.empty()) |
|||
BOOST_THROW_EXCEPTION(baseName->createTypeError("Definition of base has to precede definition of derived contract")); |
|||
input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end())); |
|||
} |
|||
input.back().push_front(&_contract); |
|||
vector<ContractDefinition const*> result = cThreeMerge(input); |
|||
if (result.empty()) |
|||
BOOST_THROW_EXCEPTION(_contract.createTypeError("Linearization of inheritance graph impossible")); |
|||
_contract.setLinearizedBaseContracts(result); |
|||
} |
|||
|
|||
template <class _T> |
|||
vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge) |
|||
{ |
|||
// returns true iff _candidate appears only as last element of the lists
|
|||
auto appearsOnlyAtHead = [&](_T const* _candidate) -> bool |
|||
{ |
|||
for (list<_T const*> const& bases: _toMerge) |
|||
{ |
|||
solAssert(!bases.empty(), ""); |
|||
if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) |
|||
return false; |
|||
} |
|||
return true; |
|||
}; |
|||
// returns the next candidate to append to the linearized list or nullptr on failure
|
|||
auto nextCandidate = [&]() -> _T const* |
|||
{ |
|||
for (list<_T const*> const& bases: _toMerge) |
|||
{ |
|||
solAssert(!bases.empty(), ""); |
|||
if (appearsOnlyAtHead(bases.front())) |
|||
return bases.front(); |
|||
} |
|||
return nullptr; |
|||
}; |
|||
// removes the given contract from all lists
|
|||
auto removeCandidate = [&](_T const* _candidate) |
|||
{ |
|||
for (auto it = _toMerge.begin(); it != _toMerge.end();) |
|||
{ |
|||
it->remove(_candidate); |
|||
if (it->empty()) |
|||
it = _toMerge.erase(it); |
|||
else |
|||
++it; |
|||
} |
|||
}; |
|||
|
|||
_toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); }); |
|||
vector<_T const*> result; |
|||
while (!_toMerge.empty()) |
|||
{ |
|||
_T const* candidate = nextCandidate(); |
|||
if (!candidate) |
|||
return vector<_T const*>(); |
|||
result.push_back(candidate); |
|||
removeCandidate(candidate); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, DeclarationContainer>& _scopes, |
|||
ASTNode& _astRoot): |
|||
m_scopes(_scopes), m_currentScope(nullptr) |
|||
{ |
|||
_astRoot.accept(*this); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) |
|||
{ |
|||
registerDeclaration(_contract, true); |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(ContractDefinition&) |
|||
{ |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(StructDefinition& _struct) |
|||
{ |
|||
registerDeclaration(_struct, true); |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(StructDefinition&) |
|||
{ |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(EnumDefinition& _enum) |
|||
{ |
|||
registerDeclaration(_enum, true); |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(EnumDefinition&) |
|||
{ |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(EnumValue& _value) |
|||
{ |
|||
registerDeclaration(_value, false); |
|||
return true; |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) |
|||
{ |
|||
registerDeclaration(_function, true); |
|||
m_currentFunction = &_function; |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) |
|||
{ |
|||
m_currentFunction = nullptr; |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier) |
|||
{ |
|||
registerDeclaration(_modifier, true); |
|||
m_currentFunction = &_modifier; |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) |
|||
{ |
|||
m_currentFunction = nullptr; |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement) |
|||
{ |
|||
// Register the local variables with the function
|
|||
// This does not fit here perfectly, but it saves us another AST visit.
|
|||
solAssert(m_currentFunction, "Variable declaration without function."); |
|||
m_currentFunction->addLocalVariable(_variableDeclarationStatement.getDeclaration()); |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) |
|||
{ |
|||
registerDeclaration(_declaration, false); |
|||
return true; |
|||
} |
|||
|
|||
bool DeclarationRegistrationHelper::visit(EventDefinition& _event) |
|||
{ |
|||
registerDeclaration(_event, true); |
|||
return true; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::endVisit(EventDefinition&) |
|||
{ |
|||
closeCurrentScope(); |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration) |
|||
{ |
|||
map<ASTNode const*, DeclarationContainer>::iterator iter; |
|||
bool newlyAdded; |
|||
tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, DeclarationContainer(m_currentScope, &m_scopes[m_currentScope])); |
|||
solAssert(newlyAdded, "Unable to add new scope."); |
|||
m_currentScope = &_declaration; |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::closeCurrentScope() |
|||
{ |
|||
solAssert(m_currentScope, "Closed non-existing scope."); |
|||
m_currentScope = m_scopes[m_currentScope].getEnclosingDeclaration(); |
|||
} |
|||
|
|||
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) |
|||
{ |
|||
if (!m_scopes[m_currentScope].registerDeclaration(_declaration, !_declaration.isVisibleInContract())) |
|||
{ |
|||
SourceLocation firstDeclarationLocation; |
|||
SourceLocation secondDeclarationLocation; |
|||
Declaration const* conflictingDeclaration = m_scopes[m_currentScope].conflictingDeclaration(_declaration); |
|||
solAssert(conflictingDeclaration, ""); |
|||
|
|||
if (_declaration.getLocation().start < conflictingDeclaration->getLocation().start) |
|||
{ |
|||
firstDeclarationLocation = _declaration.getLocation(); |
|||
secondDeclarationLocation = conflictingDeclaration->getLocation(); |
|||
} |
|||
else |
|||
{ |
|||
firstDeclarationLocation = conflictingDeclaration->getLocation(); |
|||
secondDeclarationLocation = _declaration.getLocation(); |
|||
} |
|||
|
|||
BOOST_THROW_EXCEPTION( |
|||
DeclarationError() << |
|||
errinfo_sourceLocation(secondDeclarationLocation) << |
|||
errinfo_comment("Identifier already declared.") << |
|||
errinfo_secondarySourceLocation( |
|||
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation) |
|||
) |
|||
); |
|||
} |
|||
|
|||
_declaration.setScope(m_currentScope); |
|||
if (_opensScope) |
|||
enterNewSubScope(_declaration); |
|||
} |
|||
|
|||
ReferencesResolver::ReferencesResolver( |
|||
ASTNode& _root, |
|||
NameAndTypeResolver& _resolver, |
|||
ContractDefinition const* _currentContract, |
|||
ParameterList const* _returnParameters, |
|||
bool _resolveInsideCode, |
|||
bool _allowLazyTypes |
|||
): |
|||
m_resolver(_resolver), |
|||
m_currentContract(_currentContract), |
|||
m_returnParameters(_returnParameters), |
|||
m_resolveInsideCode(_resolveInsideCode), |
|||
m_allowLazyTypes(_allowLazyTypes) |
|||
{ |
|||
_root.accept(*this); |
|||
} |
|||
|
|||
void ReferencesResolver::endVisit(VariableDeclaration& _variable) |
|||
{ |
|||
// endVisit because the internal type needs resolving if it is a user defined type
|
|||
// or mapping
|
|||
if (_variable.getTypeName()) |
|||
{ |
|||
TypePointer type = _variable.getTypeName()->toType(); |
|||
using Location = VariableDeclaration::Location; |
|||
Location loc = _variable.referenceLocation(); |
|||
// References are forced to calldata for external function parameters (not return)
|
|||
// and memory for parameters (also return) of publicly visible functions.
|
|||
// They default to memory for function parameters and storage for local variables.
|
|||
if (auto ref = dynamic_cast<ReferenceType const*>(type.get())) |
|||
{ |
|||
if (_variable.isExternalCallableParameter()) |
|||
{ |
|||
// force location of external function parameters (not return) to calldata
|
|||
if (loc != Location::Default) |
|||
BOOST_THROW_EXCEPTION(_variable.createTypeError( |
|||
"Location has to be calldata for external functions " |
|||
"(remove the \"memory\" or \"storage\" keyword)." |
|||
)); |
|||
type = ref->copyForLocation(DataLocation::CallData, true); |
|||
} |
|||
else if (_variable.isCallableParameter() && _variable.getScope()->isPublic()) |
|||
{ |
|||
// force locations of public or external function (return) parameters to memory
|
|||
if (loc == VariableDeclaration::Location::Storage) |
|||
BOOST_THROW_EXCEPTION(_variable.createTypeError( |
|||
"Location has to be memory for publicly visible functions " |
|||
"(remove the \"storage\" keyword)." |
|||
)); |
|||
type = ref->copyForLocation(DataLocation::Memory, true); |
|||
} |
|||
else |
|||
{ |
|||
if (loc == Location::Default) |
|||
loc = _variable.isCallableParameter() ? Location::Memory : Location::Storage; |
|||
bool isPointer = !_variable.isStateVariable(); |
|||
type = ref->copyForLocation( |
|||
loc == Location::Memory ? |
|||
DataLocation::Memory : |
|||
DataLocation::Storage, |
|||
isPointer |
|||
); |
|||
} |
|||
} |
|||
else if (loc != Location::Default && !ref) |
|||
BOOST_THROW_EXCEPTION(_variable.createTypeError( |
|||
"Storage location can only be given for array or struct types." |
|||
)); |
|||
|
|||
_variable.setType(type); |
|||
|
|||
if (!_variable.getType()) |
|||
BOOST_THROW_EXCEPTION(_variable.getTypeName()->createTypeError("Invalid type name")); |
|||
} |
|||
else if (!m_allowLazyTypes) |
|||
BOOST_THROW_EXCEPTION(_variable.createTypeError("Explicit type needed.")); |
|||
// otherwise we have a "var"-declaration whose type is resolved by the first assignment
|
|||
} |
|||
|
|||
bool ReferencesResolver::visit(Return& _return) |
|||
{ |
|||
_return.setFunctionReturnParameters(m_returnParameters); |
|||
return true; |
|||
} |
|||
|
|||
bool ReferencesResolver::visit(Mapping&) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
bool ReferencesResolver::visit(UserDefinedTypeName& _typeName) |
|||
{ |
|||
auto declarations = m_resolver.getNameFromCurrentScope(_typeName.getName()); |
|||
if (declarations.empty()) |
|||
BOOST_THROW_EXCEPTION( |
|||
DeclarationError() << |
|||
errinfo_sourceLocation(_typeName.getLocation()) << |
|||
errinfo_comment("Undeclared identifier.") |
|||
); |
|||
else if (declarations.size() > 1) |
|||
BOOST_THROW_EXCEPTION( |
|||
DeclarationError() << |
|||
errinfo_sourceLocation(_typeName.getLocation()) << |
|||
errinfo_comment("Duplicate identifier.") |
|||
); |
|||
else |
|||
_typeName.setReferencedDeclaration(**declarations.begin()); |
|||
return false; |
|||
} |
|||
|
|||
bool ReferencesResolver::visit(Identifier& _identifier) |
|||
{ |
|||
auto declarations = m_resolver.getNameFromCurrentScope(_identifier.getName()); |
|||
if (declarations.empty()) |
|||
BOOST_THROW_EXCEPTION( |
|||
DeclarationError() << |
|||
errinfo_sourceLocation(_identifier.getLocation()) << |
|||
errinfo_comment("Undeclared identifier.") |
|||
); |
|||
else if (declarations.size() == 1) |
|||
_identifier.setReferencedDeclaration(*declarations.front(), m_currentContract); |
|||
else |
|||
_identifier.setOverloadedDeclarations(m_resolver.cleanedDeclarations(_identifier, declarations)); |
|||
return false; |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,160 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Parser part that determines the declarations corresponding to names and the types of expressions. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <list> |
|||
#include <boost/noncopyable.hpp> |
|||
|
|||
#include <libsolidity/DeclarationContainer.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
/**
|
|||
* Resolves name references, types and checks types of all expressions. |
|||
* Specifically, it checks that all operations are valid for the inferred types. |
|||
* An exception is throw on the first error. |
|||
*/ |
|||
class NameAndTypeResolver: private boost::noncopyable |
|||
{ |
|||
public: |
|||
explicit NameAndTypeResolver(std::vector<Declaration const*> const& _globals); |
|||
/// Registers all declarations found in the source unit.
|
|||
void registerDeclarations(SourceUnit& _sourceUnit); |
|||
/// Resolves all names and types referenced from the given contract.
|
|||
void resolveNamesAndTypes(ContractDefinition& _contract); |
|||
/// Check all type requirements in the given contract.
|
|||
void checkTypeRequirements(ContractDefinition& _contract); |
|||
/// Updates the given global declaration (used for "this"). Not to be used with declarations
|
|||
/// that create their own scope.
|
|||
void updateDeclaration(Declaration const& _declaration); |
|||
|
|||
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
|
|||
/// the global scope is used (i.e. the one containing only the contract).
|
|||
/// @returns a pointer to the declaration on success or nullptr on failure.
|
|||
std::vector<Declaration const*> resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const; |
|||
|
|||
/// Resolves a name in the "current" scope. Should only be called during the initial
|
|||
/// resolving phase.
|
|||
std::vector<Declaration const*> getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); |
|||
|
|||
/// returns the vector of declarations without repetitions
|
|||
static std::vector<Declaration const*> cleanedDeclarations( |
|||
Identifier const& _identifier, |
|||
std::vector<Declaration const*> const& _declarations |
|||
); |
|||
|
|||
private: |
|||
void reset(); |
|||
|
|||
/// Imports all members declared directly in the given contract (i.e. does not import inherited members)
|
|||
/// into the current scope if they are not present already.
|
|||
void importInheritedScope(ContractDefinition const& _base); |
|||
|
|||
/// Computes "C3-Linearization" of base contracts and stores it inside the contract.
|
|||
void linearizeBaseContracts(ContractDefinition& _contract) const; |
|||
/// Computes the C3-merge of the given list of lists of bases.
|
|||
/// @returns the linearized vector or an empty vector if linearization is not possible.
|
|||
template <class _T> |
|||
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge); |
|||
|
|||
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
|
|||
/// where nullptr denotes the global scope. Note that structs are not scope since they do
|
|||
/// not contain code.
|
|||
std::map<ASTNode const*, DeclarationContainer> m_scopes; |
|||
|
|||
DeclarationContainer* m_currentScope = nullptr; |
|||
}; |
|||
|
|||
/**
|
|||
* Traverses the given AST upon construction and fills _scopes with all declarations inside the |
|||
* AST. |
|||
*/ |
|||
class DeclarationRegistrationHelper: private ASTVisitor |
|||
{ |
|||
public: |
|||
DeclarationRegistrationHelper(std::map<ASTNode const*, DeclarationContainer>& _scopes, ASTNode& _astRoot); |
|||
|
|||
private: |
|||
bool visit(ContractDefinition& _contract) override; |
|||
void endVisit(ContractDefinition& _contract) override; |
|||
bool visit(StructDefinition& _struct) override; |
|||
void endVisit(StructDefinition& _struct) override; |
|||
bool visit(EnumDefinition& _enum) override; |
|||
void endVisit(EnumDefinition& _enum) override; |
|||
bool visit(EnumValue& _value) override; |
|||
bool visit(FunctionDefinition& _function) override; |
|||
void endVisit(FunctionDefinition& _function) override; |
|||
bool visit(ModifierDefinition& _modifier) override; |
|||
void endVisit(ModifierDefinition& _modifier) override; |
|||
void endVisit(VariableDeclarationStatement& _variableDeclarationStatement) override; |
|||
bool visit(VariableDeclaration& _declaration) override; |
|||
bool visit(EventDefinition& _event) override; |
|||
void endVisit(EventDefinition& _event) override; |
|||
|
|||
void enterNewSubScope(Declaration const& _declaration); |
|||
void closeCurrentScope(); |
|||
void registerDeclaration(Declaration& _declaration, bool _opensScope); |
|||
|
|||
std::map<ASTNode const*, DeclarationContainer>& m_scopes; |
|||
Declaration const* m_currentScope; |
|||
VariableScope* m_currentFunction; |
|||
}; |
|||
|
|||
/**
|
|||
* Resolves references to declarations (of variables and types) and also establishes the link |
|||
* between a return statement and the return parameter list. |
|||
*/ |
|||
class ReferencesResolver: private ASTVisitor |
|||
{ |
|||
public: |
|||
ReferencesResolver( |
|||
ASTNode& _root, |
|||
NameAndTypeResolver& _resolver, |
|||
ContractDefinition const* _currentContract, |
|||
ParameterList const* _returnParameters, |
|||
bool _resolveInsideCode = false, |
|||
bool _allowLazyTypes = true |
|||
); |
|||
|
|||
private: |
|||
virtual void endVisit(VariableDeclaration& _variable) override; |
|||
virtual bool visit(Block&) override { return m_resolveInsideCode; } |
|||
virtual bool visit(Identifier& _identifier) override; |
|||
virtual bool visit(UserDefinedTypeName& _typeName) override; |
|||
virtual bool visit(Mapping&) override; |
|||
virtual bool visit(Return& _return) override; |
|||
|
|||
NameAndTypeResolver& m_resolver; |
|||
ContractDefinition const* m_currentContract; |
|||
ParameterList const* m_returnParameters; |
|||
bool m_resolveInsideCode; |
|||
bool m_allowLazyTypes; |
|||
}; |
|||
|
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,148 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity parser. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include "libsolidity/AST.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class Scanner; |
|||
|
|||
class Parser |
|||
{ |
|||
public: |
|||
Parser() {} |
|||
|
|||
ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner); |
|||
std::shared_ptr<std::string const> const& getSourceName() const; |
|||
|
|||
private: |
|||
class ASTNodeFactory; |
|||
|
|||
/// Start position of the current token
|
|||
int getPosition() const; |
|||
/// End position of the current token
|
|||
int getEndPosition() const; |
|||
|
|||
struct VarDeclParserOptions |
|||
{ |
|||
VarDeclParserOptions() {} |
|||
bool allowVar = false; |
|||
bool isStateVariable = false; |
|||
bool allowIndexed = false; |
|||
bool allowEmptyName = false; |
|||
bool allowInitialValue = false; |
|||
bool allowLocationSpecifier = false; |
|||
}; |
|||
|
|||
///@{
|
|||
///@name Parsing functions for the AST nodes
|
|||
ASTPointer<ImportDirective> parseImportDirective(); |
|||
ASTPointer<ContractDefinition> parseContractDefinition(); |
|||
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); |
|||
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); |
|||
ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); |
|||
ASTPointer<StructDefinition> parseStructDefinition(); |
|||
ASTPointer<EnumDefinition> parseEnumDefinition(); |
|||
ASTPointer<EnumValue> parseEnumValue(); |
|||
ASTPointer<VariableDeclaration> parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions(), |
|||
ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>()); |
|||
ASTPointer<ModifierDefinition> parseModifierDefinition(); |
|||
ASTPointer<EventDefinition> parseEventDefinition(); |
|||
ASTPointer<ModifierInvocation> parseModifierInvocation(); |
|||
ASTPointer<Identifier> parseIdentifier(); |
|||
ASTPointer<TypeName> parseTypeName(bool _allowVar); |
|||
ASTPointer<Mapping> parseMapping(); |
|||
ASTPointer<ParameterList> parseParameterList( |
|||
VarDeclParserOptions const& _options, |
|||
bool _allowEmpty = true |
|||
); |
|||
ASTPointer<Block> parseBlock(); |
|||
ASTPointer<Statement> parseStatement(); |
|||
ASTPointer<IfStatement> parseIfStatement(); |
|||
ASTPointer<WhileStatement> parseWhileStatement(); |
|||
ASTPointer<ForStatement> parseForStatement(); |
|||
/// A "simple statement" can be a variable declaration statement or an expression statement.
|
|||
ASTPointer<Statement> parseSimpleStatement(); |
|||
ASTPointer<VariableDeclarationStatement> parseVariableDeclarationStatement( |
|||
ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>()); |
|||
ASTPointer<ExpressionStatement> parseExpressionStatement( |
|||
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()); |
|||
ASTPointer<Expression> parseExpression( |
|||
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()); |
|||
ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4, |
|||
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()); |
|||
ASTPointer<Expression> parseUnaryExpression( |
|||
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()); |
|||
ASTPointer<Expression> parseLeftHandSideExpression( |
|||
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()); |
|||
ASTPointer<Expression> parsePrimaryExpression(); |
|||
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments(); |
|||
std::pair<std::vector<ASTPointer<Expression>>, std::vector<ASTPointer<ASTString>>> parseFunctionCallArguments(); |
|||
///@}
|
|||
|
|||
///@{
|
|||
///@name Helper functions
|
|||
|
|||
/// Used as return value of @see peekStatementType.
|
|||
enum class LookAheadInfo |
|||
{ |
|||
IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement |
|||
}; |
|||
|
|||
/// Performs limited look-ahead to distinguish between variable declaration and expression statement.
|
|||
/// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to
|
|||
/// decide with constant look-ahead.
|
|||
LookAheadInfo peekStatementType() const; |
|||
/// Returns a typename parsed in look-ahead fashion from something like "a[8][2**70]".
|
|||
ASTPointer<TypeName> typeNameIndexAccessStructure( |
|||
ASTPointer<PrimaryExpression> const& _primary, |
|||
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices); |
|||
/// Returns an expression parsed in look-ahead fashion from something like "a[8][2**70]".
|
|||
ASTPointer<Expression> expressionFromIndexAccessStructure( |
|||
ASTPointer<PrimaryExpression> const& _primary, |
|||
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices); |
|||
/// If current token value is not _value, throw exception otherwise advance token.
|
|||
void expectToken(Token::Value _value); |
|||
Token::Value expectAssignmentOperator(); |
|||
ASTPointer<ASTString> expectIdentifierToken(); |
|||
ASTPointer<ASTString> getLiteralAndAdvance(); |
|||
///@}
|
|||
|
|||
/// Creates an empty ParameterList at the current location (used if parameters can be omitted).
|
|||
ASTPointer<ParameterList> createEmptyParameterList(); |
|||
|
|||
/// Creates a @ref ParserError exception and annotates it with the current position and the
|
|||
/// given @a _description.
|
|||
ParserError createParserError(std::string const& _description) const; |
|||
|
|||
std::shared_ptr<Scanner> m_scanner; |
|||
/// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier.
|
|||
bool m_insideModifier = false; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,769 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|||
This file is derived from the file "scanner.cc", which was part of the |
|||
V8 project. The original copyright header follows: |
|||
|
|||
Copyright 2006-2012, the V8 project authors. All rights reserved. |
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following |
|||
disclaimer in the documentation and/or other materials provided |
|||
with the distribution. |
|||
* Neither the name of Google Inc. nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity scanner. |
|||
*/ |
|||
|
|||
#include <algorithm> |
|||
#include <tuple> |
|||
#include <libsolidity/Utils.h> |
|||
#include <libsolidity/Scanner.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
bool isDecimalDigit(char c) |
|||
{ |
|||
return '0' <= c && c <= '9'; |
|||
} |
|||
bool isHexDigit(char c) |
|||
{ |
|||
return isDecimalDigit(c) |
|||
|| ('a' <= c && c <= 'f') |
|||
|| ('A' <= c && c <= 'F'); |
|||
} |
|||
bool isLineTerminator(char c) |
|||
{ |
|||
return c == '\n'; |
|||
} |
|||
bool isWhiteSpace(char c) |
|||
{ |
|||
return c == ' ' || c == '\n' || c == '\t' || c == '\r'; |
|||
} |
|||
bool isIdentifierStart(char c) |
|||
{ |
|||
return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); |
|||
} |
|||
bool isIdentifierPart(char c) |
|||
{ |
|||
return isIdentifierStart(c) || isDecimalDigit(c); |
|||
} |
|||
|
|||
int hexValue(char c) |
|||
{ |
|||
if (c >= '0' && c <= '9') |
|||
return c - '0'; |
|||
else if (c >= 'a' && c <= 'f') |
|||
return c - 'a' + 10; |
|||
else if (c >= 'A' && c <= 'F') |
|||
return c - 'A' + 10; |
|||
else return -1; |
|||
} |
|||
} // end anonymous namespace
|
|||
|
|||
|
|||
|
|||
/// Scoped helper for literal recording. Automatically drops the literal
|
|||
/// if aborting the scanning before it's complete.
|
|||
enum LiteralType { |
|||
LITERAL_TYPE_STRING, |
|||
LITERAL_TYPE_NUMBER, // not really different from string type in behaviour
|
|||
LITERAL_TYPE_COMMENT |
|||
}; |
|||
|
|||
class LiteralScope |
|||
{ |
|||
public: |
|||
explicit LiteralScope(Scanner* _self, enum LiteralType _type): m_type(_type) |
|||
, m_scanner(_self) |
|||
, m_complete(false) |
|||
{ |
|||
if (_type == LITERAL_TYPE_COMMENT) |
|||
m_scanner->m_nextSkippedComment.literal.clear(); |
|||
else |
|||
m_scanner->m_nextToken.literal.clear(); |
|||
} |
|||
~LiteralScope() |
|||
{ |
|||
if (!m_complete) |
|||
{ |
|||
if (m_type == LITERAL_TYPE_COMMENT) |
|||
m_scanner->m_nextSkippedComment.literal.clear(); |
|||
else |
|||
m_scanner->m_nextToken.literal.clear(); |
|||
} |
|||
} |
|||
void complete() { m_complete = true; } |
|||
|
|||
private: |
|||
enum LiteralType m_type; |
|||
Scanner* m_scanner; |
|||
bool m_complete; |
|||
}; // end of LiteralScope class
|
|||
|
|||
|
|||
void Scanner::reset(CharStream const& _source, string const& _sourceName) |
|||
{ |
|||
m_source = _source; |
|||
m_sourceName = make_shared<string const>(_sourceName); |
|||
reset(); |
|||
} |
|||
|
|||
void Scanner::reset() |
|||
{ |
|||
m_source.reset(); |
|||
m_char = m_source.get(); |
|||
skipWhitespace(); |
|||
scanToken(); |
|||
next(); |
|||
} |
|||
|
|||
bool Scanner::scanHexByte(char& o_scannedByte) |
|||
{ |
|||
char x = 0; |
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
int d = hexValue(m_char); |
|||
if (d < 0) |
|||
{ |
|||
rollback(i); |
|||
return false; |
|||
} |
|||
x = x * 16 + d; |
|||
advance(); |
|||
} |
|||
o_scannedByte = x; |
|||
return true; |
|||
} |
|||
|
|||
|
|||
// Ensure that tokens can be stored in a byte.
|
|||
BOOST_STATIC_ASSERT(Token::NUM_TOKENS <= 0x100); |
|||
|
|||
Token::Value Scanner::next() |
|||
{ |
|||
m_currentToken = m_nextToken; |
|||
m_skippedComment = m_nextSkippedComment; |
|||
scanToken(); |
|||
|
|||
return m_currentToken.token; |
|||
} |
|||
|
|||
Token::Value Scanner::selectToken(char _next, Token::Value _then, Token::Value _else) |
|||
{ |
|||
advance(); |
|||
if (m_char == _next) |
|||
return selectToken(_then); |
|||
else |
|||
return _else; |
|||
} |
|||
|
|||
bool Scanner::skipWhitespace() |
|||
{ |
|||
int const startPosition = getSourcePos(); |
|||
while (isWhiteSpace(m_char)) |
|||
advance(); |
|||
// Return whether or not we skipped any characters.
|
|||
return getSourcePos() != startPosition; |
|||
} |
|||
|
|||
bool Scanner::skipWhitespaceExceptLF() |
|||
{ |
|||
int const startPosition = getSourcePos(); |
|||
while (isWhiteSpace(m_char) && !isLineTerminator(m_char)) |
|||
advance(); |
|||
// Return whether or not we skipped any characters.
|
|||
return getSourcePos() != startPosition; |
|||
} |
|||
|
|||
Token::Value Scanner::skipSingleLineComment() |
|||
{ |
|||
// The line terminator at the end of the line is not considered
|
|||
// to be part of the single-line comment; it is recognized
|
|||
// separately by the lexical grammar and becomes part of the
|
|||
// stream of input elements for the syntactic grammar
|
|||
while (advance() && !isLineTerminator(m_char)) { }; |
|||
return Token::Whitespace; |
|||
} |
|||
|
|||
Token::Value Scanner::scanSingleLineDocComment() |
|||
{ |
|||
LiteralScope literal(this, LITERAL_TYPE_COMMENT); |
|||
advance(); //consume the last '/' at ///
|
|||
skipWhitespaceExceptLF(); |
|||
while (!isSourcePastEndOfInput()) |
|||
{ |
|||
if (isLineTerminator(m_char)) |
|||
{ |
|||
// check if next line is also a documentation comment
|
|||
skipWhitespace(); |
|||
if (!m_source.isPastEndOfInput(3) && |
|||
m_source.get(0) == '/' && |
|||
m_source.get(1) == '/' && |
|||
m_source.get(2) == '/') |
|||
{ |
|||
addCommentLiteralChar('\n'); |
|||
m_char = m_source.advanceAndGet(3); |
|||
} |
|||
else |
|||
break; // next line is not a documentation comment, we are done
|
|||
|
|||
} |
|||
addCommentLiteralChar(m_char); |
|||
advance(); |
|||
} |
|||
literal.complete(); |
|||
return Token::CommentLiteral; |
|||
} |
|||
|
|||
Token::Value Scanner::skipMultiLineComment() |
|||
{ |
|||
advance(); |
|||
while (!isSourcePastEndOfInput()) |
|||
{ |
|||
char ch = m_char; |
|||
advance(); |
|||
|
|||
// If we have reached the end of the multi-line comment, we
|
|||
// consume the '/' and insert a whitespace. This way all
|
|||
// multi-line comments are treated as whitespace.
|
|||
if (ch == '*' && m_char == '/') |
|||
{ |
|||
m_char = ' '; |
|||
return Token::Whitespace; |
|||
} |
|||
} |
|||
// Unterminated multi-line comment.
|
|||
return Token::Illegal; |
|||
} |
|||
|
|||
Token::Value Scanner::scanMultiLineDocComment() |
|||
{ |
|||
LiteralScope literal(this, LITERAL_TYPE_COMMENT); |
|||
bool endFound = false; |
|||
bool charsAdded = false; |
|||
|
|||
while (!isSourcePastEndOfInput()) |
|||
{ |
|||
//handle newlines in multline comments
|
|||
if (isLineTerminator(m_char)) |
|||
{ |
|||
skipWhitespace(); |
|||
if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/') |
|||
{ // skip first '*' in subsequent lines
|
|||
if (charsAdded) |
|||
addCommentLiteralChar('\n'); |
|||
m_char = m_source.advanceAndGet(2); |
|||
} |
|||
else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') |
|||
{ // if after newline the comment ends, don't insert the newline
|
|||
m_char = m_source.advanceAndGet(2); |
|||
endFound = true; |
|||
break; |
|||
} |
|||
else if (charsAdded) |
|||
addCommentLiteralChar('\n'); |
|||
} |
|||
|
|||
if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') |
|||
{ |
|||
m_char = m_source.advanceAndGet(2); |
|||
endFound = true; |
|||
break; |
|||
} |
|||
addCommentLiteralChar(m_char); |
|||
charsAdded = true; |
|||
advance(); |
|||
} |
|||
literal.complete(); |
|||
if (!endFound) |
|||
return Token::Illegal; |
|||
else |
|||
return Token::CommentLiteral; |
|||
} |
|||
|
|||
Token::Value Scanner::scanSlash() |
|||
{ |
|||
int firstSlashPosition = getSourcePos(); |
|||
advance(); |
|||
if (m_char == '/') |
|||
{ |
|||
if (!advance()) /* double slash comment directly before EOS */ |
|||
return Token::Whitespace; |
|||
else if (m_char == '/') |
|||
{ |
|||
// doxygen style /// comment
|
|||
Token::Value comment; |
|||
m_nextSkippedComment.location.start = firstSlashPosition; |
|||
comment = scanSingleLineDocComment(); |
|||
m_nextSkippedComment.location.end = getSourcePos(); |
|||
m_nextSkippedComment.token = comment; |
|||
return Token::Whitespace; |
|||
} |
|||
else |
|||
return skipSingleLineComment(); |
|||
} |
|||
else if (m_char == '*') |
|||
{ |
|||
// doxygen style /** natspec comment
|
|||
if (!advance()) /* slash star comment before EOS */ |
|||
return Token::Whitespace; |
|||
else if (m_char == '*') |
|||
{ |
|||
advance(); //consume the last '*' at /**
|
|||
skipWhitespaceExceptLF(); |
|||
|
|||
// special case of a closed normal multiline comment
|
|||
if (!m_source.isPastEndOfInput() && m_source.get(0) == '/') |
|||
advance(); //skip the closing slash
|
|||
else // we actually have a multiline documentation comment
|
|||
{ |
|||
Token::Value comment; |
|||
m_nextSkippedComment.location.start = firstSlashPosition; |
|||
comment = scanMultiLineDocComment(); |
|||
m_nextSkippedComment.location.end = getSourcePos(); |
|||
m_nextSkippedComment.token = comment; |
|||
} |
|||
return Token::Whitespace; |
|||
} |
|||
else |
|||
return skipMultiLineComment(); |
|||
} |
|||
else if (m_char == '=') |
|||
return selectToken(Token::AssignDiv); |
|||
else |
|||
return Token::Div; |
|||
} |
|||
|
|||
void Scanner::scanToken() |
|||
{ |
|||
m_nextToken.literal.clear(); |
|||
m_nextSkippedComment.literal.clear(); |
|||
Token::Value token; |
|||
do |
|||
{ |
|||
// Remember the position of the next token
|
|||
m_nextToken.location.start = getSourcePos(); |
|||
switch (m_char) |
|||
{ |
|||
case '\n': // fall-through
|
|||
case ' ': |
|||
case '\t': |
|||
token = selectToken(Token::Whitespace); |
|||
break; |
|||
case '"': |
|||
case '\'': |
|||
token = scanString(); |
|||
break; |
|||
case '<': |
|||
// < <= << <<=
|
|||
advance(); |
|||
if (m_char == '=') |
|||
token = selectToken(Token::LessThanOrEqual); |
|||
else if (m_char == '<') |
|||
token = selectToken('=', Token::AssignShl, Token::SHL); |
|||
else |
|||
token = Token::LessThan; |
|||
break; |
|||
case '>': |
|||
// > >= >> >>= >>> >>>=
|
|||
advance(); |
|||
if (m_char == '=') |
|||
token = selectToken(Token::GreaterThanOrEqual); |
|||
else if (m_char == '>') |
|||
{ |
|||
// >> >>= >>> >>>=
|
|||
advance(); |
|||
if (m_char == '=') |
|||
token = selectToken(Token::AssignSar); |
|||
else if (m_char == '>') |
|||
token = selectToken('=', Token::AssignShr, Token::SHR); |
|||
else |
|||
token = Token::SAR; |
|||
} |
|||
else |
|||
token = Token::GreaterThan; |
|||
break; |
|||
case '=': |
|||
// = == =>
|
|||
advance(); |
|||
if (m_char == '=') |
|||
token = selectToken(Token::Equal); |
|||
else if (m_char == '>') |
|||
token = selectToken(Token::Arrow); |
|||
else |
|||
token = Token::Assign; |
|||
break; |
|||
case '!': |
|||
// ! !=
|
|||
advance(); |
|||
if (m_char == '=') |
|||
token = selectToken(Token::NotEqual); |
|||
else |
|||
token = Token::Not; |
|||
break; |
|||
case '+': |
|||
// + ++ +=
|
|||
advance(); |
|||
if (m_char == '+') |
|||
token = selectToken(Token::Inc); |
|||
else if (m_char == '=') |
|||
token = selectToken(Token::AssignAdd); |
|||
else |
|||
token = Token::Add; |
|||
break; |
|||
case '-': |
|||
// - -- -=
|
|||
advance(); |
|||
if (m_char == '-') |
|||
token = selectToken(Token::Dec); |
|||
else if (m_char == '=') |
|||
token = selectToken(Token::AssignSub); |
|||
else |
|||
token = Token::Sub; |
|||
break; |
|||
case '*': |
|||
// * ** *=
|
|||
advance(); |
|||
if (m_char == '*') |
|||
token = selectToken(Token::Exp); |
|||
else if (m_char == '=') |
|||
token = selectToken(Token::AssignMul); |
|||
else |
|||
token = Token::Mul; |
|||
break; |
|||
case '%': |
|||
// % %=
|
|||
token = selectToken('=', Token::AssignMod, Token::Mod); |
|||
break; |
|||
case '/': |
|||
// / // /* /=
|
|||
token = scanSlash(); |
|||
break; |
|||
case '&': |
|||
// & && &=
|
|||
advance(); |
|||
if (m_char == '&') |
|||
token = selectToken(Token::And); |
|||
else if (m_char == '=') |
|||
token = selectToken(Token::AssignBitAnd); |
|||
else |
|||
token = Token::BitAnd; |
|||
break; |
|||
case '|': |
|||
// | || |=
|
|||
advance(); |
|||
if (m_char == '|') |
|||
token = selectToken(Token::Or); |
|||
else if (m_char == '=') |
|||
token = selectToken(Token::AssignBitOr); |
|||
else |
|||
token = Token::BitOr; |
|||
break; |
|||
case '^': |
|||
// ^ ^=
|
|||
token = selectToken('=', Token::AssignBitXor, Token::BitXor); |
|||
break; |
|||
case '.': |
|||
// . Number
|
|||
advance(); |
|||
if (isDecimalDigit(m_char)) |
|||
token = scanNumber('.'); |
|||
else |
|||
token = Token::Period; |
|||
break; |
|||
case ':': |
|||
token = selectToken(Token::Colon); |
|||
break; |
|||
case ';': |
|||
token = selectToken(Token::Semicolon); |
|||
break; |
|||
case ',': |
|||
token = selectToken(Token::Comma); |
|||
break; |
|||
case '(': |
|||
token = selectToken(Token::LParen); |
|||
break; |
|||
case ')': |
|||
token = selectToken(Token::RParen); |
|||
break; |
|||
case '[': |
|||
token = selectToken(Token::LBrack); |
|||
break; |
|||
case ']': |
|||
token = selectToken(Token::RBrack); |
|||
break; |
|||
case '{': |
|||
token = selectToken(Token::LBrace); |
|||
break; |
|||
case '}': |
|||
token = selectToken(Token::RBrace); |
|||
break; |
|||
case '?': |
|||
token = selectToken(Token::Conditional); |
|||
break; |
|||
case '~': |
|||
token = selectToken(Token::BitNot); |
|||
break; |
|||
default: |
|||
if (isIdentifierStart(m_char)) |
|||
token = scanIdentifierOrKeyword(); |
|||
else if (isDecimalDigit(m_char)) |
|||
token = scanNumber(); |
|||
else if (skipWhitespace()) |
|||
token = Token::Whitespace; |
|||
else if (isSourcePastEndOfInput()) |
|||
token = Token::EOS; |
|||
else |
|||
token = selectToken(Token::Illegal); |
|||
break; |
|||
} |
|||
// Continue scanning for tokens as long as we're just skipping
|
|||
// whitespace.
|
|||
} |
|||
while (token == Token::Whitespace); |
|||
m_nextToken.location.end = getSourcePos(); |
|||
m_nextToken.token = token; |
|||
} |
|||
|
|||
bool Scanner::scanEscape() |
|||
{ |
|||
char c = m_char; |
|||
advance(); |
|||
// Skip escaped newlines.
|
|||
if (isLineTerminator(c)) |
|||
return true; |
|||
switch (c) |
|||
{ |
|||
case '\'': // fall through
|
|||
case '"': // fall through
|
|||
case '\\': |
|||
break; |
|||
case 'b': |
|||
c = '\b'; |
|||
break; |
|||
case 'f': |
|||
c = '\f'; |
|||
break; |
|||
case 'n': |
|||
c = '\n'; |
|||
break; |
|||
case 'r': |
|||
c = '\r'; |
|||
break; |
|||
case 't': |
|||
c = '\t'; |
|||
break; |
|||
case 'v': |
|||
c = '\v'; |
|||
break; |
|||
case 'x': |
|||
if (!scanHexByte(c)) |
|||
return false; |
|||
break; |
|||
} |
|||
|
|||
addLiteralChar(c); |
|||
return true; |
|||
} |
|||
|
|||
Token::Value Scanner::scanString() |
|||
{ |
|||
char const quote = m_char; |
|||
advance(); // consume quote
|
|||
LiteralScope literal(this, LITERAL_TYPE_STRING); |
|||
while (m_char != quote && !isSourcePastEndOfInput() && !isLineTerminator(m_char)) |
|||
{ |
|||
char c = m_char; |
|||
advance(); |
|||
if (c == '\\') |
|||
{ |
|||
if (isSourcePastEndOfInput() || !scanEscape()) |
|||
return Token::Illegal; |
|||
} |
|||
else |
|||
addLiteralChar(c); |
|||
} |
|||
if (m_char != quote) |
|||
return Token::Illegal; |
|||
literal.complete(); |
|||
advance(); // consume quote
|
|||
return Token::StringLiteral; |
|||
} |
|||
|
|||
void Scanner::scanDecimalDigits() |
|||
{ |
|||
while (isDecimalDigit(m_char)) |
|||
addLiteralCharAndAdvance(); |
|||
} |
|||
|
|||
Token::Value Scanner::scanNumber(char _charSeen) |
|||
{ |
|||
enum { DECIMAL, HEX, BINARY } kind = DECIMAL; |
|||
LiteralScope literal(this, LITERAL_TYPE_NUMBER); |
|||
if (_charSeen == '.') |
|||
{ |
|||
// we have already seen a decimal point of the float
|
|||
addLiteralChar('.'); |
|||
scanDecimalDigits(); // we know we have at least one digit
|
|||
} |
|||
else |
|||
{ |
|||
solAssert(_charSeen == 0, ""); |
|||
// if the first character is '0' we must check for octals and hex
|
|||
if (m_char == '0') |
|||
{ |
|||
addLiteralCharAndAdvance(); |
|||
// either 0, 0exxx, 0Exxx, 0.xxx or a hex number
|
|||
if (m_char == 'x' || m_char == 'X') |
|||
{ |
|||
// hex number
|
|||
kind = HEX; |
|||
addLiteralCharAndAdvance(); |
|||
if (!isHexDigit(m_char)) |
|||
return Token::Illegal; // we must have at least one hex digit after 'x'/'X'
|
|||
while (isHexDigit(m_char)) |
|||
addLiteralCharAndAdvance(); |
|||
} |
|||
} |
|||
// Parse decimal digits and allow trailing fractional part.
|
|||
if (kind == DECIMAL) |
|||
{ |
|||
scanDecimalDigits(); // optional
|
|||
if (m_char == '.') |
|||
{ |
|||
addLiteralCharAndAdvance(); |
|||
scanDecimalDigits(); // optional
|
|||
} |
|||
} |
|||
} |
|||
// scan exponent, if any
|
|||
if (m_char == 'e' || m_char == 'E') |
|||
{ |
|||
solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); |
|||
if (kind != DECIMAL) |
|||
return Token::Illegal; |
|||
// scan exponent
|
|||
addLiteralCharAndAdvance(); |
|||
if (m_char == '+' || m_char == '-') |
|||
addLiteralCharAndAdvance(); |
|||
if (!isDecimalDigit(m_char)) |
|||
return Token::Illegal; // we must have at least one decimal digit after 'e'/'E'
|
|||
scanDecimalDigits(); |
|||
} |
|||
// The source character immediately following a numeric literal must
|
|||
// not be an identifier start or a decimal digit; see ECMA-262
|
|||
// section 7.8.3, page 17 (note that we read only one decimal digit
|
|||
// if the value is 0).
|
|||
if (isDecimalDigit(m_char) || isIdentifierStart(m_char)) |
|||
return Token::Illegal; |
|||
literal.complete(); |
|||
return Token::Number; |
|||
} |
|||
|
|||
Token::Value Scanner::scanIdentifierOrKeyword() |
|||
{ |
|||
solAssert(isIdentifierStart(m_char), ""); |
|||
LiteralScope literal(this, LITERAL_TYPE_STRING); |
|||
addLiteralCharAndAdvance(); |
|||
// Scan the rest of the identifier characters.
|
|||
while (isIdentifierPart(m_char)) |
|||
addLiteralCharAndAdvance(); |
|||
literal.complete(); |
|||
return Token::fromIdentifierOrKeyword(m_nextToken.literal); |
|||
} |
|||
|
|||
char CharStream::advanceAndGet(size_t _chars) |
|||
{ |
|||
if (isPastEndOfInput()) |
|||
return 0; |
|||
m_pos += _chars; |
|||
if (isPastEndOfInput()) |
|||
return 0; |
|||
return m_source[m_pos]; |
|||
} |
|||
|
|||
char CharStream::rollback(size_t _amount) |
|||
{ |
|||
solAssert(m_pos >= _amount, ""); |
|||
m_pos -= _amount; |
|||
return get(); |
|||
} |
|||
|
|||
string CharStream::getLineAtPosition(int _position) const |
|||
{ |
|||
// if _position points to \n, it returns the line before the \n
|
|||
using size_type = string::size_type; |
|||
size_type searchStart = min<size_type>(m_source.size(), _position); |
|||
if (searchStart > 0) |
|||
searchStart--; |
|||
size_type lineStart = m_source.rfind('\n', searchStart); |
|||
if (lineStart == string::npos) |
|||
lineStart = 0; |
|||
else |
|||
lineStart++; |
|||
return m_source.substr(lineStart, min(m_source.find('\n', lineStart), |
|||
m_source.size()) - lineStart); |
|||
} |
|||
|
|||
tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const |
|||
{ |
|||
using size_type = string::size_type; |
|||
size_type searchPosition = min<size_type>(m_source.size(), _position); |
|||
int lineNumber = count(m_source.begin(), m_source.begin() + searchPosition, '\n'); |
|||
size_type lineStart; |
|||
if (searchPosition == 0) |
|||
lineStart = 0; |
|||
else |
|||
{ |
|||
lineStart = m_source.rfind('\n', searchPosition - 1); |
|||
lineStart = lineStart == string::npos ? 0 : lineStart + 1; |
|||
} |
|||
return tuple<int, int>(lineNumber, searchPosition - lineStart); |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
@ -1,224 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|||
This file is derived from the file "scanner.h", which was part of the |
|||
V8 project. The original copyright header follows: |
|||
|
|||
Copyright 2006-2012, the V8 project authors. All rights reserved. |
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following |
|||
disclaimer in the documentation and/or other materials provided |
|||
with the distribution. |
|||
* Neither the name of Google Inc. nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity scanner. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libevmasm/SourceLocation.h> |
|||
#include <libsolidity/Token.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
|
|||
class AstRawString; |
|||
class AstValueFactory; |
|||
class ParserRecorder; |
|||
|
|||
class CharStream |
|||
{ |
|||
public: |
|||
CharStream(): m_pos(0) {} |
|||
explicit CharStream(std::string const& _source): m_source(_source), m_pos(0) {} |
|||
int getPos() const { return m_pos; } |
|||
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_pos + _charsForward) >= m_source.size(); } |
|||
char get(size_t _charsForward = 0) const { return m_source[m_pos + _charsForward]; } |
|||
char advanceAndGet(size_t _chars=1); |
|||
char rollback(size_t _amount); |
|||
|
|||
void reset() { m_pos = 0; } |
|||
|
|||
///@{
|
|||
///@name Error printing helper functions
|
|||
/// Functions that help pretty-printing parse errors
|
|||
/// Do only use in error cases, they are quite expensive.
|
|||
std::string getLineAtPosition(int _position) const; |
|||
std::tuple<int, int> translatePositionToLineColumn(int _position) const; |
|||
///@}
|
|||
|
|||
private: |
|||
std::string m_source; |
|||
size_t m_pos; |
|||
}; |
|||
|
|||
|
|||
|
|||
class Scanner |
|||
{ |
|||
friend class LiteralScope; |
|||
public: |
|||
|
|||
explicit Scanner(CharStream const& _source = CharStream(), std::string const& _sourceName = "") { reset(_source, _sourceName); } |
|||
|
|||
/// Resets the scanner as if newly constructed with _source and _sourceName as input.
|
|||
void reset(CharStream const& _source, std::string const& _sourceName); |
|||
/// Resets scanner to the start of input.
|
|||
void reset(); |
|||
|
|||
/// Returns the next token and advances input
|
|||
Token::Value next(); |
|||
|
|||
///@{
|
|||
///@name Information about the current token
|
|||
|
|||
/// Returns the current token
|
|||
Token::Value getCurrentToken() |
|||
{ |
|||
return m_currentToken.token; |
|||
} |
|||
|
|||
SourceLocation getCurrentLocation() const { return m_currentToken.location; } |
|||
std::string const& getCurrentLiteral() const { return m_currentToken.literal; } |
|||
///@}
|
|||
|
|||
///@{
|
|||
///@name Information about the current comment token
|
|||
|
|||
SourceLocation getCurrentCommentLocation() const { return m_skippedComment.location; } |
|||
std::string const& getCurrentCommentLiteral() const { return m_skippedComment.literal; } |
|||
/// Called by the parser during FunctionDefinition parsing to clear the current comment
|
|||
void clearCurrentCommentLiteral() { m_skippedComment.literal.clear(); } |
|||
|
|||
///@}
|
|||
|
|||
///@{
|
|||
///@name Information about the next token
|
|||
|
|||
/// Returns the next token without advancing input.
|
|||
Token::Value peekNextToken() const { return m_nextToken.token; } |
|||
SourceLocation peekLocation() const { return m_nextToken.location; } |
|||
std::string const& peekLiteral() const { return m_nextToken.literal; } |
|||
///@}
|
|||
|
|||
std::shared_ptr<std::string const> const& getSourceName() const { return m_sourceName; } |
|||
|
|||
///@{
|
|||
///@name Error printing helper functions
|
|||
/// Functions that help pretty-printing parse errors
|
|||
/// Do only use in error cases, they are quite expensive.
|
|||
std::string getLineAtPosition(int _position) const { return m_source.getLineAtPosition(_position); } |
|||
std::tuple<int, int> translatePositionToLineColumn(int _position) const { return m_source.translatePositionToLineColumn(_position); } |
|||
///@}
|
|||
|
|||
private: |
|||
/// Used for the current and look-ahead token and comments
|
|||
struct TokenDesc |
|||
{ |
|||
Token::Value token; |
|||
SourceLocation location; |
|||
std::string literal; |
|||
}; |
|||
|
|||
///@{
|
|||
///@name Literal buffer support
|
|||
inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); } |
|||
inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); } |
|||
inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } |
|||
///@}
|
|||
|
|||
bool advance() { m_char = m_source.advanceAndGet(); return !m_source.isPastEndOfInput(); } |
|||
void rollback(int _amount) { m_char = m_source.rollback(_amount); } |
|||
|
|||
inline Token::Value selectToken(Token::Value _tok) { advance(); return _tok; } |
|||
/// If the next character is _next, advance and return _then, otherwise return _else.
|
|||
inline Token::Value selectToken(char _next, Token::Value _then, Token::Value _else); |
|||
|
|||
bool scanHexByte(char& o_scannedByte); |
|||
|
|||
/// Scans a single Solidity token.
|
|||
void scanToken(); |
|||
|
|||
/// Skips all whitespace and @returns true if something was skipped.
|
|||
bool skipWhitespace(); |
|||
/// Skips all whitespace except Line feeds and returns true if something was skipped
|
|||
bool skipWhitespaceExceptLF(); |
|||
Token::Value skipSingleLineComment(); |
|||
Token::Value skipMultiLineComment(); |
|||
|
|||
void scanDecimalDigits(); |
|||
Token::Value scanNumber(char _charSeen = 0); |
|||
Token::Value scanIdentifierOrKeyword(); |
|||
|
|||
Token::Value scanString(); |
|||
Token::Value scanSingleLineDocComment(); |
|||
Token::Value scanMultiLineDocComment(); |
|||
/// Scans a slash '/' and depending on the characters returns the appropriate token
|
|||
Token::Value scanSlash(); |
|||
|
|||
/// Scans an escape-sequence which is part of a string and adds the
|
|||
/// decoded character to the current literal. Returns true if a pattern
|
|||
/// is scanned.
|
|||
bool scanEscape(); |
|||
|
|||
/// Return the current source position.
|
|||
int getSourcePos() { return m_source.getPos(); } |
|||
bool isSourcePastEndOfInput() { return m_source.isPastEndOfInput(); } |
|||
|
|||
TokenDesc m_skippedComment; // desc for current skipped comment
|
|||
TokenDesc m_nextSkippedComment; // desc for next skiped comment
|
|||
|
|||
TokenDesc m_currentToken; // desc for current token (as returned by Next())
|
|||
TokenDesc m_nextToken; // desc for next token (one token look-ahead)
|
|||
|
|||
CharStream m_source; |
|||
std::shared_ptr<std::string const> m_sourceName; |
|||
|
|||
/// one character look-ahead, equals 0 at end of input
|
|||
char m_char; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,126 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Formatting functions for errors referencing positions and locations in the source. |
|||
*/ |
|||
|
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
void SourceReferenceFormatter::printSourceLocation( |
|||
ostream& _stream, |
|||
SourceLocation const& _location, |
|||
Scanner const& _scanner |
|||
) |
|||
{ |
|||
int startLine; |
|||
int startColumn; |
|||
tie(startLine, startColumn) = _scanner.translatePositionToLineColumn(_location.start); |
|||
int endLine; |
|||
int endColumn; |
|||
tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end); |
|||
if (startLine == endLine) |
|||
{ |
|||
string line = _scanner.getLineAtPosition(_location.start); |
|||
_stream << line << endl; |
|||
for_each( |
|||
line.cbegin(), |
|||
line.cbegin() + startColumn, |
|||
[&_stream](char const& ch) { _stream << (ch == '\t' ? '\t' : ' '); } |
|||
); |
|||
_stream << "^"; |
|||
if (endColumn > startColumn + 2) |
|||
_stream << string(endColumn - startColumn - 2, '-'); |
|||
if (endColumn > startColumn + 1) |
|||
_stream << "^"; |
|||
_stream << endl; |
|||
} |
|||
else |
|||
_stream << |
|||
_scanner.getLineAtPosition(_location.start) << |
|||
endl << |
|||
string(startColumn, ' ') << |
|||
"^\n" << |
|||
"Spanning multiple lines.\n"; |
|||
} |
|||
|
|||
void SourceReferenceFormatter::printSourceName( |
|||
ostream& _stream, |
|||
SourceLocation const& _location, |
|||
Scanner const& _scanner |
|||
) |
|||
{ |
|||
int startLine; |
|||
int startColumn; |
|||
tie(startLine, startColumn) = _scanner.translatePositionToLineColumn(_location.start); |
|||
_stream << *_location.sourceName << ":" << (startLine + 1) << ":" << (startColumn + 1) << ": "; |
|||
} |
|||
|
|||
void SourceReferenceFormatter::printExceptionInformation( |
|||
ostream& _stream, |
|||
Exception const& _exception, |
|||
string const& _name, |
|||
CompilerStack const& _compiler |
|||
) |
|||
{ |
|||
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception); |
|||
auto secondarylocation = boost::get_error_info<errinfo_secondarySourceLocation>(_exception); |
|||
Scanner const* scanner = nullptr; |
|||
|
|||
if (location) |
|||
{ |
|||
scanner = &_compiler.getScanner(*location->sourceName); |
|||
printSourceName(_stream, *location, *scanner); |
|||
} |
|||
|
|||
_stream << _name; |
|||
if (string const* description = boost::get_error_info<errinfo_comment>(_exception)) |
|||
_stream << ": " << *description << endl; |
|||
|
|||
if (location) |
|||
{ |
|||
scanner = &_compiler.getScanner(*location->sourceName); |
|||
printSourceLocation(_stream, *location, *scanner); |
|||
} |
|||
|
|||
if (secondarylocation && !secondarylocation->infos.empty()) |
|||
{ |
|||
for (auto info: secondarylocation->infos) |
|||
{ |
|||
scanner = &_compiler.getScanner(*info.second.sourceName); |
|||
_stream << info.first << " "; |
|||
printSourceName(_stream, info.second, *scanner); |
|||
_stream << endl; |
|||
printSourceLocation(_stream, info.second, *scanner); |
|||
} |
|||
_stream << endl; |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,54 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Formatting functions for errors referencing positions and locations in the source. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <libevmasm/SourceLocation.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
struct Exception; // forward
|
|||
|
|||
namespace solidity |
|||
{ |
|||
|
|||
class Scanner; // forward
|
|||
class CompilerStack; // forward
|
|||
|
|||
struct SourceReferenceFormatter |
|||
{ |
|||
public: |
|||
static void printSourceLocation(std::ostream& _stream, SourceLocation const& _location, Scanner const& _scanner); |
|||
static void printExceptionInformation( |
|||
std::ostream& _stream, |
|||
Exception const& _exception, |
|||
std::string const& _name, |
|||
CompilerStack const& _compiler |
|||
); |
|||
private: |
|||
static void printSourceName(std::ostream& _stream, SourceLocation const& _location, Scanner const& _scanner); |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,100 +0,0 @@ |
|||
// Copyright 2006-2012, the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//
|
|||
// Modifications as part of cpp-ethereum under the following license:
|
|||
//
|
|||
// cpp-ethereum 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.
|
|||
//
|
|||
// cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|||
#include <map> |
|||
#include <libsolidity/Token.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
#define T(name, string, precedence) #name, |
|||
char const* const Token::m_name[NUM_TOKENS] = |
|||
{ |
|||
TOKEN_LIST(T, T) |
|||
}; |
|||
#undef T |
|||
|
|||
|
|||
#define T(name, string, precedence) string, |
|||
char const* const Token::m_string[NUM_TOKENS] = |
|||
{ |
|||
TOKEN_LIST(T, T) |
|||
}; |
|||
#undef T |
|||
|
|||
|
|||
#define T(name, string, precedence) precedence, |
|||
int8_t const Token::m_precedence[NUM_TOKENS] = |
|||
{ |
|||
TOKEN_LIST(T, T) |
|||
}; |
|||
#undef T |
|||
|
|||
|
|||
#define KT(a, b, c) 'T', |
|||
#define KK(a, b, c) 'K', |
|||
char const Token::m_tokenType[] = |
|||
{ |
|||
TOKEN_LIST(KT, KK) |
|||
}; |
|||
Token::Value Token::fromIdentifierOrKeyword(const std::string& _name) |
|||
{ |
|||
// The following macros are used inside TOKEN_LIST and cause non-keyword tokens to be ignored
|
|||
// and keywords to be put inside the keywords variable.
|
|||
#define KEYWORD(name, string, precedence) {string, Token::name}, |
|||
#define TOKEN(name, string, precedence) |
|||
static const map<string, Token::Value> keywords({TOKEN_LIST(TOKEN, KEYWORD)}); |
|||
#undef KEYWORD |
|||
#undef TOKEN |
|||
auto it = keywords.find(_name); |
|||
return it == keywords.end() ? Token::Identifier : it->second; |
|||
} |
|||
|
|||
#undef KT |
|||
#undef KK |
|||
|
|||
} |
|||
} |
@ -1,406 +0,0 @@ |
|||
// Copyright 2006-2012, the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//
|
|||
// Modifications as part of cpp-ethereum under the following license:
|
|||
//
|
|||
// cpp-ethereum 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.
|
|||
//
|
|||
// cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libsolidity/Utils.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libdevcore/UndefMacros.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
// TOKEN_LIST takes a list of 3 macros M, all of which satisfy the
|
|||
// same signature M(name, string, precedence), where name is the
|
|||
// symbolic token name, string is the corresponding syntactic symbol
|
|||
// (or NULL, for literals), and precedence is the precedence (or 0).
|
|||
// The parameters are invoked for token categories as follows:
|
|||
//
|
|||
// T: Non-keyword tokens
|
|||
// K: Keyword tokens
|
|||
|
|||
// IGNORE_TOKEN is a convenience macro that can be supplied as
|
|||
// an argument (at any position) for a TOKEN_LIST call. It does
|
|||
// nothing with tokens belonging to the respective category.
|
|||
|
|||
#define IGNORE_TOKEN(name, string, precedence) |
|||
|
|||
#define TOKEN_LIST(T, K) \ |
|||
/* End of source indicator. */ \ |
|||
T(EOS, "EOS", 0) \ |
|||
\ |
|||
/* Punctuators (ECMA-262, section 7.7, page 15). */ \ |
|||
T(LParen, "(", 0) \ |
|||
T(RParen, ")", 0) \ |
|||
T(LBrack, "[", 0) \ |
|||
T(RBrack, "]", 0) \ |
|||
T(LBrace, "{", 0) \ |
|||
T(RBrace, "}", 0) \ |
|||
T(Colon, ":", 0) \ |
|||
T(Semicolon, ";", 0) \ |
|||
T(Period, ".", 0) \ |
|||
T(Conditional, "?", 3) \ |
|||
T(Arrow, "=>", 0) \ |
|||
\ |
|||
/* Assignment operators. */ \ |
|||
/* IsAssignmentOp() relies on this block of enum values being */ \ |
|||
/* contiguous and sorted in the same order!*/ \ |
|||
T(Assign, "=", 2) \ |
|||
/* The following have to be in exactly the same order as the simple binary operators*/ \ |
|||
T(AssignBitOr, "|=", 2) \ |
|||
T(AssignBitXor, "^=", 2) \ |
|||
T(AssignBitAnd, "&=", 2) \ |
|||
T(AssignShl, "<<=", 2) \ |
|||
T(AssignSar, ">>=", 2) \ |
|||
T(AssignShr, ">>>=", 2) \ |
|||
T(AssignAdd, "+=", 2) \ |
|||
T(AssignSub, "-=", 2) \ |
|||
T(AssignMul, "*=", 2) \ |
|||
T(AssignDiv, "/=", 2) \ |
|||
T(AssignMod, "%=", 2) \ |
|||
\ |
|||
/* Binary operators sorted by precedence. */ \ |
|||
/* IsBinaryOp() relies on this block of enum values */ \ |
|||
/* being contiguous and sorted in the same order! */ \ |
|||
T(Comma, ",", 1) \ |
|||
T(Or, "||", 4) \ |
|||
T(And, "&&", 5) \ |
|||
T(BitOr, "|", 8) \ |
|||
T(BitXor, "^", 9) \ |
|||
T(BitAnd, "&", 10) \ |
|||
T(SHL, "<<", 11) \ |
|||
T(SAR, ">>", 11) \ |
|||
T(SHR, ">>>", 11) \ |
|||
T(Add, "+", 12) \ |
|||
T(Sub, "-", 12) \ |
|||
T(Mul, "*", 13) \ |
|||
T(Div, "/", 13) \ |
|||
T(Mod, "%", 13) \ |
|||
T(Exp, "**", 14) \ |
|||
\ |
|||
/* Compare operators sorted by precedence. */ \ |
|||
/* IsCompareOp() relies on this block of enum values */ \ |
|||
/* being contiguous and sorted in the same order! */ \ |
|||
T(Equal, "==", 6) \ |
|||
T(NotEqual, "!=", 6) \ |
|||
T(LessThan, "<", 7) \ |
|||
T(GreaterThan, ">", 7) \ |
|||
T(LessThanOrEqual, "<=", 7) \ |
|||
T(GreaterThanOrEqual, ">=", 7) \ |
|||
K(In, "in", 7) \ |
|||
\ |
|||
/* Unary operators. */ \ |
|||
/* IsUnaryOp() relies on this block of enum values */ \ |
|||
/* being contiguous and sorted in the same order! */ \ |
|||
T(Not, "!", 0) \ |
|||
T(BitNot, "~", 0) \ |
|||
T(Inc, "++", 0) \ |
|||
T(Dec, "--", 0) \ |
|||
K(Delete, "delete", 0) \ |
|||
\ |
|||
/* Keywords */ \ |
|||
K(Anonymous, "anonymous", 0) \ |
|||
K(Break, "break", 0) \ |
|||
K(Const, "constant", 0) \ |
|||
K(Continue, "continue", 0) \ |
|||
K(Contract, "contract", 0) \ |
|||
K(Default, "default", 0) \ |
|||
K(Do, "do", 0) \ |
|||
K(Else, "else", 0) \ |
|||
K(Enum, "enum", 0) \ |
|||
K(Event, "event", 0) \ |
|||
K(External, "external", 0) \ |
|||
K(For, "for", 0) \ |
|||
K(Function, "function", 0) \ |
|||
K(If, "if", 0) \ |
|||
K(Indexed, "indexed", 0) \ |
|||
K(Internal, "internal", 0) \ |
|||
K(Import, "import", 0) \ |
|||
K(Is, "is", 0) \ |
|||
K(Mapping, "mapping", 0) \ |
|||
K(Memory, "memory", 0) \ |
|||
K(Modifier, "modifier", 0) \ |
|||
K(New, "new", 0) \ |
|||
K(Public, "public", 0) \ |
|||
K(Private, "private", 0) \ |
|||
K(Return, "return", 0) \ |
|||
K(Returns, "returns", 0) \ |
|||
K(Storage, "storage", 0) \ |
|||
K(Struct, "struct", 0) \ |
|||
K(Var, "var", 0) \ |
|||
K(While, "while", 0) \ |
|||
\ |
|||
/* Ether subdenominations */ \ |
|||
K(SubWei, "wei", 0) \ |
|||
K(SubSzabo, "szabo", 0) \ |
|||
K(SubFinney, "finney", 0) \ |
|||
K(SubEther, "ether", 0) \ |
|||
K(SubSecond, "seconds", 0) \ |
|||
K(SubMinute, "minutes", 0) \ |
|||
K(SubHour, "hours", 0) \ |
|||
K(SubDay, "days", 0) \ |
|||
K(SubWeek, "weeks", 0) \ |
|||
K(SubYear, "years", 0) \ |
|||
K(After, "after", 0) \ |
|||
/* type keywords, keep them in this order, keep int as first keyword
|
|||
* the implementation in Types.cpp has to be synced to this here */\ |
|||
K(Int, "int", 0) \ |
|||
K(Int8, "int8", 0) \ |
|||
K(Int16, "int16", 0) \ |
|||
K(Int24, "int24", 0) \ |
|||
K(Int32, "int32", 0) \ |
|||
K(Int40, "int40", 0) \ |
|||
K(Int48, "int48", 0) \ |
|||
K(Int56, "int56", 0) \ |
|||
K(Int64, "int64", 0) \ |
|||
K(Int72, "int72", 0) \ |
|||
K(Int80, "int80", 0) \ |
|||
K(Int88, "int88", 0) \ |
|||
K(Int96, "int96", 0) \ |
|||
K(Int104, "int104", 0) \ |
|||
K(Int112, "int112", 0) \ |
|||
K(Int120, "int120", 0) \ |
|||
K(Int128, "int128", 0) \ |
|||
K(Int136, "int136", 0) \ |
|||
K(Int144, "int144", 0) \ |
|||
K(Int152, "int152", 0) \ |
|||
K(Int160, "int160", 0) \ |
|||
K(Int168, "int168", 0) \ |
|||
K(Int176, "int178", 0) \ |
|||
K(Int184, "int184", 0) \ |
|||
K(Int192, "int192", 0) \ |
|||
K(Int200, "int200", 0) \ |
|||
K(Int208, "int208", 0) \ |
|||
K(Int216, "int216", 0) \ |
|||
K(Int224, "int224", 0) \ |
|||
K(Int232, "int232", 0) \ |
|||
K(Int240, "int240", 0) \ |
|||
K(Int248, "int248", 0) \ |
|||
K(Int256, "int256", 0) \ |
|||
K(UInt, "uint", 0) \ |
|||
K(UInt8, "uint8", 0) \ |
|||
K(UInt16, "uint16", 0) \ |
|||
K(UInt24, "uint24", 0) \ |
|||
K(UInt32, "uint32", 0) \ |
|||
K(UInt40, "uint40", 0) \ |
|||
K(UInt48, "uint48", 0) \ |
|||
K(UInt56, "uint56", 0) \ |
|||
K(UInt64, "uint64", 0) \ |
|||
K(UInt72, "uint72", 0) \ |
|||
K(UInt80, "uint80", 0) \ |
|||
K(UInt88, "uint88", 0) \ |
|||
K(UInt96, "uint96", 0) \ |
|||
K(UInt104, "uint104", 0) \ |
|||
K(UInt112, "uint112", 0) \ |
|||
K(UInt120, "uint120", 0) \ |
|||
K(UInt128, "uint128", 0) \ |
|||
K(UInt136, "uint136", 0) \ |
|||
K(UInt144, "uint144", 0) \ |
|||
K(UInt152, "uint152", 0) \ |
|||
K(UInt160, "uint160", 0) \ |
|||
K(UInt168, "uint168", 0) \ |
|||
K(UInt176, "uint178", 0) \ |
|||
K(UInt184, "uint184", 0) \ |
|||
K(UInt192, "uint192", 0) \ |
|||
K(UInt200, "uint200", 0) \ |
|||
K(UInt208, "uint208", 0) \ |
|||
K(UInt216, "uint216", 0) \ |
|||
K(UInt224, "uint224", 0) \ |
|||
K(UInt232, "uint232", 0) \ |
|||
K(UInt240, "uint240", 0) \ |
|||
K(UInt248, "uint248", 0) \ |
|||
K(UInt256, "uint256", 0) \ |
|||
K(Bytes1, "bytes1", 0) \ |
|||
K(Bytes2, "bytes2", 0) \ |
|||
K(Bytes3, "bytes3", 0) \ |
|||
K(Bytes4, "bytes4", 0) \ |
|||
K(Bytes5, "bytes5", 0) \ |
|||
K(Bytes6, "bytes6", 0) \ |
|||
K(Bytes7, "bytes7", 0) \ |
|||
K(Bytes8, "bytes8", 0) \ |
|||
K(Bytes9, "bytes9", 0) \ |
|||
K(Bytes10, "bytes10", 0) \ |
|||
K(Bytes11, "bytes11", 0) \ |
|||
K(Bytes12, "bytes12", 0) \ |
|||
K(Bytes13, "bytes13", 0) \ |
|||
K(Bytes14, "bytes14", 0) \ |
|||
K(Bytes15, "bytes15", 0) \ |
|||
K(Bytes16, "bytes16", 0) \ |
|||
K(Bytes17, "bytes17", 0) \ |
|||
K(Bytes18, "bytes18", 0) \ |
|||
K(Bytes19, "bytes19", 0) \ |
|||
K(Bytes20, "bytes20", 0) \ |
|||
K(Bytes21, "bytes21", 0) \ |
|||
K(Bytes22, "bytes22", 0) \ |
|||
K(Bytes23, "bytes23", 0) \ |
|||
K(Bytes24, "bytes24", 0) \ |
|||
K(Bytes25, "bytes25", 0) \ |
|||
K(Bytes26, "bytes26", 0) \ |
|||
K(Bytes27, "bytes27", 0) \ |
|||
K(Bytes28, "bytes28", 0) \ |
|||
K(Bytes29, "bytes29", 0) \ |
|||
K(Bytes30, "bytes30", 0) \ |
|||
K(Bytes31, "bytes31", 0) \ |
|||
K(Bytes32, "bytes32", 0) \ |
|||
K(Bytes, "bytes", 0) \ |
|||
K(Byte, "byte", 0) \ |
|||
K(String, "string", 0) \ |
|||
K(Address, "address", 0) \ |
|||
K(Bool, "bool", 0) \ |
|||
K(Real, "real", 0) \ |
|||
K(UReal, "ureal", 0) \ |
|||
T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ |
|||
\ |
|||
/* Literals */ \ |
|||
K(NullLiteral, "null", 0) \ |
|||
K(TrueLiteral, "true", 0) \ |
|||
K(FalseLiteral, "false", 0) \ |
|||
T(Number, NULL, 0) \ |
|||
T(StringLiteral, NULL, 0) \ |
|||
T(CommentLiteral, NULL, 0) \ |
|||
\ |
|||
/* Identifiers (not keywords or future reserved words). */ \ |
|||
T(Identifier, NULL, 0) \ |
|||
\ |
|||
/* Keywords reserved for future. use. */ \ |
|||
K(As, "as", 0) \ |
|||
K(Case, "case", 0) \ |
|||
K(Catch, "catch", 0) \ |
|||
K(Final, "final", 0) \ |
|||
K(Let, "let", 0) \ |
|||
K(Match, "match", 0) \ |
|||
K(Of, "of", 0) \ |
|||
K(Relocatable, "relocatable", 0) \ |
|||
K(Switch, "switch", 0) \ |
|||
K(Throw, "throw", 0) \ |
|||
K(Try, "try", 0) \ |
|||
K(Type, "type", 0) \ |
|||
K(TypeOf, "typeof", 0) \ |
|||
K(Using, "using", 0) \ |
|||
/* Illegal token - not able to scan. */ \ |
|||
T(Illegal, "ILLEGAL", 0) \ |
|||
\ |
|||
/* Scanner-internal use only. */ \ |
|||
T(Whitespace, NULL, 0) |
|||
|
|||
|
|||
class Token |
|||
{ |
|||
public: |
|||
// All token values.
|
|||
// attention! msvc issue:
|
|||
// http://stackoverflow.com/questions/9567868/compile-errors-after-adding-v8-to-my-project-c2143-c2059
|
|||
// @todo: avoid TOKEN_LIST macro
|
|||
#define T(name, string, precedence) name, |
|||
enum Value |
|||
{ |
|||
TOKEN_LIST(T, T) |
|||
NUM_TOKENS |
|||
}; |
|||
#undef T |
|||
|
|||
// Returns a string corresponding to the C++ token name
|
|||
// (e.g. "LT" for the token LT).
|
|||
static char const* getName(Value tok) |
|||
{ |
|||
solAssert(tok < NUM_TOKENS, ""); |
|||
return m_name[tok]; |
|||
} |
|||
|
|||
// Predicates
|
|||
static bool isElementaryTypeName(Value tok) { return Int <= tok && tok < TypesEnd; } |
|||
static bool isAssignmentOp(Value tok) { return Assign <= tok && tok <= AssignMod; } |
|||
static bool isBinaryOp(Value op) { return Comma <= op && op <= Exp; } |
|||
static bool isCommutativeOp(Value op) { return op == BitOr || op == BitXor || op == BitAnd || |
|||
op == Add || op == Mul || op == Equal || op == NotEqual; } |
|||
static bool isArithmeticOp(Value op) { return Add <= op && op <= Exp; } |
|||
static bool isCompareOp(Value op) { return Equal <= op && op <= In; } |
|||
|
|||
static Value AssignmentToBinaryOp(Value op) |
|||
{ |
|||
solAssert(isAssignmentOp(op) && op != Assign, ""); |
|||
return Value(op + (BitOr - AssignBitOr)); |
|||
} |
|||
|
|||
static bool isBitOp(Value op) { return (BitOr <= op && op <= SHR) || op == BitNot; } |
|||
static bool isBooleanOp(Value op) { return (Or <= op && op <= And) || op == Not; } |
|||
static bool isUnaryOp(Value op) { return (Not <= op && op <= Delete) || op == Add || op == Sub || op == After; } |
|||
static bool isCountOp(Value op) { return op == Inc || op == Dec; } |
|||
static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); } |
|||
static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; } |
|||
static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; } |
|||
static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage; } |
|||
static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; } |
|||
static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; } |
|||
|
|||
// Returns a string corresponding to the JS token string
|
|||
// (.e., "<" for the token LT) or NULL if the token doesn't
|
|||
// have a (unique) string (e.g. an IDENTIFIER).
|
|||
static char const* toString(Value tok) |
|||
{ |
|||
solAssert(tok < NUM_TOKENS, ""); |
|||
return m_string[tok]; |
|||
} |
|||
|
|||
// Returns the precedence > 0 for binary and compare
|
|||
// operators; returns 0 otherwise.
|
|||
static int precedence(Value tok) |
|||
{ |
|||
solAssert(tok < NUM_TOKENS, ""); |
|||
return m_precedence[tok]; |
|||
} |
|||
|
|||
static Token::Value fromIdentifierOrKeyword(std::string const& _name); |
|||
|
|||
private: |
|||
static char const* const m_name[NUM_TOKENS]; |
|||
static char const* const m_string[NUM_TOKENS]; |
|||
static int8_t const m_precedence[NUM_TOKENS]; |
|||
static char const m_tokenType[NUM_TOKENS]; |
|||
}; |
|||
|
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,935 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity data types |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <string> |
|||
#include <map> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <libdevcore/Common.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libsolidity/ASTForward.h> |
|||
#include <libsolidity/Token.h> |
|||
#include <libdevcore/UndefMacros.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class Type; // forward
|
|||
class FunctionType; // forward
|
|||
using TypePointer = std::shared_ptr<Type const>; |
|||
using FunctionTypePointer = std::shared_ptr<FunctionType const>; |
|||
using TypePointers = std::vector<TypePointer>; |
|||
|
|||
|
|||
enum class DataLocation { Storage, CallData, Memory }; |
|||
|
|||
/**
|
|||
* Helper class to compute storage offsets of members of structs and contracts. |
|||
*/ |
|||
class StorageOffsets |
|||
{ |
|||
public: |
|||
/// Resets the StorageOffsets objects and determines the position in storage for each
|
|||
/// of the elements of @a _types.
|
|||
void computeOffsets(TypePointers const& _types); |
|||
/// @returns the offset of the given member, might be null if the member is not part of storage.
|
|||
std::pair<u256, unsigned> const* getOffset(size_t _index) const; |
|||
/// @returns the total number of slots occupied by all members.
|
|||
u256 const& getStorageSize() const { return m_storageSize; } |
|||
|
|||
private: |
|||
u256 m_storageSize; |
|||
std::map<size_t, std::pair<u256, unsigned>> m_offsets; |
|||
}; |
|||
|
|||
/**
|
|||
* List of members of a type. |
|||
*/ |
|||
class MemberList |
|||
{ |
|||
public: |
|||
struct Member |
|||
{ |
|||
Member(std::string const& _name, TypePointer const& _type, Declaration const* _declaration = nullptr): |
|||
name(_name), |
|||
type(_type), |
|||
declaration(_declaration) |
|||
{ |
|||
} |
|||
|
|||
std::string name; |
|||
TypePointer type; |
|||
Declaration const* declaration = nullptr; |
|||
}; |
|||
|
|||
using MemberMap = std::vector<Member>; |
|||
|
|||
MemberList() {} |
|||
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} |
|||
MemberList& operator=(MemberList&& _other); |
|||
TypePointer getMemberType(std::string const& _name) const |
|||
{ |
|||
TypePointer type; |
|||
for (auto const& it: m_memberTypes) |
|||
if (it.name == _name) |
|||
{ |
|||
solAssert(!type, "Requested member type by non-unique name."); |
|||
type = it.type; |
|||
} |
|||
return type; |
|||
} |
|||
MemberMap membersByName(std::string const& _name) const |
|||
{ |
|||
MemberMap members; |
|||
for (auto const& it: m_memberTypes) |
|||
if (it.name == _name) |
|||
members.push_back(it); |
|||
return members; |
|||
} |
|||
/// @returns the offset of the given member in storage slots and bytes inside a slot or
|
|||
/// a nullptr if the member is not part of storage.
|
|||
std::pair<u256, unsigned> const* getMemberStorageOffset(std::string const& _name) const; |
|||
/// @returns the number of storage slots occupied by the members.
|
|||
u256 const& getStorageSize() const; |
|||
|
|||
MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } |
|||
MemberMap::const_iterator end() const { return m_memberTypes.end(); } |
|||
|
|||
private: |
|||
MemberMap m_memberTypes; |
|||
mutable std::unique_ptr<StorageOffsets> m_storageOffsets; |
|||
}; |
|||
|
|||
/**
|
|||
* Abstract base class that forms the root of the type hierarchy. |
|||
*/ |
|||
class Type: private boost::noncopyable, public std::enable_shared_from_this<Type> |
|||
{ |
|||
public: |
|||
enum class Category |
|||
{ |
|||
Integer, IntegerConstant, StringLiteral, Bool, Real, Array, |
|||
FixedBytes, Contract, Struct, Function, Enum, |
|||
Mapping, Void, TypeType, Modifier, Magic |
|||
}; |
|||
|
|||
/// @{
|
|||
/// @name Factory functions
|
|||
/// Factory functions that convert an AST @ref TypeName to a Type.
|
|||
static TypePointer fromElementaryTypeName(Token::Value _typeToken); |
|||
static TypePointer fromElementaryTypeName(std::string const& _name); |
|||
static TypePointer fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); |
|||
static TypePointer fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType); |
|||
static TypePointer fromArrayTypeName(TypeName& _baseTypeName, Expression* _length); |
|||
/// @}
|
|||
|
|||
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
|||
/// not fit any type.
|
|||
static TypePointer forLiteral(Literal const& _literal); |
|||
/// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise
|
|||
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); |
|||
|
|||
/// Calculates the
|
|||
|
|||
virtual Category getCategory() const = 0; |
|||
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const |
|||
{ |
|||
return isImplicitlyConvertibleTo(_convertTo); |
|||
} |
|||
/// @returns the resulting type of applying the given unary operator or an empty pointer if
|
|||
/// this is not possible.
|
|||
/// The default implementation does not allow any unary operator.
|
|||
virtual TypePointer unaryOperatorResult(Token::Value) const { return TypePointer(); } |
|||
/// @returns the resulting type of applying the given binary operator or an empty pointer if
|
|||
/// this is not possible.
|
|||
/// The default implementation allows comparison operators if a common type exists
|
|||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const |
|||
{ |
|||
return Token::isCompareOp(_operator) ? commonType(shared_from_this(), _other) : TypePointer(); |
|||
} |
|||
|
|||
virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } |
|||
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } |
|||
|
|||
/// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding
|
|||
/// is not a simple big-endian encoding or the type cannot be stored in calldata.
|
|||
/// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes.
|
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const { (void)_padded; return 0; } |
|||
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
|
|||
/// types, this is the size of the memory pointer.
|
|||
virtual unsigned memoryHeadSize() const { return getCalldataEncodedSize(); } |
|||
/// Convenience version of @see getCalldataEncodedSize(bool)
|
|||
unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } |
|||
/// @returns true if the type is dynamically encoded in calldata
|
|||
virtual bool isDynamicallySized() const { return false; } |
|||
/// @returns the number of storage slots required to hold this value in storage.
|
|||
/// For dynamically "allocated" types, it returns the size of the statically allocated head,
|
|||
virtual u256 getStorageSize() const { return 1; } |
|||
/// Multiple small types can be packed into a single storage slot. If such a packing is possible
|
|||
/// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if
|
|||
/// it does not fit.
|
|||
/// In order to avoid computation at runtime of whether such moving is necessary, structs and
|
|||
/// array data (not each element) always start a new slot.
|
|||
virtual unsigned getStorageBytes() const { return 32; } |
|||
/// Returns true if the type can be stored in storage.
|
|||
virtual bool canBeStored() const { return true; } |
|||
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
|
|||
virtual bool canLiveOutsideStorage() const { return true; } |
|||
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
|
|||
/// i.e. it behaves differently in lvalue context and in value context.
|
|||
virtual bool isValueType() const { return false; } |
|||
virtual unsigned getSizeOnStack() const { return 1; } |
|||
/// @returns the mobile (in contrast to static) type corresponding to the given type.
|
|||
/// This returns the corresponding integer type for IntegerConstantTypes and the pointer type
|
|||
/// for storage reference types.
|
|||
virtual TypePointer mobileType() const { return shared_from_this(); } |
|||
/// @returns true if this is a non-value type and the data of this type is stored at the
|
|||
/// given location.
|
|||
virtual bool dataStoredIn(DataLocation) const { return false; } |
|||
|
|||
/// Returns the list of all members of this type. Default implementation: no members.
|
|||
virtual MemberList const& getMembers() const { return EmptyMemberList; } |
|||
/// Convenience method, returns the type of the given named member or an empty pointer if no such member exists.
|
|||
TypePointer getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } |
|||
|
|||
virtual std::string toString(bool _short) const = 0; |
|||
std::string toString() const { return toString(false); } |
|||
virtual u256 literalValue(Literal const*) const |
|||
{ |
|||
BOOST_THROW_EXCEPTION( |
|||
InternalCompilerError() << |
|||
errinfo_comment("Literal value requested for type without literals.") |
|||
); |
|||
} |
|||
|
|||
/// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address.
|
|||
/// If there is no such type, returns an empty shared pointer.
|
|||
virtual TypePointer externalType() const { return TypePointer(); } |
|||
|
|||
protected: |
|||
/// Convenience object used when returning an empty member list.
|
|||
static const MemberList EmptyMemberList; |
|||
}; |
|||
|
|||
/**
|
|||
* Any kind of integer type (signed, unsigned, address). |
|||
*/ |
|||
class IntegerType: public Type |
|||
{ |
|||
public: |
|||
enum class Modifier |
|||
{ |
|||
Unsigned, Signed, Address |
|||
}; |
|||
virtual Category getCategory() const override { return Category::Integer; } |
|||
|
|||
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned); |
|||
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
|
|||
virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } |
|||
virtual unsigned getStorageBytes() const override { return m_bits / 8; } |
|||
virtual bool isValueType() const override { return true; } |
|||
|
|||
virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; } |
|||
|
|||
virtual std::string toString(bool _short) const override; |
|||
|
|||
virtual TypePointer externalType() const override { return shared_from_this(); } |
|||
|
|||
int getNumBits() const { return m_bits; } |
|||
bool isAddress() const { return m_modifier == Modifier::Address; } |
|||
bool isSigned() const { return m_modifier == Modifier::Signed; } |
|||
|
|||
static const MemberList AddressMemberList; |
|||
|
|||
private: |
|||
int m_bits; |
|||
Modifier m_modifier; |
|||
}; |
|||
|
|||
/**
|
|||
* Integer constants either literals or computed. Example expressions: 2, 2+10, ~10. |
|||
* There is one distinct type per value. |
|||
*/ |
|||
class IntegerConstantType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::IntegerConstant; } |
|||
|
|||
/// @returns true if the literal is a valid integer.
|
|||
static bool isValidLiteral(Literal const& _literal); |
|||
|
|||
explicit IntegerConstantType(Literal const& _literal); |
|||
explicit IntegerConstantType(bigint _value): m_value(_value) {} |
|||
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
|
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
|
|||
virtual std::string toString(bool _short) const override; |
|||
virtual u256 literalValue(Literal const* _literal) const override; |
|||
virtual TypePointer mobileType() const override; |
|||
|
|||
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
|
|||
std::shared_ptr<IntegerType const> getIntegerType() const; |
|||
|
|||
private: |
|||
bigint m_value; |
|||
}; |
|||
|
|||
/**
|
|||
* Literal string, can be converted to bytes, bytesX or string. |
|||
*/ |
|||
class StringLiteralType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::StringLiteral; } |
|||
|
|||
explicit StringLiteralType(Literal const& _literal); |
|||
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override |
|||
{ |
|||
return TypePointer(); |
|||
} |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
|
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
virtual unsigned getSizeOnStack() const override { return 0; } |
|||
|
|||
virtual std::string toString(bool) const override { return "literal_string \"" + m_value + "\""; } |
|||
virtual TypePointer mobileType() const override; |
|||
|
|||
std::string const& value() const { return m_value; } |
|||
|
|||
private: |
|||
std::string m_value; |
|||
}; |
|||
|
|||
/**
|
|||
* Bytes type with fixed length of up to 32 bytes. |
|||
*/ |
|||
class FixedBytesType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::FixedBytes; } |
|||
|
|||
/// @returns the smallest bytes type for the given literal or an empty pointer
|
|||
/// if no type fits.
|
|||
static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal); |
|||
|
|||
explicit FixedBytesType(int _bytes); |
|||
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; |
|||
|
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } |
|||
virtual unsigned getStorageBytes() const override { return m_bytes; } |
|||
virtual bool isValueType() const override { return true; } |
|||
|
|||
virtual std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } |
|||
virtual TypePointer externalType() const override { return shared_from_this(); } |
|||
|
|||
int numBytes() const { return m_bytes; } |
|||
|
|||
private: |
|||
int m_bytes; |
|||
}; |
|||
|
|||
/**
|
|||
* The boolean type. |
|||
*/ |
|||
class BoolType: public Type |
|||
{ |
|||
public: |
|||
BoolType() {} |
|||
virtual Category getCategory() const override { return Category::Bool; } |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; |
|||
|
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } |
|||
virtual unsigned getStorageBytes() const override { return 1; } |
|||
virtual bool isValueType() const override { return true; } |
|||
|
|||
virtual std::string toString(bool) const override { return "bool"; } |
|||
virtual u256 literalValue(Literal const* _literal) const override; |
|||
virtual TypePointer externalType() const override { return shared_from_this(); } |
|||
}; |
|||
|
|||
/**
|
|||
* Base class used by types which are not value types and can be stored either in storage, memory |
|||
* or calldata. This is currently used by arrays and structs. |
|||
*/ |
|||
class ReferenceType: public Type |
|||
{ |
|||
public: |
|||
explicit ReferenceType(DataLocation _location): m_location(_location) {} |
|||
DataLocation location() const { return m_location; } |
|||
|
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override |
|||
{ |
|||
return TypePointer(); |
|||
} |
|||
virtual unsigned memoryHeadSize() const override { return 32; } |
|||
|
|||
/// @returns a copy of this type with location (recursively) changed to @a _location,
|
|||
/// whereas isPointer is only shallowly changed - the deep copy is always a bound reference.
|
|||
virtual TypePointer copyForLocation(DataLocation _location, bool _isPointer) const = 0; |
|||
|
|||
virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } |
|||
virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } |
|||
|
|||
/// Storage references can be pointers or bound references. In general, local variables are of
|
|||
/// pointer type, state variables are bound references. Assignments to pointers or deleting
|
|||
/// them will not modify storage (that will only change the pointer). Assignment from
|
|||
/// non-storage objects to a variable of storage pointer type is not possible.
|
|||
bool isPointer() const { return m_isPointer; } |
|||
|
|||
bool operator==(ReferenceType const& _other) const |
|||
{ |
|||
return location() == _other.location() && isPointer() == _other.isPointer(); |
|||
} |
|||
|
|||
/// @returns a copy of @a _type having the same location as this (and is not a pointer type)
|
|||
/// if _type is a reference type and an unmodified copy of _type otherwise.
|
|||
/// This function is mostly useful to modify inner types appropriately.
|
|||
static TypePointer copyForLocationIfReference(DataLocation _location, TypePointer const& _type); |
|||
|
|||
protected: |
|||
TypePointer copyForLocationIfReference(TypePointer const& _type) const; |
|||
/// @returns a human-readable description of the reference part of the type.
|
|||
std::string stringForReferencePart() const; |
|||
|
|||
DataLocation m_location = DataLocation::Storage; |
|||
bool m_isPointer = true; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>]) |
|||
* and dynamically-sized array (<type>[]). |
|||
* In storage, all arrays are packed tightly (as long as more than one elementary type fits in |
|||
* one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and |
|||
* thus start on their own slot. |
|||
*/ |
|||
class ArrayType: public ReferenceType |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Array; } |
|||
|
|||
/// Constructor for a byte array ("bytes") and string.
|
|||
explicit ArrayType(DataLocation _location, bool _isString = false): |
|||
ReferenceType(_location), |
|||
m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), |
|||
m_baseType(std::make_shared<FixedBytesType>(1)) |
|||
{ |
|||
} |
|||
/// Constructor for a dynamically sized array type ("type[]")
|
|||
ArrayType(DataLocation _location, TypePointer const& _baseType): |
|||
ReferenceType(_location), |
|||
m_baseType(copyForLocationIfReference(_baseType)) |
|||
{ |
|||
} |
|||
/// Constructor for a fixed-size array type ("type[20]")
|
|||
ArrayType(DataLocation _location, TypePointer const& _baseType, u256 const& _length): |
|||
ReferenceType(_location), |
|||
m_baseType(copyForLocationIfReference(_baseType)), |
|||
m_hasDynamicLength(false), |
|||
m_length(_length) |
|||
{} |
|||
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual bool operator==(const Type& _other) const override; |
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const override; |
|||
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } |
|||
virtual unsigned getSizeOnStack() const override; |
|||
virtual std::string toString(bool _short) const override; |
|||
virtual MemberList const& getMembers() const override |
|||
{ |
|||
return isString() ? EmptyMemberList : s_arrayTypeMemberList; |
|||
} |
|||
virtual TypePointer externalType() const override; |
|||
|
|||
/// @returns true if this is a byte array or a string
|
|||
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } |
|||
/// @returns true if this is a string
|
|||
bool isString() const { return m_arrayKind == ArrayKind::String; } |
|||
TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} |
|||
u256 const& getLength() const { return m_length; } |
|||
|
|||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; |
|||
|
|||
private: |
|||
/// String is interpreted as a subtype of Bytes.
|
|||
enum class ArrayKind { Ordinary, Bytes, String }; |
|||
|
|||
///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays.
|
|||
ArrayKind m_arrayKind = ArrayKind::Ordinary; |
|||
TypePointer m_baseType; |
|||
bool m_hasDynamicLength = true; |
|||
u256 m_length; |
|||
static const MemberList s_arrayTypeMemberList; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of a contract instance, there is one distinct type for each contract definition. |
|||
*/ |
|||
class ContractType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Contract; } |
|||
explicit ContractType(ContractDefinition const& _contract, bool _super = false): |
|||
m_contract(_contract), m_super(_super) {} |
|||
/// Contracts can be implicitly converted to super classes and to addresses.
|
|||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
/// Contracts can be converted to themselves and to integers.
|
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual unsigned getCalldataEncodedSize(bool _padded ) const override |
|||
{ |
|||
return externalType()->getCalldataEncodedSize(_padded); |
|||
} |
|||
virtual unsigned getStorageBytes() const override { return 20; } |
|||
virtual bool canLiveOutsideStorage() const override { return true; } |
|||
virtual bool isValueType() const override { return true; } |
|||
virtual std::string toString(bool _short) const override; |
|||
|
|||
virtual MemberList const& getMembers() const override; |
|||
virtual TypePointer externalType() const override |
|||
{ |
|||
return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); |
|||
} |
|||
|
|||
bool isSuper() const { return m_super; } |
|||
ContractDefinition const& getContractDefinition() const { return m_contract; } |
|||
|
|||
/// Returns the function type of the constructor. Note that the location part of the function type
|
|||
/// is not used, as this type cannot be the type of a variable or expression.
|
|||
FunctionTypePointer const& getConstructorType() const; |
|||
|
|||
/// @returns the identifier of the function with the given name or Invalid256 if such a name does
|
|||
/// not exist.
|
|||
u256 getFunctionIdentifier(std::string const& _functionName) const; |
|||
|
|||
/// @returns a list of all state variables (including inherited) of the contract and their
|
|||
/// offsets in storage.
|
|||
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> getStateVariables() const; |
|||
|
|||
private: |
|||
ContractDefinition const& m_contract; |
|||
/// If true, it is the "super" type of the current contract, i.e. it contains only inherited
|
|||
/// members.
|
|||
bool m_super; |
|||
/// Type of the constructor, @see getConstructorType. Lazily initialized.
|
|||
mutable FunctionTypePointer m_constructorType; |
|||
/// List of member types, will be lazy-initialized because of recursive references.
|
|||
mutable std::unique_ptr<MemberList> m_members; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of a struct instance, there is one distinct type per struct definition. |
|||
*/ |
|||
class StructType: public ReferenceType |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Struct; } |
|||
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): |
|||
ReferenceType(_location), m_struct(_struct) {} |
|||
virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const override; |
|||
u256 memorySize() const; |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return true; } |
|||
virtual std::string toString(bool _short) const override; |
|||
|
|||
virtual MemberList const& getMembers() const override; |
|||
|
|||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; |
|||
|
|||
/// @returns a function that peforms the type conversion between a list of struct members
|
|||
/// and a memory struct of this type.
|
|||
FunctionTypePointer constructorType() const; |
|||
|
|||
std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const; |
|||
u256 memoryOffsetOfMember(std::string const& _name) const; |
|||
|
|||
StructDefinition const& structDefinition() const { return m_struct; } |
|||
|
|||
/// @returns the set of all members that are removed in the memory version (typically mappings).
|
|||
std::set<std::string> membersMissingInMemory() const; |
|||
|
|||
private: |
|||
StructDefinition const& m_struct; |
|||
/// List of member types, will be lazy-initialized because of recursive references.
|
|||
mutable std::unique_ptr<MemberList> m_members; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of an enum instance, there is one distinct type per enum definition. |
|||
*/ |
|||
class EnumType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Enum; } |
|||
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} |
|||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual unsigned getCalldataEncodedSize(bool _padded) const override |
|||
{ |
|||
return externalType()->getCalldataEncodedSize(_padded); |
|||
} |
|||
virtual unsigned getStorageBytes() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return true; } |
|||
virtual std::string toString(bool _short) const override; |
|||
virtual bool isValueType() const override { return true; } |
|||
|
|||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; |
|||
virtual TypePointer externalType() const override |
|||
{ |
|||
return std::make_shared<IntegerType>(8 * int(getStorageBytes())); |
|||
} |
|||
|
|||
EnumDefinition const& getEnumDefinition() const { return m_enum; } |
|||
/// @returns the value that the string has in the Enum
|
|||
unsigned int getMemberValue(ASTString const& _member) const; |
|||
|
|||
private: |
|||
EnumDefinition const& m_enum; |
|||
/// List of member types, will be lazy-initialized because of recursive references.
|
|||
mutable std::unique_ptr<MemberList> m_members; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of a function, identified by its (return) parameter types. |
|||
* @todo the return parameters should also have names, i.e. return parameters should be a struct |
|||
* type. |
|||
*/ |
|||
class FunctionType: public Type |
|||
{ |
|||
public: |
|||
/// How this function is invoked on the EVM.
|
|||
/// @todo This documentation is outdated, and Location should rather be named "Type"
|
|||
enum class Location |
|||
{ |
|||
Internal, ///< stack-call using plain JUMP
|
|||
External, ///< external call using CALL
|
|||
CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage
|
|||
Bare, ///< CALL without function hash
|
|||
BareCallCode, ///< CALLCODE without function hash
|
|||
Creation, ///< external call using CREATE
|
|||
Send, ///< CALL, but without data and gas
|
|||
SHA3, ///< SHA3
|
|||
Suicide, ///< SUICIDE
|
|||
ECRecover, ///< CALL to special contract for ecrecover
|
|||
SHA256, ///< CALL to special contract for sha256
|
|||
RIPEMD160, ///< CALL to special contract for ripemd160
|
|||
Log0, |
|||
Log1, |
|||
Log2, |
|||
Log3, |
|||
Log4, |
|||
Event, ///< syntactic sugar for LOG*
|
|||
SetGas, ///< modify the default gas value for the function call
|
|||
SetValue, ///< modify the default value transfer for the function call
|
|||
BlockHash ///< BLOCKHASH
|
|||
}; |
|||
|
|||
virtual Category getCategory() const override { return Category::Function; } |
|||
|
|||
/// @returns TypePointer of a new FunctionType object. All input/return parameters are an
|
|||
/// appropriate external types of input/return parameters of current function.
|
|||
/// Returns an empty shared pointer if one of the input/return parameters does not have an
|
|||
/// external type.
|
|||
FunctionTypePointer externalFunctionType() const; |
|||
virtual TypePointer externalType() const override { return externalFunctionType(); } |
|||
|
|||
/// Creates the type of a function.
|
|||
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); |
|||
/// Creates the accessor function type of a state variable.
|
|||
explicit FunctionType(VariableDeclaration const& _varDecl); |
|||
/// Creates the function type of an event.
|
|||
explicit FunctionType(EventDefinition const& _event); |
|||
FunctionType( |
|||
strings const& _parameterTypes, |
|||
strings const& _returnParameterTypes, |
|||
Location _location = Location::Internal, |
|||
bool _arbitraryParameters = false |
|||
): FunctionType( |
|||
parseElementaryTypeVector(_parameterTypes), |
|||
parseElementaryTypeVector(_returnParameterTypes), |
|||
strings(), |
|||
strings(), |
|||
_location, |
|||
_arbitraryParameters |
|||
) |
|||
{ |
|||
} |
|||
FunctionType( |
|||
TypePointers const& _parameterTypes, |
|||
TypePointers const& _returnParameterTypes, |
|||
strings _parameterNames = strings(), |
|||
strings _returnParameterNames = strings(), |
|||
Location _location = Location::Internal, |
|||
bool _arbitraryParameters = false, |
|||
Declaration const* _declaration = nullptr, |
|||
bool _gasSet = false, |
|||
bool _valueSet = false |
|||
): |
|||
m_parameterTypes(_parameterTypes), |
|||
m_returnParameterTypes(_returnParameterTypes), |
|||
m_parameterNames(_parameterNames), |
|||
m_returnParameterNames(_returnParameterNames), |
|||
m_location(_location), |
|||
m_arbitraryParameters(_arbitraryParameters), |
|||
m_gasSet(_gasSet), |
|||
m_valueSet(_valueSet), |
|||
m_declaration(_declaration) |
|||
{} |
|||
|
|||
TypePointers const& getParameterTypes() const { return m_parameterTypes; } |
|||
std::vector<std::string> const& getParameterNames() const { return m_parameterNames; } |
|||
std::vector<std::string> const getParameterTypeNames() const; |
|||
TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } |
|||
std::vector<std::string> const& getReturnParameterNames() const { return m_returnParameterNames; } |
|||
std::vector<std::string> const getReturnParameterTypeNames() const; |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual std::string toString(bool _short) const override; |
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
virtual unsigned getSizeOnStack() const override; |
|||
virtual MemberList const& getMembers() const override; |
|||
|
|||
/// @returns true if this function can take the given argument types (possibly
|
|||
/// after implicit conversion).
|
|||
bool canTakeArguments(TypePointers const& _arguments) const; |
|||
/// @returns true if the types of parameters are equal (does't check return parameter types)
|
|||
bool hasEqualArgumentTypes(FunctionType const& _other) const; |
|||
|
|||
/// @returns true if the ABI is used for this call (only meaningful for external calls)
|
|||
bool isBareCall() const; |
|||
Location const& getLocation() const { return m_location; } |
|||
/// @returns the external signature of this function type given the function name
|
|||
/// If @a _name is not provided (empty string) then the @c m_declaration member of the
|
|||
/// function type is used
|
|||
std::string externalSignature(std::string const& _name = "") const; |
|||
/// @returns the external identifier of this function (the hash of the signature).
|
|||
u256 externalIdentifier() const; |
|||
Declaration const& getDeclaration() const |
|||
{ |
|||
solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); |
|||
return *m_declaration; |
|||
} |
|||
bool hasDeclaration() const { return !!m_declaration; } |
|||
bool isConstant() const { return m_isConstant; } |
|||
/// @return A shared pointer of an ASTString.
|
|||
/// Can contain a nullptr in which case indicates absence of documentation
|
|||
ASTPointer<ASTString> getDocumentation() const; |
|||
|
|||
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
|
|||
bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } |
|||
bool takesArbitraryParameters() const { return m_arbitraryParameters; } |
|||
bool gasSet() const { return m_gasSet; } |
|||
bool valueSet() const { return m_valueSet; } |
|||
|
|||
/// @returns a copy of this type, where gas or value are set manually. This will never set one
|
|||
/// of the parameters to fals.
|
|||
TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; |
|||
|
|||
/// @returns a copy of this function type where all return parameters of dynamic size are
|
|||
/// removed and the location of reference types is changed from CallData to Memory.
|
|||
/// This is needed if external functions are called on other contracts, as they cannot return
|
|||
/// dynamic values.
|
|||
FunctionTypePointer asMemberFunction() const; |
|||
|
|||
private: |
|||
static TypePointers parseElementaryTypeVector(strings const& _types); |
|||
|
|||
TypePointers m_parameterTypes; |
|||
TypePointers m_returnParameterTypes; |
|||
std::vector<std::string> m_parameterNames; |
|||
std::vector<std::string> m_returnParameterNames; |
|||
Location const m_location; |
|||
/// true if the function takes an arbitrary number of arguments of arbitrary types
|
|||
bool const m_arbitraryParameters = false; |
|||
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
|
|||
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
|
|||
bool m_isConstant = false; |
|||
mutable std::unique_ptr<MemberList> m_members; |
|||
Declaration const* m_declaration = nullptr; |
|||
}; |
|||
|
|||
/**
|
|||
* The type of a mapping, there is one distinct type per key/value type pair. |
|||
* Mappings always occupy their own storage slot, but do not actually use it. |
|||
*/ |
|||
class MappingType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Mapping; } |
|||
MappingType(TypePointer const& _keyType, TypePointer const& _valueType): |
|||
m_keyType(_keyType), m_valueType(_valueType) {} |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual std::string toString(bool _short) const override; |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
|
|||
TypePointer const& getKeyType() const { return m_keyType; } |
|||
TypePointer const& getValueType() const { return m_valueType; } |
|||
|
|||
private: |
|||
TypePointer m_keyType; |
|||
TypePointer m_valueType; |
|||
}; |
|||
|
|||
/**
|
|||
* The void type, can only be implicitly used as the type that is returned by functions without |
|||
* return parameters. |
|||
*/ |
|||
class VoidType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Void; } |
|||
VoidType() {} |
|||
|
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } |
|||
virtual std::string toString(bool) const override { return "void"; } |
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
virtual unsigned getSizeOnStack() const override { return 0; } |
|||
}; |
|||
|
|||
/**
|
|||
* The type of a type reference. The type of "uint32" when used in "a = uint32(2)" is an example |
|||
* of a TypeType. |
|||
*/ |
|||
class TypeType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::TypeType; } |
|||
explicit TypeType(TypePointer const& _actualType, ContractDefinition const* _currentContract = nullptr): |
|||
m_actualType(_actualType), m_currentContract(_currentContract) {} |
|||
TypePointer const& getActualType() const { return m_actualType; } |
|||
|
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
virtual unsigned getSizeOnStack() const override { return 0; } |
|||
virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } |
|||
virtual MemberList const& getMembers() const override; |
|||
|
|||
private: |
|||
TypePointer m_actualType; |
|||
/// Context in which this type is used (influences visibility etc.), can be nullptr.
|
|||
ContractDefinition const* m_currentContract; |
|||
/// List of member types, will be lazy-initialized because of recursive references.
|
|||
mutable std::unique_ptr<MemberList> m_members; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* The type of a function modifier. Not used for anything for now. |
|||
*/ |
|||
class ModifierType: public Type |
|||
{ |
|||
public: |
|||
virtual Category getCategory() const override { return Category::Modifier; } |
|||
explicit ModifierType(ModifierDefinition const& _modifier); |
|||
|
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } |
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual u256 getStorageSize() const override; |
|||
virtual bool canLiveOutsideStorage() const override { return false; } |
|||
virtual unsigned getSizeOnStack() const override { return 0; } |
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual std::string toString(bool _short) const override; |
|||
|
|||
private: |
|||
TypePointers m_parameterTypes; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Special type for magic variables (block, msg, tx), similar to a struct but without any reference |
|||
* (it always references a global singleton by name). |
|||
*/ |
|||
class MagicType: public Type |
|||
{ |
|||
public: |
|||
enum class Kind { Block, Message, Transaction }; |
|||
virtual Category getCategory() const override { return Category::Magic; } |
|||
|
|||
explicit MagicType(Kind _kind); |
|||
|
|||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override |
|||
{ |
|||
return TypePointer(); |
|||
} |
|||
|
|||
virtual bool operator==(Type const& _other) const override; |
|||
virtual bool canBeStored() const override { return false; } |
|||
virtual bool canLiveOutsideStorage() const override { return true; } |
|||
virtual unsigned getSizeOnStack() const override { return 0; } |
|||
virtual MemberList const& getMembers() const override { return m_members; } |
|||
|
|||
virtual std::string toString(bool _short) const override; |
|||
|
|||
private: |
|||
Kind m_kind; |
|||
|
|||
MemberList m_members; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity Utilities. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Assertions.h> |
|||
|
|||
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
|
|||
#define solAssert(CONDITION, DESCRIPTION) \ |
|||
assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) |
|||
|
@ -1,39 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Versioning. |
|||
*/ |
|||
|
|||
#include <libsolidity/Version.h> |
|||
#include <string> |
|||
#include <BuildInfo.h> |
|||
#include <libdevcore/Common.h> |
|||
|
|||
using namespace dev; |
|||
using namespace dev::solidity; |
|||
using namespace std; |
|||
|
|||
char const* dev::solidity::VersionNumber = "0.1.1"; |
|||
extern string const dev::solidity::VersionString = |
|||
string(dev::solidity::VersionNumber) + |
|||
"-" + |
|||
string(DEV_QUOTED(ETH_COMMIT_HASH)).substr(0, 8) + |
|||
(ETH_CLEAN_REPO ? "" : "*") + |
|||
"/" DEV_QUOTED(ETH_BUILD_TYPE) "-" DEV_QUOTED(ETH_BUILD_PLATFORM); |
|||
|
@ -1,36 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Versioning. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
extern char const* VersionNumber; |
|||
extern std::string const VersionString; |
|||
|
|||
} |
|||
} |
@ -1,47 +0,0 @@ |
|||
ContractDefinition = 'contract' Identifier |
|||
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? |
|||
'{' ContractPart* '}' |
|||
ContractPart = StateVariableDeclaration | StructDefinition | ModifierDefinition | FunctionDefinition | EnumDefinition |
|||
|
|||
InheritanceSpecifier = Identifier ( '(' Expression ( ',' Expression )* ')' )? |
|||
StructDefinition = 'struct' Identifier '{' |
|||
( VariableDeclaration (';' VariableDeclaration)* )? '} |
|||
StateVariableDeclaration = TypeName ( 'public' | 'inheritable' | 'private' )? Identifier ';' |
|||
ModifierDefinition = 'modifier' Identifier ParameterList? Block |
|||
FunctionDefinition = 'function' Identifier ParameterList |
|||
( Identifier | 'constant' | 'external' | 'public' | 'inheritable' | 'private' )* |
|||
( 'returns' ParameterList )? Block |
|||
|
|||
EnumValue = Identifier |
|||
EnumDefinition = 'enum' '{' EnumValue (',' EnumValue)* '}' |
|||
ParameterList = '(' ( VariableDeclaration (',' VariableDeclaration)* )? ')' |
|||
// semantic restriction: mappings and structs (recursively) containing mappings |
|||
// are not allowed in argument lists |
|||
VariableDeclaration = TypeName Identifier |
|||
TypeName = ElementaryTypeName | Identifier | Mapping | ArrayTypeName |
|||
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' |
|||
ArrayTypeName = TypeName '[' (Expression)? ']' |
|||
|
|||
Block = '{' Statement* '}' |
|||
Statement = IfStatement | WhileStatement | Block | |
|||
( Continue | Break | Return | VariableDefinition | ExpressionStatement ) ';' |
|||
|
|||
ExpressionStatement = Expression |
|||
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? |
|||
WhileStatement = 'while' '(' Expression ')' Statement |
|||
VardefOrExprStmt = Variabledefinition | ExpressionStatement |
|||
ForStatement = 'for' '(' (VardefOrExprStmt)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement |
|||
Continue = 'continue' ';' |
|||
Break = 'break' ';' |
|||
Return = 'return' Expression? ';' |
|||
VariableDefinition = VariableDeclaration ( = Expression )? ';' |
|||
|
|||
Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewExpression | IndexAccess | |
|||
MemberAccess | PrimaryExpression |
|||
// The expression syntax is actually much more complicated |
|||
Assignment = Expression (AssignmentOp Expression) |
|||
FunctionCall = Expression '(' Expression ( ',' Expression )* ')' |
|||
NewExpression = 'new' Identifier |
|||
MemberAccess = Expression '.' Identifier |
|||
IndexAccess = Expression '[' (Expresison)? ']' |
|||
PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' |
@ -1,29 +0,0 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
list(REMOVE_ITEM SRC_LIST "./jsonCompiler.cpp") |
|||
|
|||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) |
|||
include_directories(BEFORE ..) |
|||
include_directories(${Boost_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE solc) |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) |
|||
|
|||
add_dependencies(${EXECUTABLE} BuildInfo.h) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_PROGRAM_OPTIONS_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} solidity) |
|||
|
|||
if (APPLE) |
|||
install(TARGETS ${EXECUTABLE} DESTINATION bin) |
|||
else() |
|||
eth_install_executable(${EXECUTABLE}) |
|||
endif() |
|||
|
|||
add_library(soljson jsonCompiler.cpp ${HEADERS}) |
|||
target_link_libraries(soljson solidity) |
@ -1,657 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris <lefteris@ethdev.com> |
|||
* @author Gav Wood <g@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity command line interface. |
|||
*/ |
|||
#include "CommandLineInterface.h" |
|||
|
|||
#include <string> |
|||
#include <iostream> |
|||
#include <fstream> |
|||
|
|||
#include <boost/filesystem.hpp> |
|||
#include <boost/algorithm/string.hpp> |
|||
|
|||
#include "BuildInfo.h" |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libevmcore/Params.h> |
|||
#include <libsolidity/Version.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/ASTPrinter.h> |
|||
#include <libsolidity/ASTJsonConverter.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
#include <libsolidity/GasEstimator.h> |
|||
|
|||
using namespace std; |
|||
namespace po = boost::program_options; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
static string const g_argAbiStr = "json-abi"; |
|||
static string const g_argSolAbiStr = "sol-abi"; |
|||
static string const g_argSignatureHashes = "hashes"; |
|||
static string const g_argGas = "gas"; |
|||
static string const g_argAsmStr = "asm"; |
|||
static string const g_argAsmJsonStr = "asm-json"; |
|||
static string const g_argAstStr = "ast"; |
|||
static string const g_argAstJson = "ast-json"; |
|||
static string const g_argBinaryStr = "binary"; |
|||
static string const g_argCloneBinaryStr = "clone-binary"; |
|||
static string const g_argOpcodesStr = "opcodes"; |
|||
static string const g_argNatspecDevStr = "natspec-dev"; |
|||
static string const g_argNatspecUserStr = "natspec-user"; |
|||
static string const g_argAddStandard = "add-std"; |
|||
|
|||
/// Possible arguments to for --combined-json
|
|||
static set<string> const g_combinedJsonArgs{ |
|||
"binary", |
|||
"clone-binary", |
|||
"opcodes", |
|||
"json-abi", |
|||
"sol-abi", |
|||
"asm", |
|||
"ast", |
|||
"natspec-user", |
|||
"natspec-dev" |
|||
}; |
|||
|
|||
static void version() |
|||
{ |
|||
cout << |
|||
"solc, the solidity compiler commandline interface" << |
|||
endl << |
|||
"Version: " << |
|||
dev::solidity::VersionString << |
|||
endl; |
|||
exit(0); |
|||
} |
|||
|
|||
static inline bool humanTargetedStdout(po::variables_map const& _args, string const& _name) |
|||
{ |
|||
return _args.count(_name) && _args[_name].as<OutputType>() != OutputType::FILE; |
|||
} |
|||
|
|||
static bool needsHumanTargetedStdout(po::variables_map const& _args) |
|||
{ |
|||
|
|||
return |
|||
_args.count(g_argGas) || |
|||
humanTargetedStdout(_args, g_argAbiStr) || |
|||
humanTargetedStdout(_args, g_argSolAbiStr) || |
|||
humanTargetedStdout(_args, g_argSignatureHashes) || |
|||
humanTargetedStdout(_args, g_argNatspecUserStr) || |
|||
humanTargetedStdout(_args, g_argAstJson) || |
|||
humanTargetedStdout(_args, g_argNatspecDevStr) || |
|||
humanTargetedStdout(_args, g_argAsmStr) || |
|||
humanTargetedStdout(_args, g_argAsmJsonStr) || |
|||
humanTargetedStdout(_args, g_argOpcodesStr) || |
|||
humanTargetedStdout(_args, g_argBinaryStr) || |
|||
humanTargetedStdout(_args, g_argCloneBinaryStr); |
|||
} |
|||
|
|||
static inline bool outputToFile(OutputType type) |
|||
{ |
|||
return type == OutputType::FILE || type == OutputType::BOTH; |
|||
} |
|||
|
|||
static inline bool outputToStdout(OutputType type) |
|||
{ |
|||
return type == OutputType::STDOUT || type == OutputType::BOTH; |
|||
} |
|||
|
|||
static std::istream& operator>>(std::istream& _in, OutputType& io_output) |
|||
{ |
|||
std::string token; |
|||
_in >> token; |
|||
if (token == "stdout") |
|||
io_output = OutputType::STDOUT; |
|||
else if (token == "file") |
|||
io_output = OutputType::FILE; |
|||
else if (token == "both") |
|||
io_output = OutputType::BOTH; |
|||
else |
|||
throw boost::program_options::invalid_option_value(token); |
|||
return _in; |
|||
} |
|||
|
|||
void CommandLineInterface::handleBinary(string const& _contract) |
|||
{ |
|||
if (m_args.count(g_argBinaryStr)) |
|||
{ |
|||
if (outputToStdout(m_args[g_argBinaryStr].as<OutputType>())) |
|||
{ |
|||
cout << "Binary: " << endl; |
|||
cout << toHex(m_compiler->getBytecode(_contract)) << endl; |
|||
} |
|||
if (outputToFile(m_args[g_argBinaryStr].as<OutputType>())) |
|||
{ |
|||
ofstream outFile(_contract + ".binary"); |
|||
outFile << toHex(m_compiler->getBytecode(_contract)); |
|||
outFile.close(); |
|||
} |
|||
} |
|||
if (m_args.count(g_argCloneBinaryStr)) |
|||
{ |
|||
if (outputToStdout(m_args[g_argCloneBinaryStr].as<OutputType>())) |
|||
{ |
|||
cout << "Clone Binary: " << endl; |
|||
cout << toHex(m_compiler->getCloneBytecode(_contract)) << endl; |
|||
} |
|||
if (outputToFile(m_args[g_argCloneBinaryStr].as<OutputType>())) |
|||
{ |
|||
ofstream outFile(_contract + ".clone_binary"); |
|||
outFile << toHex(m_compiler->getCloneBytecode(_contract)); |
|||
outFile.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CommandLineInterface::handleOpcode(string const& _contract) |
|||
{ |
|||
auto choice = m_args[g_argOpcodesStr].as<OutputType>(); |
|||
if (outputToStdout(choice)) |
|||
{ |
|||
cout << "Opcodes: " << endl; |
|||
cout << eth::disassemble(m_compiler->getBytecode(_contract)); |
|||
cout << endl; |
|||
} |
|||
|
|||
if (outputToFile(choice)) |
|||
{ |
|||
ofstream outFile(_contract + ".opcode"); |
|||
outFile << eth::disassemble(m_compiler->getBytecode(_contract)); |
|||
outFile.close(); |
|||
} |
|||
} |
|||
|
|||
void CommandLineInterface::handleBytecode(string const& _contract) |
|||
{ |
|||
if (m_args.count(g_argOpcodesStr)) |
|||
handleOpcode(_contract); |
|||
if (m_args.count(g_argBinaryStr) || m_args.count(g_argCloneBinaryStr)) |
|||
handleBinary(_contract); |
|||
} |
|||
|
|||
void CommandLineInterface::handleSignatureHashes(string const& _contract) |
|||
{ |
|||
if (!m_args.count(g_argSignatureHashes)) |
|||
return; |
|||
|
|||
string out; |
|||
for (auto const& it: m_compiler->getContractDefinition(_contract).getInterfaceFunctions()) |
|||
out += toHex(it.first.ref()) + ": " + it.second->externalSignature() + "\n"; |
|||
|
|||
auto choice = m_args[g_argSignatureHashes].as<OutputType>(); |
|||
if (outputToStdout(choice)) |
|||
cout << "Function signatures: " << endl << out; |
|||
|
|||
if (outputToFile(choice)) |
|||
{ |
|||
ofstream outFile(_contract + ".signatures"); |
|||
outFile << out; |
|||
outFile.close(); |
|||
} |
|||
} |
|||
|
|||
void CommandLineInterface::handleMeta(DocumentationType _type, string const& _contract) |
|||
{ |
|||
std::string argName; |
|||
std::string suffix; |
|||
std::string title; |
|||
switch(_type) |
|||
{ |
|||
case DocumentationType::ABIInterface: |
|||
argName = g_argAbiStr; |
|||
suffix = ".abi"; |
|||
title = "Contract JSON ABI"; |
|||
break; |
|||
case DocumentationType::ABISolidityInterface: |
|||
argName = g_argSolAbiStr; |
|||
suffix = ".sol"; |
|||
title = "Contract Solidity ABI"; |
|||
break; |
|||
case DocumentationType::NatspecUser: |
|||
argName = g_argNatspecUserStr; |
|||
suffix = ".docuser"; |
|||
title = "User Documentation"; |
|||
break; |
|||
case DocumentationType::NatspecDev: |
|||
argName = g_argNatspecDevStr; |
|||
suffix = ".docdev"; |
|||
title = "Developer Documentation"; |
|||
break; |
|||
default: |
|||
// should never happen
|
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation _type")); |
|||
} |
|||
|
|||
if (m_args.count(argName)) |
|||
{ |
|||
auto choice = m_args[argName].as<OutputType>(); |
|||
if (outputToStdout(choice)) |
|||
{ |
|||
cout << title << endl; |
|||
cout << m_compiler->getMetadata(_contract, _type) << endl; |
|||
} |
|||
|
|||
if (outputToFile(choice)) |
|||
{ |
|||
ofstream outFile(_contract + suffix); |
|||
outFile << m_compiler->getMetadata(_contract, _type); |
|||
outFile.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CommandLineInterface::handleGasEstimation(string const& _contract) |
|||
{ |
|||
using Gas = GasEstimator::GasConsumption; |
|||
if (!m_compiler->getAssemblyItems(_contract) && !m_compiler->getRuntimeAssemblyItems(_contract)) |
|||
return; |
|||
cout << "Gas estimation:" << endl; |
|||
if (eth::AssemblyItems const* items = m_compiler->getAssemblyItems(_contract)) |
|||
{ |
|||
Gas gas = GasEstimator::functionalEstimation(*items); |
|||
u256 bytecodeSize(m_compiler->getRuntimeBytecode(_contract).size()); |
|||
cout << "construction:" << endl; |
|||
cout << " " << gas << " + " << (bytecodeSize * eth::c_createDataGas) << " = "; |
|||
gas += bytecodeSize * eth::c_createDataGas; |
|||
cout << gas << endl; |
|||
} |
|||
if (eth::AssemblyItems const* items = m_compiler->getRuntimeAssemblyItems(_contract)) |
|||
{ |
|||
ContractDefinition const& contract = m_compiler->getContractDefinition(_contract); |
|||
cout << "external:" << endl; |
|||
for (auto it: contract.getInterfaceFunctions()) |
|||
{ |
|||
string sig = it.second->externalSignature(); |
|||
GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); |
|||
cout << " " << sig << ":\t" << gas << endl; |
|||
} |
|||
if (contract.getFallbackFunction()) |
|||
{ |
|||
GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID"); |
|||
cout << " fallback:\t" << gas << endl; |
|||
} |
|||
cout << "internal:" << endl; |
|||
for (auto const& it: contract.getDefinedFunctions()) |
|||
{ |
|||
if (it->isPartOfExternalInterface() || it->isConstructor()) |
|||
continue; |
|||
size_t entry = m_compiler->getFunctionEntryPoint(_contract, *it); |
|||
GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); |
|||
if (entry > 0) |
|||
gas = GasEstimator::functionalEstimation(*items, entry, *it); |
|||
FunctionType type(*it); |
|||
cout << " " << it->getName() << "("; |
|||
auto end = type.getParameterTypes().end(); |
|||
for (auto it = type.getParameterTypes().begin(); it != end; ++it) |
|||
cout << (*it)->toString() << (it + 1 == end ? "" : ","); |
|||
cout << "):\t" << gas << endl; |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool CommandLineInterface::parseArguments(int argc, char** argv) |
|||
{ |
|||
// Declare the supported options.
|
|||
po::options_description desc("Allowed options"); |
|||
desc.add_options() |
|||
("help", "Show help message and exit") |
|||
("version", "Show version and exit") |
|||
("optimize", po::value<bool>()->default_value(false), "Optimize bytecode") |
|||
("optimize-runs", po::value<unsigned>()->default_value(200), "Estimated number of contract runs for optimizer.") |
|||
("add-std", po::value<bool>()->default_value(false), "Add standard contracts") |
|||
("input-file", po::value<vector<string>>(), "input file") |
|||
( |
|||
"combined-json", |
|||
po::value<string>()->value_name(boost::join(g_combinedJsonArgs, ",")), |
|||
"Output a single json document containing the specified information, can be combined." |
|||
) |
|||
(g_argAstStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the AST of the contract.") |
|||
(g_argAstJson.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the AST of the contract in JSON format.") |
|||
(g_argAsmStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the EVM assembly of the contract.") |
|||
(g_argAsmJsonStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the EVM assembly of the contract in JSON format.") |
|||
(g_argOpcodesStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the Opcodes of the contract.") |
|||
(g_argBinaryStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract in binary (hexadecimal).") |
|||
(g_argCloneBinaryStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the clone contract in binary (hexadecimal).") |
|||
(g_argAbiStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract's JSON ABI interface.") |
|||
(g_argSolAbiStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract's Solidity ABI interface.") |
|||
(g_argSignatureHashes.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract's functions' signature hashes.") |
|||
(g_argGas.c_str(), |
|||
"Request to output an estimate for each function's maximal gas usage.") |
|||
(g_argNatspecUserStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract's Natspec user documentation.") |
|||
(g_argNatspecDevStr.c_str(), po::value<OutputType>()->value_name("stdout|file|both"), |
|||
"Request to output the contract's Natspec developer documentation."); |
|||
|
|||
// All positional options should be interpreted as input files
|
|||
po::positional_options_description p; |
|||
p.add("input-file", -1); |
|||
|
|||
// parse the compiler arguments
|
|||
try |
|||
{ |
|||
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args); |
|||
} |
|||
catch (po::error const& _exception) |
|||
{ |
|||
cerr << _exception.what() << endl; |
|||
return false; |
|||
} |
|||
if (m_args.count("combined-json")) |
|||
{ |
|||
vector<string> requests; |
|||
for (string const& item: boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(","))) |
|||
if (!g_combinedJsonArgs.count(item)) |
|||
{ |
|||
cerr << "Invalid option to --combined-json: " << item << endl; |
|||
return false; |
|||
} |
|||
} |
|||
po::notify(m_args); |
|||
|
|||
if (m_args.count("help")) |
|||
{ |
|||
cout << desc; |
|||
return false; |
|||
} |
|||
|
|||
if (m_args.count("version")) |
|||
{ |
|||
version(); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool CommandLineInterface::processInput() |
|||
{ |
|||
if (!m_args.count("input-file")) |
|||
{ |
|||
string s; |
|||
while (!cin.eof()) |
|||
{ |
|||
getline(cin, s); |
|||
m_sourceCodes["<stdin>"].append(s + '\n'); |
|||
} |
|||
} |
|||
else |
|||
for (string const& infile: m_args["input-file"].as<vector<string>>()) |
|||
{ |
|||
auto path = boost::filesystem::path(infile); |
|||
if (!boost::filesystem::exists(path)) |
|||
{ |
|||
cerr << "Skipping non existant input file \"" << infile << "\"" << endl; |
|||
continue; |
|||
} |
|||
|
|||
if (!boost::filesystem::is_regular_file(path)) |
|||
{ |
|||
cerr << "\"" << infile << "\" is not a valid file. Skipping" << endl; |
|||
continue; |
|||
} |
|||
|
|||
m_sourceCodes[infile] = dev::contentsString(infile); |
|||
} |
|||
|
|||
m_compiler.reset(new CompilerStack(m_args["add-std"].as<bool>())); |
|||
try |
|||
{ |
|||
for (auto const& sourceCode: m_sourceCodes) |
|||
m_compiler->addSource(sourceCode.first, sourceCode.second); |
|||
// TODO: Perhaps we should not compile unless requested
|
|||
bool optimize = m_args["optimize"].as<bool>(); |
|||
unsigned runs = m_args["optimize-runs"].as<unsigned>(); |
|||
m_compiler->compile(optimize, runs); |
|||
} |
|||
catch (ParserError const& _exception) |
|||
{ |
|||
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", *m_compiler); |
|||
return false; |
|||
} |
|||
catch (DeclarationError const& _exception) |
|||
{ |
|||
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", *m_compiler); |
|||
return false; |
|||
} |
|||
catch (TypeError const& _exception) |
|||
{ |
|||
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", *m_compiler); |
|||
return false; |
|||
} |
|||
catch (CompilerError const& _exception) |
|||
{ |
|||
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", *m_compiler); |
|||
return false; |
|||
} |
|||
catch (InternalCompilerError const& _exception) |
|||
{ |
|||
cerr << "Internal compiler error during compilation:" << endl |
|||
<< boost::diagnostic_information(_exception); |
|||
return false; |
|||
} |
|||
catch (DocstringParsingError const& _exception) |
|||
{ |
|||
cerr << "Documentation parsing error: " << *boost::get_error_info<errinfo_comment>(_exception) << endl; |
|||
return false; |
|||
} |
|||
catch (Exception const& _exception) |
|||
{ |
|||
cerr << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl; |
|||
return false; |
|||
} |
|||
catch (...) |
|||
{ |
|||
cerr << "Unknown exception during compilation." << endl; |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void CommandLineInterface::handleCombinedJSON() |
|||
{ |
|||
if (!m_args.count("combined-json")) |
|||
return; |
|||
|
|||
Json::Value output(Json::objectValue); |
|||
|
|||
set<string> requests; |
|||
boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(",")); |
|||
vector<string> contracts = m_compiler->getContractNames(); |
|||
|
|||
if (!contracts.empty()) |
|||
output["contracts"] = Json::Value(Json::objectValue); |
|||
for (string const& contractName: contracts) |
|||
{ |
|||
Json::Value contractData(Json::objectValue); |
|||
if (requests.count("sol-abi")) |
|||
contractData["sol-abi"] = m_compiler->getSolidityInterface(contractName); |
|||
if (requests.count("json-abi")) |
|||
contractData["json-abi"] = m_compiler->getInterface(contractName); |
|||
if (requests.count("binary")) |
|||
contractData["binary"] = toHex(m_compiler->getBytecode(contractName)); |
|||
if (requests.count("clone-binary")) |
|||
contractData["clone-binary"] = toHex(m_compiler->getCloneBytecode(contractName)); |
|||
if (requests.count("opcodes")) |
|||
contractData["opcodes"] = eth::disassemble(m_compiler->getBytecode(contractName)); |
|||
if (requests.count("asm")) |
|||
{ |
|||
ostringstream unused; |
|||
contractData["asm"] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true); |
|||
} |
|||
if (requests.count("natspec-dev")) |
|||
contractData["natspec-dev"] = m_compiler->getMetadata(contractName, DocumentationType::NatspecDev); |
|||
if (requests.count("natspec-user")) |
|||
contractData["natspec-user"] = m_compiler->getMetadata(contractName, DocumentationType::NatspecUser); |
|||
output["contracts"][contractName] = contractData; |
|||
} |
|||
|
|||
if (requests.count("ast")) |
|||
{ |
|||
output["sources"] = Json::Value(Json::objectValue); |
|||
for (auto const& sourceCode: m_sourceCodes) |
|||
{ |
|||
ASTJsonConverter converter(m_compiler->getAST(sourceCode.first)); |
|||
output["sources"][sourceCode.first] = Json::Value(Json::objectValue); |
|||
output["sources"][sourceCode.first]["AST"] = converter.json(); |
|||
} |
|||
} |
|||
cout << Json::FastWriter().write(output) << endl; |
|||
} |
|||
|
|||
void CommandLineInterface::handleAst(string const& _argStr) |
|||
{ |
|||
string title; |
|||
|
|||
if (_argStr == g_argAstStr) |
|||
title = "Syntax trees:"; |
|||
else if (_argStr == g_argAstJson) |
|||
title = "JSON AST:"; |
|||
else |
|||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal argStr for AST")); |
|||
|
|||
// do we need AST output?
|
|||
if (m_args.count(_argStr)) |
|||
{ |
|||
vector<ASTNode const*> asts; |
|||
for (auto const& sourceCode: m_sourceCodes) |
|||
asts.push_back(&m_compiler->getAST(sourceCode.first)); |
|||
map<ASTNode const*, eth::GasMeter::GasConsumption> gasCosts; |
|||
if (m_compiler->getRuntimeAssemblyItems()) |
|||
gasCosts = GasEstimator::breakToStatementLevel( |
|||
GasEstimator::structuralEstimation(*m_compiler->getRuntimeAssemblyItems(), asts), |
|||
asts |
|||
); |
|||
|
|||
auto choice = m_args[_argStr].as<OutputType>(); |
|||
if (outputToStdout(choice)) |
|||
{ |
|||
cout << title << endl << endl; |
|||
for (auto const& sourceCode: m_sourceCodes) |
|||
{ |
|||
cout << endl << "======= " << sourceCode.first << " =======" << endl; |
|||
if (_argStr == g_argAstStr) |
|||
{ |
|||
ASTPrinter printer( |
|||
m_compiler->getAST(sourceCode.first), |
|||
sourceCode.second, |
|||
gasCosts |
|||
); |
|||
printer.print(cout); |
|||
} |
|||
else |
|||
{ |
|||
ASTJsonConverter converter(m_compiler->getAST(sourceCode.first)); |
|||
converter.print(cout); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (outputToFile(choice)) |
|||
{ |
|||
for (auto const& sourceCode: m_sourceCodes) |
|||
{ |
|||
boost::filesystem::path p(sourceCode.first); |
|||
ofstream outFile(p.stem().string() + ".ast"); |
|||
if (_argStr == g_argAstStr) |
|||
{ |
|||
ASTPrinter printer(m_compiler->getAST(sourceCode.first), sourceCode.second); |
|||
printer.print(outFile); |
|||
} |
|||
else |
|||
{ |
|||
ASTJsonConverter converter(m_compiler->getAST(sourceCode.first)); |
|||
converter.print(outFile); |
|||
} |
|||
outFile.close(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CommandLineInterface::actOnInput() |
|||
{ |
|||
handleCombinedJSON(); |
|||
|
|||
// do we need AST output?
|
|||
handleAst(g_argAstStr); |
|||
handleAst(g_argAstJson); |
|||
|
|||
vector<string> contracts = m_compiler->getContractNames(); |
|||
for (string const& contract: contracts) |
|||
{ |
|||
if (needsHumanTargetedStdout(m_args)) |
|||
cout << endl << "======= " << contract << " =======" << endl; |
|||
|
|||
// do we need EVM assembly?
|
|||
if (m_args.count(g_argAsmStr) || m_args.count(g_argAsmJsonStr)) |
|||
{ |
|||
auto choice = m_args.count(g_argAsmStr) ? m_args[g_argAsmStr].as<OutputType>() : m_args[g_argAsmJsonStr].as<OutputType>(); |
|||
if (outputToStdout(choice)) |
|||
{ |
|||
cout << "EVM assembly:" << endl; |
|||
m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr)); |
|||
} |
|||
|
|||
if (outputToFile(choice)) |
|||
{ |
|||
ofstream outFile(contract + (m_args.count(g_argAsmJsonStr) ? "_evm.json" : ".evm")); |
|||
m_compiler->streamAssembly(outFile, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr)); |
|||
outFile.close(); |
|||
} |
|||
} |
|||
|
|||
if (m_args.count(g_argGas)) |
|||
handleGasEstimation(contract); |
|||
|
|||
handleBytecode(contract); |
|||
handleSignatureHashes(contract); |
|||
handleMeta(DocumentationType::ABIInterface, contract); |
|||
handleMeta(DocumentationType::ABISolidityInterface, contract); |
|||
handleMeta(DocumentationType::NatspecDev, contract); |
|||
handleMeta(DocumentationType::NatspecUser, contract); |
|||
} // end of contracts iteration
|
|||
} |
|||
|
|||
} |
|||
} |
@ -1,75 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris <lefteris@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity command line interface. |
|||
*/ |
|||
#pragma once |
|||
|
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <memory> |
|||
#include <boost/program_options.hpp> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
//forward declaration
|
|||
enum class DocumentationType: uint8_t; |
|||
|
|||
enum class OutputType: uint8_t |
|||
{ |
|||
STDOUT, |
|||
FILE, |
|||
BOTH |
|||
}; |
|||
|
|||
class CommandLineInterface |
|||
{ |
|||
public: |
|||
CommandLineInterface() {} |
|||
|
|||
/// Parse command line arguments and return false if we should not continue
|
|||
bool parseArguments(int argc, char** argv); |
|||
/// Parse the files and create source code objects
|
|||
bool processInput(); |
|||
/// Perform actions on the input depending on provided compiler arguments
|
|||
void actOnInput(); |
|||
|
|||
private: |
|||
void handleCombinedJSON(); |
|||
void handleAst(std::string const& _argStr); |
|||
void handleBinary(std::string const& _contract); |
|||
void handleOpcode(std::string const& _contract); |
|||
void handleBytecode(std::string const& _contract); |
|||
void handleSignatureHashes(std::string const& _contract); |
|||
void handleMeta(DocumentationType _type, |
|||
std::string const& _contract); |
|||
void handleGasEstimation(std::string const& _contract); |
|||
|
|||
/// Compiler arguments variable map
|
|||
boost::program_options::variables_map m_args; |
|||
/// map of input files to source code strings
|
|||
std::map<std::string, std::string> m_sourceCodes; |
|||
/// Solidity compiler stack
|
|||
std::unique_ptr<dev::solidity::CompilerStack> m_compiler; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,70 +0,0 @@ |
|||
FROM ubuntu:14.04 |
|||
|
|||
ENV DEBIAN_FRONTEND noninteractive |
|||
RUN apt-get update |
|||
RUN apt-get upgrade -y |
|||
|
|||
# Ethereum dependencies |
|||
RUN apt-get install -qy build-essential git cmake libcurl4-openssl-dev wget |
|||
RUN apt-get install -qy automake libtool yasm scons |
|||
|
|||
RUN useradd -ms /bin/bash user |
|||
USER user |
|||
ENV HOME /home/user |
|||
WORKDIR /home/user |
|||
|
|||
# Emscripten SDK |
|||
RUN wget -c https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz |
|||
RUN tar xzf emsdk-portable.tar.gz |
|||
WORKDIR /home/user/emsdk_portable |
|||
RUN ./emsdk update && ./emsdk install latest && ./emsdk activate latest |
|||
ENV PATH $PATH:/home/user/emsdk_portable:/home/user/emsdk_portable/clang/fastcomp/build_master_64/bin:/home/user/emsdk_portable/emscripten/master |
|||
|
|||
USER root |
|||
RUN apt-get install -qy nodejs |
|||
USER user |
|||
RUN sed -i "s/NODE_JS = 'node'/NODE_JS = 'nodejs'/g" ~/.emscripten |
|||
|
|||
# CryptoPP |
|||
WORKDIR /home/user |
|||
RUN git clone https://github.com/mmoss/cryptopp.git |
|||
WORKDIR /home/user/cryptopp |
|||
RUN emcmake cmake -DCRYPTOPP_LIBRARY_TYPE=STATIC -DCRYPTOPP_RUNTIME_TYPE=STATIC && emmake make -j 4 |
|||
RUN ln -s . src/cryptopp |
|||
|
|||
# Boost |
|||
WORKDIR /home/user |
|||
RUN wget 'http://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2F1.57.0%2F&ts=1421887207&use_mirror=cznic' -O boost_1_57_0.tar.bz2 |
|||
RUN tar xjf boost_1_57_0.tar.bz2 |
|||
WORKDIR /home/user/boost_1_57_0 |
|||
RUN ./bootstrap.sh --with-libraries=thread,system,regex |
|||
RUN sed -i 's/using gcc ;/using gcc : : \/home\/user\/emsdk_portable\/emscripten\/master\/em++ ;/g' ./project-config.jam |
|||
RUN sed -i 's/$(archiver\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emar/g' ./tools/build/src/tools/gcc.jam |
|||
RUN sed -i 's/$(ranlib\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emranlib/g' ./tools/build/src/tools/gcc.jam |
|||
RUN ./b2 link=static variant=release threading=single runtime-link=static thread system regex |
|||
|
|||
# Json-CPP |
|||
WORKDIR /home/user |
|||
RUN git clone https://github.com/open-source-parsers/jsoncpp.git |
|||
WORKDIR /home/user/jsoncpp |
|||
RUN emcmake cmake -DJSONCPP_LIB_BUILD_STATIC=ON -DJSONCPP_LIB_BUILD_SHARED=OFF -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF -G "Unix Makefiles" . |
|||
RUN emmake make |
|||
|
|||
## Build soljs |
|||
WORKDIR /home/user |
|||
ADD https://api.github.com/repos/ethereum/cpp-ethereum/git/refs/heads/develop unused.txt |
|||
RUN git clone --depth=1 https://github.com/ethereum/cpp-ethereum |
|||
WORKDIR /home/user/cpp-ethereum |
|||
RUN git config --global user.email "me@example.com" |
|||
RUN git config --global user.name "Jane Doe" |
|||
ADD https://api.github.com/repos/chriseth/cpp-ethereum/git/refs/heads/solidity-js unused2.txt |
|||
RUN git remote add -f solidityjs https://github.com/chriseth/cpp-ethereum |
|||
# TODO this should be a proper merge but somehow causes problems |
|||
# NOTE that we only get the latest commit of that branch |
|||
RUN git cherry-pick solidityjs/solidity-js |
|||
RUN emcmake cmake -DMINER=0 -DETHKEY=0 -DSERPENT=0 -DTESTS=0 -DETHASHCL=0 -DJSCONSOLE=0 -DEVMJIT=0 -DETH_STATIC=1 -DSOLIDITY=1 -DGUI=0 -DCMAKE_CXX_COMPILER=/home/user/emsdk_portable/emscripten/master/em++ -DCMAKE_C_COMPILER=/home/user/emsdk_portable/emscripten/master/emcc |
|||
RUN emmake make -j 6 soljson |
|||
|
|||
WORKDIR /home/user/cpp-ethereum/solc |
|||
ENTRYPOINT cat soljson.js |
|||
|
@ -1,196 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* JSON interface for the solidity compiler to be used from Javascript. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <iostream> |
|||
#include <json/json.h> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <libevmcore/Params.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/ASTPrinter.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
#include <libsolidity/ASTJsonConverter.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace solidity; |
|||
|
|||
string formatError(Exception const& _exception, string const& _name, CompilerStack const& _compiler) |
|||
{ |
|||
ostringstream errorOutput; |
|||
SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _compiler); |
|||
|
|||
Json::Value output(Json::objectValue); |
|||
output["error"] = errorOutput.str(); |
|||
return Json::FastWriter().write(output); |
|||
} |
|||
|
|||
Json::Value functionHashes(ContractDefinition const& _contract) |
|||
{ |
|||
Json::Value functionHashes(Json::objectValue); |
|||
for (auto const& it: _contract.getInterfaceFunctions()) |
|||
functionHashes[it.second->externalSignature()] = toHex(it.first.ref()); |
|||
return functionHashes; |
|||
} |
|||
|
|||
Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) |
|||
{ |
|||
if (_gas.isInfinite || _gas.value > std::numeric_limits<Json::LargestUInt>::max()) |
|||
return Json::Value(Json::nullValue); |
|||
else |
|||
return Json::Value(Json::LargestUInt(_gas.value)); |
|||
} |
|||
|
|||
Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) |
|||
{ |
|||
Json::Value gasEstimates(Json::objectValue); |
|||
using Gas = GasEstimator::GasConsumption; |
|||
if (!_compiler.getAssemblyItems(_contract) && !_compiler.getRuntimeAssemblyItems(_contract)) |
|||
return gasEstimates; |
|||
if (eth::AssemblyItems const* items = _compiler.getAssemblyItems(_contract)) |
|||
{ |
|||
Gas gas = GasEstimator::functionalEstimation(*items); |
|||
u256 bytecodeSize(_compiler.getRuntimeBytecode(_contract).size()); |
|||
Json::Value creationGas(Json::arrayValue); |
|||
creationGas[0] = gasToJson(gas); |
|||
creationGas[1] = gasToJson(bytecodeSize * eth::c_createDataGas); |
|||
gasEstimates["creation"] = creationGas; |
|||
} |
|||
if (eth::AssemblyItems const* items = _compiler.getRuntimeAssemblyItems(_contract)) |
|||
{ |
|||
ContractDefinition const& contract = _compiler.getContractDefinition(_contract); |
|||
Json::Value externalFunctions(Json::objectValue); |
|||
for (auto it: contract.getInterfaceFunctions()) |
|||
{ |
|||
string sig = it.second->externalSignature(); |
|||
externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); |
|||
} |
|||
if (contract.getFallbackFunction()) |
|||
externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); |
|||
gasEstimates["external"] = externalFunctions; |
|||
Json::Value internalFunctions(Json::objectValue); |
|||
for (auto const& it: contract.getDefinedFunctions()) |
|||
{ |
|||
if (it->isPartOfExternalInterface() || it->isConstructor()) |
|||
continue; |
|||
size_t entry = _compiler.getFunctionEntryPoint(_contract, *it); |
|||
GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); |
|||
if (entry > 0) |
|||
gas = GasEstimator::functionalEstimation(*items, entry, *it); |
|||
FunctionType type(*it); |
|||
string sig = it->getName() + "("; |
|||
auto end = type.getParameterTypes().end(); |
|||
for (auto it = type.getParameterTypes().begin(); it != end; ++it) |
|||
sig += (*it)->toString() + (it + 1 == end ? "" : ","); |
|||
sig += ")"; |
|||
internalFunctions[sig] = gasToJson(gas); |
|||
} |
|||
gasEstimates["internal"] = internalFunctions; |
|||
} |
|||
return gasEstimates; |
|||
} |
|||
|
|||
string compile(string _input, bool _optimize) |
|||
{ |
|||
StringMap sources; |
|||
sources[""] = _input; |
|||
|
|||
Json::Value output(Json::objectValue); |
|||
CompilerStack compiler; |
|||
try |
|||
{ |
|||
compiler.compile(_input, _optimize); |
|||
} |
|||
catch (ParserError const& exception) |
|||
{ |
|||
return formatError(exception, "Parser error", compiler); |
|||
} |
|||
catch (DeclarationError const& exception) |
|||
{ |
|||
return formatError(exception, "Declaration error", compiler); |
|||
} |
|||
catch (TypeError const& exception) |
|||
{ |
|||
return formatError(exception, "Type error", compiler); |
|||
} |
|||
catch (CompilerError const& exception) |
|||
{ |
|||
return formatError(exception, "Compiler error", compiler); |
|||
} |
|||
catch (InternalCompilerError const& exception) |
|||
{ |
|||
return formatError(exception, "Internal compiler error", compiler); |
|||
} |
|||
catch (DocstringParsingError const& exception) |
|||
{ |
|||
return formatError(exception, "Documentation parsing error", compiler); |
|||
} |
|||
catch (Exception const& exception) |
|||
{ |
|||
output["error"] = "Exception during compilation: " + boost::diagnostic_information(exception); |
|||
return Json::FastWriter().write(output); |
|||
} |
|||
catch (...) |
|||
{ |
|||
output["error"] = "Unknown exception during compilation."; |
|||
return Json::FastWriter().write(output); |
|||
} |
|||
|
|||
output["contracts"] = Json::Value(Json::objectValue); |
|||
for (string const& contractName: compiler.getContractNames()) |
|||
{ |
|||
Json::Value contractData(Json::objectValue); |
|||
contractData["solidity_interface"] = compiler.getSolidityInterface(contractName); |
|||
contractData["interface"] = compiler.getInterface(contractName); |
|||
contractData["bytecode"] = toHex(compiler.getBytecode(contractName)); |
|||
contractData["opcodes"] = eth::disassemble(compiler.getBytecode(contractName)); |
|||
contractData["functionHashes"] = functionHashes(compiler.getContractDefinition(contractName)); |
|||
contractData["gasEstimates"] = estimateGas(compiler, contractName); |
|||
ostringstream unused; |
|||
contractData["assembly"] = compiler.streamAssembly(unused, contractName, sources, true); |
|||
output["contracts"][contractName] = contractData; |
|||
} |
|||
|
|||
output["sources"] = Json::Value(Json::objectValue); |
|||
output["sources"][""] = Json::Value(Json::objectValue); |
|||
output["sources"][""]["AST"] = ASTJsonConverter(compiler.getAST("")).json(); |
|||
|
|||
return Json::FastWriter().write(output); |
|||
} |
|||
|
|||
static string outputBuffer; |
|||
|
|||
extern "C" |
|||
{ |
|||
extern char const* compileJSON(char const* _input, bool _optimize) |
|||
{ |
|||
outputBuffer = compile(_input, _optimize); |
|||
return outputBuffer.c_str(); |
|||
} |
|||
} |
@ -1,35 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Solidity commandline compiler. |
|||
*/ |
|||
|
|||
#include "CommandLineInterface.h" |
|||
|
|||
int main(int argc, char** argv) |
|||
{ |
|||
dev::solidity::CommandLineInterface cli; |
|||
if (!cli.parseArguments(argc, argv)) |
|||
return 1; |
|||
if (!cli.processInput()) |
|||
return 1; |
|||
cli.actOnInput(); |
|||
|
|||
return 0; |
|||
} |
@ -1,497 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Tests for a fixed fee registrar contract. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <tuple> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libdevcore/Hash.h> |
|||
#include <libethcore/ABI.h> |
|||
#include <test/libsolidity/solidityExecutionFramework.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
|
|||
static char const* registrarCode = R"DELIMITER( |
|||
//sol
|
|||
|
|||
contract NameRegister { |
|||
function addr(string _name) constant returns (address o_owner); |
|||
function name(address _owner) constant returns (string o_name); |
|||
} |
|||
|
|||
contract Registrar is NameRegister { |
|||
event Changed(string indexed name); |
|||
event PrimaryChanged(string indexed name, address indexed addr); |
|||
|
|||
function owner(string _name) constant returns (address o_owner); |
|||
function addr(string _name) constant returns (address o_address); |
|||
function subRegistrar(string _name) constant returns (address o_subRegistrar); |
|||
function content(string _name) constant returns (bytes32 o_content); |
|||
|
|||
function name(address _owner) constant returns (string o_name); |
|||
} |
|||
|
|||
contract AuctionSystem { |
|||
event AuctionEnded(string indexed _name, address _winner); |
|||
event NewBid(string indexed _name, address _bidder, uint _value); |
|||
|
|||
/// Function that is called once an auction ends.
|
|||
function onAuctionEnd(string _name) internal; |
|||
|
|||
function bid(string _name, address _bidder, uint _value) internal { |
|||
var auction = m_auctions[_name]; |
|||
if (auction.endDate > 0 && now > auction.endDate) |
|||
{ |
|||
AuctionEnded(_name, auction.highestBidder); |
|||
onAuctionEnd(_name); |
|||
delete m_auctions[_name]; |
|||
return; |
|||
} |
|||
if (msg.value > auction.highestBid) |
|||
{ |
|||
// new bid on auction
|
|||
auction.secondHighestBid = auction.highestBid; |
|||
auction.sumOfBids += _value; |
|||
auction.highestBid = _value; |
|||
auction.highestBidder = _bidder; |
|||
auction.endDate = now + c_biddingTime; |
|||
|
|||
NewBid(_name, _bidder, _value); |
|||
} |
|||
} |
|||
|
|||
uint constant c_biddingTime = 7 days; |
|||
|
|||
struct Auction { |
|||
address highestBidder; |
|||
uint highestBid; |
|||
uint secondHighestBid; |
|||
uint sumOfBids; |
|||
uint endDate; |
|||
} |
|||
mapping(string => Auction) m_auctions; |
|||
} |
|||
|
|||
contract GlobalRegistrar is Registrar, AuctionSystem { |
|||
struct Record { |
|||
address owner; |
|||
address primary; |
|||
address subRegistrar; |
|||
bytes32 content; |
|||
uint renewalDate; |
|||
} |
|||
|
|||
uint constant c_renewalInterval = 1 years; |
|||
uint constant c_freeBytes = 12; |
|||
|
|||
function Registrar() { |
|||
// TODO: Populate with hall-of-fame.
|
|||
} |
|||
|
|||
function() { |
|||
// prevent people from just sending funds to the registrar
|
|||
__throw(); |
|||
} |
|||
|
|||
function onAuctionEnd(string _name) internal { |
|||
var auction = m_auctions[_name]; |
|||
var record = m_toRecord[_name]; |
|||
if (record.owner != 0) |
|||
record.owner.send(auction.sumOfBids - auction.highestBid / 100); |
|||
else |
|||
auction.highestBidder.send(auction.highestBid - auction.secondHighestBid); |
|||
record.renewalDate = now + c_renewalInterval; |
|||
record.owner = auction.highestBidder; |
|||
Changed(_name); |
|||
} |
|||
|
|||
function reserve(string _name) external { |
|||
if (bytes(_name).length == 0) |
|||
__throw(); |
|||
bool needAuction = requiresAuction(_name); |
|||
if (needAuction) |
|||
{ |
|||
if (now < m_toRecord[_name].renewalDate) |
|||
__throw(); |
|||
bid(_name, msg.sender, msg.value); |
|||
} |
|||
else |
|||
{ |
|||
Record record = m_toRecord[_name]; |
|||
if (record.owner != 0) |
|||
__throw(); |
|||
m_toRecord[_name].owner = msg.sender; |
|||
Changed(_name); |
|||
} |
|||
} |
|||
|
|||
function requiresAuction(string _name) internal returns (bool) { |
|||
return bytes(_name).length < c_freeBytes; |
|||
} |
|||
|
|||
modifier onlyrecordowner(string _name) { if (m_toRecord[_name].owner == msg.sender) _ } |
|||
|
|||
function transfer(string _name, address _newOwner) onlyrecordowner(_name) { |
|||
m_toRecord[_name].owner = _newOwner; |
|||
Changed(_name); |
|||
} |
|||
|
|||
function disown(string _name) onlyrecordowner(_name) { |
|||
if (stringsEqual(m_toName[m_toRecord[_name].primary], _name)) |
|||
{ |
|||
PrimaryChanged(_name, m_toRecord[_name].primary); |
|||
m_toName[m_toRecord[_name].primary] = ""; |
|||
} |
|||
delete m_toRecord[_name]; |
|||
Changed(_name); |
|||
} |
|||
|
|||
function setAddress(string _name, address _a, bool _primary) onlyrecordowner(_name) { |
|||
m_toRecord[_name].primary = _a; |
|||
if (_primary) |
|||
{ |
|||
PrimaryChanged(_name, _a); |
|||
m_toName[_a] = _name; |
|||
} |
|||
Changed(_name); |
|||
} |
|||
function setSubRegistrar(string _name, address _registrar) onlyrecordowner(_name) { |
|||
m_toRecord[_name].subRegistrar = _registrar; |
|||
Changed(_name); |
|||
} |
|||
function setContent(string _name, bytes32 _content) onlyrecordowner(_name) { |
|||
m_toRecord[_name].content = _content; |
|||
Changed(_name); |
|||
} |
|||
|
|||
function stringsEqual(string storage _a, string memory _b) internal returns (bool) { |
|||
bytes storage a = bytes(_a); |
|||
bytes memory b = bytes(_b); |
|||
if (a.length != b.length) |
|||
return false; |
|||
// @todo unroll this loop
|
|||
for (uint i = 0; i < a.length; i ++) |
|||
if (a[i] != b[i]) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
function owner(string _name) constant returns (address) { return m_toRecord[_name].owner; } |
|||
function addr(string _name) constant returns (address) { return m_toRecord[_name].primary; } |
|||
function subRegistrar(string _name) constant returns (address) { return m_toRecord[_name].subRegistrar; } |
|||
function content(string _name) constant returns (bytes32) { return m_toRecord[_name].content; } |
|||
function name(address _addr) constant returns (string o_name) { return m_toName[_addr]; } |
|||
|
|||
function __throw() internal { |
|||
// workaround until we have "throw"
|
|||
uint[] x; x[1]; |
|||
} |
|||
|
|||
mapping (address => string) m_toName; |
|||
mapping (string => Record) m_toRecord; |
|||
} |
|||
)DELIMITER"; |
|||
|
|||
static unique_ptr<bytes> s_compiledRegistrar; |
|||
|
|||
class AuctionRegistrarTestFramework: public ExecutionFramework |
|||
{ |
|||
protected: |
|||
void deployRegistrar() |
|||
{ |
|||
if (!s_compiledRegistrar) |
|||
{ |
|||
m_optimize = true; |
|||
m_compiler.reset(false, m_addStandardSources); |
|||
m_compiler.addSource("", registrarCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); |
|||
s_compiledRegistrar.reset(new bytes(m_compiler.getBytecode("GlobalRegistrar"))); |
|||
} |
|||
sendMessage(*s_compiledRegistrar, true); |
|||
BOOST_REQUIRE(!m_output.empty()); |
|||
} |
|||
|
|||
using ContractInterface = ExecutionFramework::ContractInterface; |
|||
class RegistrarInterface: public ContractInterface |
|||
{ |
|||
public: |
|||
RegistrarInterface(ExecutionFramework& _framework): ContractInterface(_framework) {} |
|||
void reserve(string const& _name) |
|||
{ |
|||
callString("reserve", _name); |
|||
} |
|||
u160 owner(string const& _name) |
|||
{ |
|||
return callStringReturnsAddress("owner", _name); |
|||
} |
|||
void setAddress(string const& _name, u160 const& _address, bool _primary) |
|||
{ |
|||
callStringAddressBool("setAddress", _name, _address, _primary); |
|||
} |
|||
u160 addr(string const& _name) |
|||
{ |
|||
return callStringReturnsAddress("addr", _name); |
|||
} |
|||
string name(u160 const& _addr) |
|||
{ |
|||
return callAddressReturnsString("name", _addr); |
|||
} |
|||
void setSubRegistrar(string const& _name, u160 const& _address) |
|||
{ |
|||
callStringAddress("setSubRegistrar", _name, _address); |
|||
} |
|||
u160 subRegistrar(string const& _name) |
|||
{ |
|||
return callStringReturnsAddress("subRegistrar", _name); |
|||
} |
|||
void setContent(string const& _name, h256 const& _content) |
|||
{ |
|||
callStringBytes32("setContent", _name, _content); |
|||
} |
|||
h256 content(string const& _name) |
|||
{ |
|||
return callStringReturnsBytes32("content", _name); |
|||
} |
|||
void transfer(string const& _name, u160 const& _target) |
|||
{ |
|||
return callStringAddress("transfer", _name, _target); |
|||
} |
|||
void disown(string const& _name) |
|||
{ |
|||
return callString("disown", _name); |
|||
} |
|||
}; |
|||
|
|||
u256 const m_biddingTime = u256(7 * 24 * 3600); |
|||
u256 const m_renewalInterval = u256(365 * 24 * 3600); |
|||
}; |
|||
|
|||
} |
|||
|
|||
/// This is a test suite that tests optimised code!
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityAuctionRegistrar, AuctionRegistrarTestFramework) |
|||
|
|||
BOOST_AUTO_TEST_CASE(creation) |
|||
{ |
|||
deployRegistrar(); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(reserve) |
|||
{ |
|||
// Test that reserving works for long strings
|
|||
deployRegistrar(); |
|||
vector<string> names{"abcabcabcabcabc", "defdefdefdefdef", "ghighighighighighighighighighighighighighighi"}; |
|||
m_sender = Address(0x123); |
|||
|
|||
RegistrarInterface registrar(*this); |
|||
|
|||
// should not work
|
|||
registrar.reserve(""); |
|||
BOOST_CHECK_EQUAL(registrar.owner(""), u160(0)); |
|||
|
|||
for (auto const& name: names) |
|||
{ |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(double_reserve_long) |
|||
{ |
|||
// Test that it is not possible to re-reserve from a different address.
|
|||
deployRegistrar(); |
|||
string name = "abcabcabcabcabcabcabcabcabcabca"; |
|||
m_sender = Address(0x123); |
|||
RegistrarInterface registrar(*this); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); |
|||
|
|||
m_sender = Address(0x124); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(properties) |
|||
{ |
|||
// Test setting and retrieving the various properties works.
|
|||
deployRegistrar(); |
|||
RegistrarInterface registrar(*this); |
|||
string names[] = {"abcaeouoeuaoeuaoeu", "defncboagufra,fui", "ghagpyajfbcuajouhaeoi"}; |
|||
size_t addr = 0x9872543; |
|||
for (string const& name: names) |
|||
{ |
|||
addr++; |
|||
size_t sender = addr + 10007; |
|||
m_sender = Address(sender); |
|||
// setting by sender works
|
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(sender)); |
|||
registrar.setAddress(name, addr, true); |
|||
BOOST_CHECK_EQUAL(registrar.addr(name), u160(addr)); |
|||
registrar.setSubRegistrar(name, addr + 20); |
|||
BOOST_CHECK_EQUAL(registrar.subRegistrar(name), u160(addr + 20)); |
|||
registrar.setContent(name, h256(u256(addr + 90))); |
|||
BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90))); |
|||
|
|||
// but not by someone else
|
|||
m_sender = Address(h256(addr + 10007 - 1)); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(sender)); |
|||
registrar.setAddress(name, addr + 1, true); |
|||
BOOST_CHECK_EQUAL(registrar.addr(name), u160(addr)); |
|||
registrar.setSubRegistrar(name, addr + 20 + 1); |
|||
BOOST_CHECK_EQUAL(registrar.subRegistrar(name), u160(addr + 20)); |
|||
registrar.setContent(name, h256(u256(addr + 90 + 1))); |
|||
BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90))); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(transfer) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "abcaoeguaoucaeoduceo"; |
|||
m_sender = Address(0x123); |
|||
RegistrarInterface registrar(*this); |
|||
registrar.reserve(name); |
|||
registrar.setContent(name, h256(u256(123))); |
|||
registrar.transfer(name, u160(555)); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), u160(555)); |
|||
BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(123))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(disown) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "abcaoeguaoucaeoduceo"; |
|||
m_sender = Address(0x123); |
|||
RegistrarInterface registrar(*this); |
|||
registrar.reserve(name); |
|||
registrar.setContent(name, h256(u256(123))); |
|||
registrar.setAddress(name, u160(124), true); |
|||
registrar.setSubRegistrar(name, u160(125)); |
|||
BOOST_CHECK_EQUAL(registrar.name(u160(124)), name); |
|||
|
|||
// someone else tries disowning
|
|||
m_sender = Address(0x128); |
|||
registrar.disown(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); |
|||
|
|||
m_sender = Address(0x123); |
|||
registrar.disown(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0); |
|||
BOOST_CHECK_EQUAL(registrar.addr(name), 0); |
|||
BOOST_CHECK_EQUAL(registrar.subRegistrar(name), 0); |
|||
BOOST_CHECK_EQUAL(registrar.content(name), h256()); |
|||
BOOST_CHECK_EQUAL(registrar.name(u160(124)), ""); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(auction_simple) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "x"; |
|||
m_sender = Address(0x123); |
|||
RegistrarInterface registrar(*this); |
|||
// initiate auction
|
|||
registrar.setNextValue(8); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0); |
|||
// "wait" until auction end
|
|||
m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime + 10); |
|||
// trigger auction again
|
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(auction_bidding) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "x"; |
|||
m_sender = Address(0x123); |
|||
RegistrarInterface registrar(*this); |
|||
// initiate auction
|
|||
registrar.setNextValue(8); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0); |
|||
// overbid self
|
|||
m_envInfo.setTimestamp(m_biddingTime - 10); |
|||
registrar.setNextValue(12); |
|||
registrar.reserve(name); |
|||
// another bid by someone else
|
|||
m_sender = Address(0x124); |
|||
m_envInfo.setTimestamp(2 * m_biddingTime - 50); |
|||
registrar.setNextValue(13); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0); |
|||
// end auction by first bidder (which is not highest) trying to overbid again (too late)
|
|||
m_sender = Address(0x123); |
|||
m_envInfo.setTimestamp(4 * m_biddingTime); |
|||
registrar.setNextValue(20); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x124); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(auction_renewal) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "x"; |
|||
RegistrarInterface registrar(*this); |
|||
// register name by auction
|
|||
m_sender = Address(0x123); |
|||
registrar.setNextValue(8); |
|||
registrar.reserve(name); |
|||
m_envInfo.setTimestamp(4 * m_biddingTime); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); |
|||
|
|||
// try to re-register before interval end
|
|||
m_sender = Address(0x222); |
|||
registrar.setNextValue(80); |
|||
m_envInfo.setTimestamp(m_envInfo.timestamp() + m_renewalInterval - 1); |
|||
registrar.reserve(name); |
|||
m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime); |
|||
// if there is a bug in the renewal logic, this would transfer the ownership to 0x222,
|
|||
// but if there is no bug, this will initiate the auction, albeit with a zero bid
|
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); |
|||
|
|||
m_envInfo.setTimestamp(m_envInfo.timestamp() + 2); |
|||
registrar.setNextValue(80); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); |
|||
m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime + 2); |
|||
registrar.reserve(name); |
|||
BOOST_CHECK_EQUAL(registrar.owner(name), 0x222); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,5 +0,0 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
|
|||
aux_source_directory(. SRCS) |
|||
|
|||
add_sources(${SRCS}) |
@ -1,242 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Tests for a fixed fee registrar contract. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <tuple> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libdevcore/Hash.h> |
|||
#include <test/libsolidity/solidityExecutionFramework.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
|
|||
static char const* registrarCode = R"DELIMITER( |
|||
//sol FixedFeeRegistrar
|
|||
// Simple global registrar with fixed-fee reservations.
|
|||
// @authors:
|
|||
// Gav Wood <g@ethdev.com>
|
|||
|
|||
contract Registrar { |
|||
event Changed(string indexed name); |
|||
|
|||
function owner(string _name) constant returns (address o_owner); |
|||
function addr(string _name) constant returns (address o_address); |
|||
function subRegistrar(string _name) constant returns (address o_subRegistrar); |
|||
function content(string _name) constant returns (bytes32 o_content); |
|||
} |
|||
|
|||
contract FixedFeeRegistrar is Registrar { |
|||
struct Record { |
|||
address addr; |
|||
address subRegistrar; |
|||
bytes32 content; |
|||
address owner; |
|||
} |
|||
|
|||
modifier onlyrecordowner(string _name) { if (m_record(_name).owner == msg.sender) _ } |
|||
|
|||
function reserve(string _name) { |
|||
Record rec = m_record(_name); |
|||
if (rec.owner == 0 && msg.value >= c_fee) { |
|||
rec.owner = msg.sender; |
|||
Changed(_name); |
|||
} |
|||
} |
|||
function disown(string _name, address _refund) onlyrecordowner(_name) { |
|||
delete m_recordData[uint(sha3(_name)) / 8]; |
|||
_refund.send(c_fee); |
|||
Changed(_name); |
|||
} |
|||
function transfer(string _name, address _newOwner) onlyrecordowner(_name) { |
|||
m_record(_name).owner = _newOwner; |
|||
Changed(_name); |
|||
} |
|||
function setAddr(string _name, address _a) onlyrecordowner(_name) { |
|||
m_record(_name).addr = _a; |
|||
Changed(_name); |
|||
} |
|||
function setSubRegistrar(string _name, address _registrar) onlyrecordowner(_name) { |
|||
m_record(_name).subRegistrar = _registrar; |
|||
Changed(_name); |
|||
} |
|||
function setContent(string _name, bytes32 _content) onlyrecordowner(_name) { |
|||
m_record(_name).content = _content; |
|||
Changed(_name); |
|||
} |
|||
|
|||
function record(string _name) constant returns (address o_addr, address o_subRegistrar, bytes32 o_content, address o_owner) { |
|||
Record rec = m_record(_name); |
|||
o_addr = rec.addr; |
|||
o_subRegistrar = rec.subRegistrar; |
|||
o_content = rec.content; |
|||
o_owner = rec.owner; |
|||
} |
|||
function addr(string _name) constant returns (address) { return m_record(_name).addr; } |
|||
function subRegistrar(string _name) constant returns (address) { return m_record(_name).subRegistrar; } |
|||
function content(string _name) constant returns (bytes32) { return m_record(_name).content; } |
|||
function owner(string _name) constant returns (address) { return m_record(_name).owner; } |
|||
|
|||
Record[2**253] m_recordData; |
|||
function m_record(string _name) constant internal returns (Record storage o_record) { |
|||
return m_recordData[uint(sha3(_name)) / 8]; |
|||
} |
|||
uint constant c_fee = 69 ether; |
|||
} |
|||
)DELIMITER"; |
|||
|
|||
static unique_ptr<bytes> s_compiledRegistrar; |
|||
|
|||
class RegistrarTestFramework: public ExecutionFramework |
|||
{ |
|||
protected: |
|||
void deployRegistrar() |
|||
{ |
|||
if (!s_compiledRegistrar) |
|||
{ |
|||
m_optimize = true; |
|||
m_compiler.reset(false, m_addStandardSources); |
|||
m_compiler.addSource("", registrarCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); |
|||
s_compiledRegistrar.reset(new bytes(m_compiler.getBytecode("FixedFeeRegistrar"))); |
|||
} |
|||
sendMessage(*s_compiledRegistrar, true); |
|||
BOOST_REQUIRE(!m_output.empty()); |
|||
} |
|||
u256 const m_fee = u256("69000000000000000000"); |
|||
}; |
|||
|
|||
} |
|||
|
|||
/// This is a test suite that tests optimised code!
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityFixedFeeRegistrar, RegistrarTestFramework) |
|||
|
|||
BOOST_AUTO_TEST_CASE(creation) |
|||
{ |
|||
deployRegistrar(); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(reserve) |
|||
{ |
|||
// Test that reserving works and fee is taken into account.
|
|||
deployRegistrar(); |
|||
string name[] = {"abc", "def", "ghi"}; |
|||
m_sender = Address(0x123); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name[0])) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[0])) == encodeArgs(h256(0x123))); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee + 1, encodeDyn(name[1])) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[1])) == encodeArgs(h256(0x123))); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee - 1, encodeDyn(name[2])) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[2])) == encodeArgs(h256(0))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(double_reserve) |
|||
{ |
|||
// Test that it is not possible to re-reserve from a different address.
|
|||
deployRegistrar(); |
|||
string name = "abc"; |
|||
m_sender = Address(0x123); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(h256(0x123))); |
|||
|
|||
m_sender = Address(0x124); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(h256(0x123))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(properties) |
|||
{ |
|||
// Test setting and retrieving the various properties works.
|
|||
deployRegistrar(); |
|||
string names[] = {"abc", "def", "ghi"}; |
|||
size_t addr = 0x9872543; |
|||
for (string const& name: names) |
|||
{ |
|||
addr++; |
|||
size_t sender = addr + 10007; |
|||
m_sender = Address(sender); |
|||
// setting by sender works
|
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(sender))); |
|||
BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), u256(addr), u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(addr)); |
|||
BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), addr + 20, u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(addr + 20)); |
|||
BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), addr + 90, u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(addr + 90)); |
|||
// but not by someone else
|
|||
m_sender = Address(h256(addr + 10007 - 1)); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(sender)); |
|||
BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), addr + 1, u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(addr)); |
|||
BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), addr + 20 + 1, u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(addr + 20)); |
|||
BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), addr + 90 + 1, u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(addr + 90)); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(transfer) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "abc"; |
|||
m_sender = Address(0x123); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), u256(123), u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("transfer(string,address)", u256(0x40), u256(555), u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(555))); |
|||
BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(u256(123))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(disown) |
|||
{ |
|||
deployRegistrar(); |
|||
string name = "abc"; |
|||
m_sender = Address(0x123); |
|||
BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), u256(123), u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), u256(124), u256(name.length()), name) == encodeArgs()); |
|||
BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), u256(125), u256(name.length()), name) == encodeArgs()); |
|||
|
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x124)), 0); |
|||
BOOST_CHECK(callContractFunction("disown(string,address)", u256(0x40), u256(0x124), name.size(), name) == encodeArgs()); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x124)), m_fee); |
|||
|
|||
BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(0))); |
|||
BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(u256(0))); |
|||
BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(u256(0))); |
|||
BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(u256(0))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,668 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Tests for a (comparatively) complex multisig wallet contract. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <tuple> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libdevcore/Hash.h> |
|||
#include <test/libsolidity/solidityExecutionFramework.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
static char const* walletCode = R"DELIMITER( |
|||
//sol Wallet
|
|||
// Multi-sig, daily-limited account proxy/wallet.
|
|||
// @authors:
|
|||
// Gav Wood <g@ethdev.com>
|
|||
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
|
|||
// single, or, crucially, each of a number of, designated owners.
|
|||
// usage:
|
|||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
|||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
|||
// interior is executed.
|
|||
contract multiowned { |
|||
|
|||
// TYPES
|
|||
|
|||
// struct for the status of a pending operation.
|
|||
struct PendingState { |
|||
uint yetNeeded; |
|||
uint ownersDone; |
|||
uint index; |
|||
} |
|||
|
|||
// EVENTS
|
|||
|
|||
// this contract only has five types of events: it can accept a confirmation, in which case
|
|||
// we record owner and operation (hash) alongside it.
|
|||
event Confirmation(address owner, bytes32 operation); |
|||
event Revoke(address owner, bytes32 operation); |
|||
// some others are in the case of an owner changing.
|
|||
event OwnerChanged(address oldOwner, address newOwner); |
|||
event OwnerAdded(address newOwner); |
|||
event OwnerRemoved(address oldOwner); |
|||
// the last one is emitted if the required signatures change
|
|||
event RequirementChanged(uint newRequirement); |
|||
|
|||
// MODIFIERS
|
|||
|
|||
// simple single-sig function modifier.
|
|||
modifier onlyowner { |
|||
if (isOwner(msg.sender)) |
|||
_ |
|||
} |
|||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
|||
// that later attempts can be realised as the same underlying operation and
|
|||
// thus count as confirmations.
|
|||
modifier onlymanyowners(bytes32 _operation) { |
|||
if (confirmAndCheck(_operation)) |
|||
_ |
|||
} |
|||
|
|||
// METHODS
|
|||
|
|||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
|||
// as well as the selection of addresses capable of confirming them.
|
|||
function multiowned(address[] _owners, uint _required) { |
|||
m_numOwners = _owners.length + 1; |
|||
m_owners[1] = uint(msg.sender); |
|||
m_ownerIndex[uint(msg.sender)] = 1; |
|||
for (uint i = 0; i < _owners.length; ++i) |
|||
{ |
|||
m_owners[2 + i] = uint(_owners[i]); |
|||
m_ownerIndex[uint(_owners[i])] = 2 + i; |
|||
} |
|||
m_required = _required; |
|||
} |
|||
|
|||
// Revokes a prior confirmation of the given operation
|
|||
function revoke(bytes32 _operation) external { |
|||
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; |
|||
// make sure they're an owner
|
|||
if (ownerIndex == 0) return; |
|||
uint ownerIndexBit = 2**ownerIndex; |
|||
var pending = m_pending[_operation]; |
|||
if (pending.ownersDone & ownerIndexBit > 0) { |
|||
pending.yetNeeded++; |
|||
pending.ownersDone -= ownerIndexBit; |
|||
Revoke(msg.sender, _operation); |
|||
} |
|||
} |
|||
|
|||
// Replaces an owner `_from` with another `_to`.
|
|||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { |
|||
if (isOwner(_to)) return; |
|||
uint ownerIndex = m_ownerIndex[uint(_from)]; |
|||
if (ownerIndex == 0) return; |
|||
|
|||
clearPending(); |
|||
m_owners[ownerIndex] = uint(_to); |
|||
m_ownerIndex[uint(_from)] = 0; |
|||
m_ownerIndex[uint(_to)] = ownerIndex; |
|||
OwnerChanged(_from, _to); |
|||
} |
|||
|
|||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { |
|||
if (isOwner(_owner)) return; |
|||
|
|||
clearPending(); |
|||
if (m_numOwners >= c_maxOwners) |
|||
reorganizeOwners(); |
|||
if (m_numOwners >= c_maxOwners) |
|||
return; |
|||
m_numOwners++; |
|||
m_owners[m_numOwners] = uint(_owner); |
|||
m_ownerIndex[uint(_owner)] = m_numOwners; |
|||
OwnerAdded(_owner); |
|||
} |
|||
|
|||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { |
|||
uint ownerIndex = m_ownerIndex[uint(_owner)]; |
|||
if (ownerIndex == 0) return; |
|||
if (m_required > m_numOwners - 1) return; |
|||
|
|||
m_owners[ownerIndex] = 0; |
|||
m_ownerIndex[uint(_owner)] = 0; |
|||
clearPending(); |
|||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
|||
OwnerRemoved(_owner); |
|||
} |
|||
|
|||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { |
|||
if (_newRequired > m_numOwners) return; |
|||
m_required = _newRequired; |
|||
clearPending(); |
|||
RequirementChanged(_newRequired); |
|||
} |
|||
|
|||
function isOwner(address _addr) returns (bool) { |
|||
return m_ownerIndex[uint(_addr)] > 0; |
|||
} |
|||
|
|||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { |
|||
var pending = m_pending[_operation]; |
|||
uint ownerIndex = m_ownerIndex[uint(_owner)]; |
|||
|
|||
// make sure they're an owner
|
|||
if (ownerIndex == 0) return false; |
|||
|
|||
// determine the bit to set for this owner.
|
|||
uint ownerIndexBit = 2**ownerIndex; |
|||
if (pending.ownersDone & ownerIndexBit == 0) { |
|||
return false; |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// INTERNAL METHODS
|
|||
|
|||
function confirmAndCheck(bytes32 _operation) internal returns (bool) { |
|||
// determine what index the present sender is:
|
|||
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; |
|||
// make sure they're an owner
|
|||
if (ownerIndex == 0) return; |
|||
|
|||
var pending = m_pending[_operation]; |
|||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
|||
if (pending.yetNeeded == 0) { |
|||
// reset count of confirmations needed.
|
|||
pending.yetNeeded = m_required; |
|||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
|||
pending.ownersDone = 0; |
|||
pending.index = m_pendingIndex.length++; |
|||
m_pendingIndex[pending.index] = _operation; |
|||
} |
|||
// determine the bit to set for this owner.
|
|||
uint ownerIndexBit = 2**ownerIndex; |
|||
// make sure we (the message sender) haven't confirmed this operation previously.
|
|||
if (pending.ownersDone & ownerIndexBit == 0) { |
|||
Confirmation(msg.sender, _operation); |
|||
// ok - check if count is enough to go ahead.
|
|||
if (pending.yetNeeded <= 1) { |
|||
// enough confirmations: reset and run interior.
|
|||
delete m_pendingIndex[m_pending[_operation].index]; |
|||
delete m_pending[_operation]; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
// not enough: record that this owner in particular confirmed.
|
|||
pending.yetNeeded--; |
|||
pending.ownersDone |= ownerIndexBit; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function reorganizeOwners() private returns (bool) { |
|||
uint free = 1; |
|||
while (free < m_numOwners) |
|||
{ |
|||
while (free < m_numOwners && m_owners[free] != 0) free++; |
|||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; |
|||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) |
|||
{ |
|||
m_owners[free] = m_owners[m_numOwners]; |
|||
m_ownerIndex[m_owners[free]] = free; |
|||
m_owners[m_numOwners] = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function clearPending() internal { |
|||
uint length = m_pendingIndex.length; |
|||
for (uint i = 0; i < length; ++i) |
|||
if (m_pendingIndex[i] != 0) |
|||
delete m_pending[m_pendingIndex[i]]; |
|||
delete m_pendingIndex; |
|||
} |
|||
|
|||
// FIELDS
|
|||
|
|||
// the number of owners that must confirm the same operation before it is run.
|
|||
uint public m_required; |
|||
// pointer used to find a free slot in m_owners
|
|||
uint public m_numOwners; |
|||
|
|||
// list of owners
|
|||
uint[256] m_owners; |
|||
uint constant c_maxOwners = 250; |
|||
// index on the list of owners to allow reverse lookup
|
|||
mapping(uint => uint) m_ownerIndex; |
|||
// the ongoing operations.
|
|||
mapping(bytes32 => PendingState) m_pending; |
|||
bytes32[] m_pendingIndex; |
|||
} |
|||
|
|||
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
|
|||
// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method
|
|||
// uses is specified in the modifier.
|
|||
contract daylimit is multiowned { |
|||
|
|||
// MODIFIERS
|
|||
|
|||
// simple modifier for daily limit.
|
|||
modifier limitedDaily(uint _value) { |
|||
if (underLimit(_value)) |
|||
_ |
|||
} |
|||
|
|||
// METHODS
|
|||
|
|||
// constructor - stores initial daily limit and records the present day's index.
|
|||
function daylimit(uint _limit) { |
|||
m_dailyLimit = _limit; |
|||
m_lastDay = today(); |
|||
} |
|||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
|||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { |
|||
m_dailyLimit = _newLimit; |
|||
} |
|||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
|||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external { |
|||
m_spentToday = 0; |
|||
} |
|||
|
|||
// INTERNAL METHODS
|
|||
|
|||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
|||
// returns true. otherwise just returns false.
|
|||
function underLimit(uint _value) internal onlyowner returns (bool) { |
|||
// reset the spend limit if we're on a different day to last time.
|
|||
if (today() > m_lastDay) { |
|||
m_spentToday = 0; |
|||
m_lastDay = today(); |
|||
} |
|||
// check to see if there's enough left - if so, subtract and return true.
|
|||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { |
|||
m_spentToday += _value; |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
// determines today's index.
|
|||
function today() private constant returns (uint) { return now / 1 days; } |
|||
|
|||
// FIELDS
|
|||
|
|||
uint public m_dailyLimit; |
|||
uint m_spentToday; |
|||
uint m_lastDay; |
|||
} |
|||
|
|||
// interface contract for multisig proxy contracts; see below for docs.
|
|||
contract multisig { |
|||
|
|||
// EVENTS
|
|||
|
|||
// logged events:
|
|||
// Funds has arrived into the wallet (record how much).
|
|||
event Deposit(address from, uint value); |
|||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
|||
event SingleTransact(address owner, uint value, address to, bytes data); |
|||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
|||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); |
|||
// Confirmation still needed for a transaction.
|
|||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); |
|||
|
|||
// FUNCTIONS
|
|||
|
|||
// TODO: document
|
|||
function changeOwner(address _from, address _to) external; |
|||
function execute(address _to, uint _value, bytes _data) external returns (bytes32); |
|||
function confirm(bytes32 _h) returns (bool); |
|||
} |
|||
|
|||
// usage:
|
|||
// bytes32 h = Wallet(w).from(oneOwner).transact(to, value, data);
|
|||
// Wallet(w).from(anotherOwner).confirm(h);
|
|||
contract Wallet is multisig, multiowned, daylimit { |
|||
|
|||
// TYPES
|
|||
|
|||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
|||
struct Transaction { |
|||
address to; |
|||
uint value; |
|||
bytes data; |
|||
} |
|||
|
|||
// METHODS
|
|||
|
|||
// constructor - just pass on the owner array to the multiowned and
|
|||
// the limit to daylimit
|
|||
function Wallet(address[] _owners, uint _required, uint _daylimit) |
|||
multiowned(_owners, _required) daylimit(_daylimit) { |
|||
} |
|||
|
|||
// kills the contract sending everything to `_to`.
|
|||
function kill(address _to) onlymanyowners(sha3(msg.data)) external { |
|||
suicide(_to); |
|||
} |
|||
|
|||
// gets called when no other function matches
|
|||
function() { |
|||
// just being sent some cash?
|
|||
if (msg.value > 0) |
|||
Deposit(msg.sender, msg.value); |
|||
} |
|||
|
|||
// Outside-visible transact entry point. Executes transacion immediately if below daily spend limit.
|
|||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
|||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
|||
// and _data arguments). They still get the option of using them if they want, anyways.
|
|||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { |
|||
// first, take the opportunity to check that we're under the daily limit.
|
|||
if (underLimit(_value)) { |
|||
SingleTransact(msg.sender, _value, _to, _data); |
|||
// yes - just execute the call.
|
|||
_to.call.value(_value)(_data); |
|||
return 0; |
|||
} |
|||
// determine our operation hash.
|
|||
_r = sha3(msg.data, block.number); |
|||
if (!confirm(_r) && m_txs[_r].to == 0) { |
|||
m_txs[_r].to = _to; |
|||
m_txs[_r].value = _value; |
|||
m_txs[_r].data = _data; |
|||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data); |
|||
} |
|||
} |
|||
|
|||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
|||
// to determine the body of the transaction from the hash provided.
|
|||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { |
|||
if (m_txs[_h].to != 0) { |
|||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); |
|||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); |
|||
delete m_txs[_h]; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// INTERNAL METHODS
|
|||
|
|||
function clearPending() internal { |
|||
uint length = m_pendingIndex.length; |
|||
for (uint i = 0; i < length; ++i) |
|||
delete m_txs[m_pendingIndex[i]]; |
|||
super.clearPending(); |
|||
} |
|||
|
|||
// FIELDS
|
|||
|
|||
// pending transactions we have at present.
|
|||
mapping (bytes32 => Transaction) m_txs; |
|||
} |
|||
)DELIMITER"; |
|||
|
|||
static unique_ptr<bytes> s_compiledWallet; |
|||
|
|||
class WalletTestFramework: public ExecutionFramework |
|||
{ |
|||
protected: |
|||
void deployWallet( |
|||
u256 const& _value = 0, |
|||
vector<u256> const& _owners = vector<u256>{}, |
|||
u256 _required = 1, |
|||
u256 _dailyLimit = 0 |
|||
) |
|||
{ |
|||
if (!s_compiledWallet) |
|||
{ |
|||
m_optimize = true; |
|||
m_compiler.reset(false, m_addStandardSources); |
|||
m_compiler.addSource("", walletCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); |
|||
s_compiledWallet.reset(new bytes(m_compiler.getBytecode("Wallet"))); |
|||
} |
|||
bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); |
|||
sendMessage(*s_compiledWallet + args, true, _value); |
|||
BOOST_REQUIRE(!m_output.empty()); |
|||
} |
|||
}; |
|||
|
|||
/// This is a test suite that tests optimised code!
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityWallet, WalletTestFramework) |
|||
|
|||
BOOST_AUTO_TEST_CASE(creation) |
|||
{ |
|||
deployWallet(200); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == encodeArgs(false)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(add_owners) |
|||
{ |
|||
deployWallet(200); |
|||
Address originalOwner = m_sender; |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); |
|||
// now let the new owner add someone
|
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); |
|||
// and check that a non-owner cannot add a new owner
|
|||
m_sender = Address(0x50); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x20)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x20)) == encodeArgs(false)); |
|||
// finally check that all the owners are there
|
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(originalOwner, h256::AlignRight)) == encodeArgs(true)); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(change_owners) |
|||
{ |
|||
deployWallet(200); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); |
|||
BOOST_REQUIRE(callContractFunction("changeOwner(address,address)", h256(0x12), h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(false)); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(remove_owner) |
|||
{ |
|||
deployWallet(200); |
|||
// add 10 owners
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
{ |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); |
|||
} |
|||
// check they are there again
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); |
|||
// remove the odd owners
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
if (i % 2 == 1) |
|||
BOOST_REQUIRE(callContractFunction("removeOwner(address)", h256(0x12 + i)) == encodeArgs()); |
|||
// check the result
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(i % 2 == 0)); |
|||
// add them again
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
if (i % 2 == 1) |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs()); |
|||
// check everyone is there
|
|||
for (unsigned i = 0; i < 10; ++i) |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(initial_owners) |
|||
{ |
|||
vector<u256> owners{ |
|||
u256("0x00000000000000000000000042c56279432962a17176998a4747d1b4d6ed4367"), |
|||
u256("0x000000000000000000000000d4d4669f5ba9f4c27d38ef02a358c339b5560c47"), |
|||
u256("0x000000000000000000000000e6716f9544a56c530d868e4bfbacb172315bdead"), |
|||
u256("0x000000000000000000000000775e18be7a50a0abb8a4e82b1bd697d79f31fe04"), |
|||
u256("0x000000000000000000000000f4dd5c3794f1fd0cdc0327a83aa472609c806e99"), |
|||
u256("0x0000000000000000000000004c9113886af165b2de069d6e99430647e94a9fff"), |
|||
u256("0x0000000000000000000000003fb1cd2cd96c6d5c0b5eb3322d807b34482481d4") |
|||
}; |
|||
deployWallet(0, owners, 4, 2); |
|||
BOOST_CHECK(callContractFunction("m_numOwners()") == encodeArgs(u256(8))); |
|||
BOOST_CHECK(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); |
|||
for (u256 const& owner: owners) |
|||
{ |
|||
BOOST_CHECK(callContractFunction("isOwner(address)", owner) == encodeArgs(true)); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multisig_value_transfer) |
|||
{ |
|||
deployWallet(200); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); |
|||
// 4 owners, set required to 3
|
|||
BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); |
|||
// check that balance is and stays zero at destination address
|
|||
h256 opHash("6244b4fa93f73e09db0ae52750095ca0364a76b72bc01723c97011fcb876cc9e"); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x13); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x14); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
// now it should go through
|
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 100); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(revoke_addOwner) |
|||
{ |
|||
deployWallet(); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); |
|||
// 4 owners, set required to 3
|
|||
BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); |
|||
// add a new owner
|
|||
Address deployer = m_sender; |
|||
h256 opHash = sha3(FixedHash<4>(dev::sha3("addOwner(address)")).asBytes() + h256(0x33).asBytes()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); |
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); |
|||
// revoke one confirmation
|
|||
m_sender = deployer; |
|||
BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); |
|||
m_sender = Address(0x13); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); |
|||
m_sender = Address(0x14); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(true)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(revoke_transaction) |
|||
{ |
|||
deployWallet(200); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); |
|||
// 4 owners, set required to 3
|
|||
BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); |
|||
// create a transaction
|
|||
Address deployer = m_sender; |
|||
h256 opHash("6244b4fa93f73e09db0ae52750095ca0364a76b72bc01723c97011fcb876cc9e"); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x13); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); |
|||
m_sender = deployer; |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x14); |
|||
BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); |
|||
// now it should go through
|
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 100); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(daylimit) |
|||
{ |
|||
deployWallet(200); |
|||
BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(0))); |
|||
BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(100)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(100))); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); |
|||
// 4 owners, set required to 3
|
|||
BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); |
|||
|
|||
// try to send tx over daylimit
|
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE( |
|||
callContractFunction("execute(address,uint256,bytes)", h256(0x05), 150, 0x60, 0x00) != |
|||
encodeArgs(u256(0)) |
|||
); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
// try to send tx under daylimit by stranger
|
|||
m_sender = Address(0x77); |
|||
BOOST_REQUIRE( |
|||
callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == |
|||
encodeArgs(u256(0)) |
|||
); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); |
|||
// now send below limit by owner
|
|||
m_sender = Address(0x12); |
|||
BOOST_REQUIRE( |
|||
callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == |
|||
encodeArgs(u256(0)) |
|||
); |
|||
BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 90); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(daylimit_constructor) |
|||
{ |
|||
deployWallet(200, {}, 1, 20); |
|||
BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(20))); |
|||
BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(30)) == encodeArgs()); |
|||
BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(30))); |
|||
} |
|||
|
|||
//@todo test data calls
|
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,120 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
* Unit tests for Assembly Items from evmasm/Assembly.h |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <iostream> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libdevcore/Log.h> |
|||
#include <libevmasm/SourceLocation.h> |
|||
#include <libevmasm/Assembly.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/Compiler.h> |
|||
#include <libsolidity/AST.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev::eth; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
|
|||
eth::AssemblyItems compileContract(const string& _sourceCode) |
|||
{ |
|||
Parser parser; |
|||
ASTPointer<SourceUnit> sourceUnit; |
|||
BOOST_REQUIRE_NO_THROW(sourceUnit = parser.parse(make_shared<Scanner>(CharStream(_sourceCode)))); |
|||
NameAndTypeResolver resolver({}); |
|||
resolver.registerDeclarations(*sourceUnit); |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); |
|||
} |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract)); |
|||
} |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
Compiler compiler; |
|||
compiler.compileContract(*contract, map<ContractDefinition const*, bytes const*>{}); |
|||
|
|||
return compiler.getRuntimeAssemblyItems(); |
|||
} |
|||
BOOST_FAIL("No contract found in source."); |
|||
return AssemblyItems(); |
|||
} |
|||
|
|||
void checkAssemblyLocations(AssemblyItems const& _items, vector<SourceLocation> const& _locations) |
|||
{ |
|||
BOOST_CHECK_EQUAL(_items.size(), _locations.size()); |
|||
for (size_t i = 0; i < min(_items.size(), _locations.size()); ++i) |
|||
{ |
|||
BOOST_CHECK_MESSAGE( |
|||
_items[i].getLocation() == _locations[i], |
|||
"Location mismatch for assembly item " + to_string(i) + ". Found: " + |
|||
to_string(_items[i].getLocation().start) + "-" + |
|||
to_string(_items[i].getLocation().end) + ", expected: " + |
|||
to_string(_locations[i].start) + "-" + |
|||
to_string(_locations[i].end)); |
|||
} |
|||
} |
|||
|
|||
} // end anonymous namespace
|
|||
|
|||
BOOST_AUTO_TEST_SUITE(Assembly) |
|||
|
|||
BOOST_AUTO_TEST_CASE(location_test) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function f() returns (uint256 a) { |
|||
return 16; |
|||
} |
|||
} |
|||
)"; |
|||
shared_ptr<string const> n = make_shared<string>("source"); |
|||
AssemblyItems items = compileContract(sourceCode); |
|||
vector<SourceLocation> locations = |
|||
vector<SourceLocation>(17, SourceLocation(2, 75, n)) + |
|||
vector<SourceLocation>(26, SourceLocation(20, 72, n)) + |
|||
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + |
|||
vector<SourceLocation>(4, SourceLocation(58, 67, n)) + |
|||
vector<SourceLocation>(3, SourceLocation(20, 72, n)); |
|||
checkAssemblyLocations(items, locations); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,5 +0,0 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
|
|||
aux_source_directory(. SRCS) |
|||
|
|||
add_sources(${SRCS}) |
@ -1,235 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Unit tests for the gas estimator. |
|||
*/ |
|||
|
|||
#include <test/libsolidity/solidityExecutionFramework.h> |
|||
#include <libevmasm/GasMeter.h> |
|||
#include <libevmasm/KnownState.h> |
|||
#include <libevmasm/PathGasMeter.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/GasEstimator.h> |
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev::eth; |
|||
using namespace dev::solidity; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class GasMeterTestFramework: public ExecutionFramework |
|||
{ |
|||
public: |
|||
GasMeterTestFramework() { } |
|||
void compile(string const& _sourceCode) |
|||
{ |
|||
m_compiler.setSource(_sourceCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); |
|||
|
|||
AssemblyItems const* items = m_compiler.getRuntimeAssemblyItems(""); |
|||
ASTNode const& sourceUnit = m_compiler.getAST(); |
|||
BOOST_REQUIRE(items != nullptr); |
|||
m_gasCosts = GasEstimator::breakToStatementLevel( |
|||
GasEstimator::structuralEstimation(*items, vector<ASTNode const*>({&sourceUnit})), |
|||
{&sourceUnit} |
|||
); |
|||
} |
|||
|
|||
void testCreationTimeGas(string const& _sourceCode) |
|||
{ |
|||
compileAndRun(_sourceCode); |
|||
auto state = make_shared<KnownState>(); |
|||
PathGasMeter meter(*m_compiler.getAssemblyItems()); |
|||
GasMeter::GasConsumption gas = meter.estimateMax(0, state); |
|||
u256 bytecodeSize(m_compiler.getRuntimeBytecode().size()); |
|||
gas += bytecodeSize * c_createDataGas; |
|||
BOOST_REQUIRE(!gas.isInfinite); |
|||
BOOST_CHECK(gas.value == m_gasUsed); |
|||
} |
|||
|
|||
/// Compares the gas computed by PathGasMeter for the given signature (but unknown arguments)
|
|||
/// against the actual gas usage computed by the VM on the given set of argument variants.
|
|||
void testRunTimeGas(string const& _sig, vector<bytes> _argumentVariants) |
|||
{ |
|||
u256 gasUsed = 0; |
|||
FixedHash<4> hash(dev::sha3(_sig)); |
|||
for (bytes const& arguments: _argumentVariants) |
|||
{ |
|||
sendMessage(hash.asBytes() + arguments, false, 0); |
|||
gasUsed = max(gasUsed, m_gasUsed); |
|||
} |
|||
|
|||
GasMeter::GasConsumption gas = GasEstimator::functionalEstimation( |
|||
*m_compiler.getRuntimeAssemblyItems(), |
|||
_sig |
|||
); |
|||
BOOST_REQUIRE(!gas.isInfinite); |
|||
BOOST_CHECK(gas.value == m_gasUsed); |
|||
} |
|||
|
|||
protected: |
|||
map<ASTNode const*, eth::GasMeter::GasConsumption> m_gasCosts; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE(GasMeterTests, GasMeterTestFramework) |
|||
|
|||
BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
bytes x; |
|||
function f(uint a) returns (uint b) { |
|||
x.length = a; |
|||
for (; a < 200; ++a) { |
|||
x[a] = 9; |
|||
b = a * a; |
|||
} |
|||
return f(a - 1); |
|||
} |
|||
} |
|||
)"; |
|||
compile(sourceCode); |
|||
for (auto first = m_gasCosts.cbegin(); first != m_gasCosts.cend(); ++first) |
|||
{ |
|||
auto second = first; |
|||
for (++second; second != m_gasCosts.cend(); ++second) |
|||
if (first->first->getLocation().intersects(second->first->getLocation())) |
|||
{ |
|||
BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!"); |
|||
SourceReferenceFormatter::printSourceLocation(cout, first->first->getLocation(), m_compiler.getScanner()); |
|||
SourceReferenceFormatter::printSourceLocation(cout, second->first->getLocation(), m_compiler.getScanner()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(simple_contract) |
|||
{ |
|||
// Tests a simple "deploy contract" code without constructor. The actual contract is not relevant.
|
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
bytes32 public shaValue; |
|||
function f(uint a) { |
|||
shaValue = sha3(a); |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(store_sha3) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
bytes32 public shaValue; |
|||
function test(uint a) { |
|||
shaValue = sha3(a); |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(updating_store) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
uint data; |
|||
uint data2; |
|||
function test() { |
|||
data = 1; |
|||
data = 2; |
|||
data2 = 0; |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(branches) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
uint data; |
|||
uint data2; |
|||
function f(uint x) { |
|||
if (x > 7) |
|||
data2 = 1; |
|||
else |
|||
data = 1; |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)}); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_calls) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
uint data; |
|||
uint data2; |
|||
function f(uint x) { |
|||
if (x > 7) |
|||
data2 = g(x**8) + 1; |
|||
else |
|||
data = 1; |
|||
} |
|||
function g(uint x) internal returns (uint) { |
|||
return data2; |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)}); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_external_functions) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
uint data; |
|||
uint data2; |
|||
function f(uint x) { |
|||
if (x > 7) |
|||
data2 = g(x**8) + 1; |
|||
else |
|||
data = 1; |
|||
} |
|||
function g(uint x) returns (uint) { |
|||
return data2; |
|||
} |
|||
} |
|||
)"; |
|||
testCreationTimeGas(sourceCode); |
|||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)}); |
|||
testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)}); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
@ -1,602 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity compiler JSON Interface output. |
|||
*/ |
|||
|
|||
#include "../TestHelper.h" |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <json/json.h> |
|||
#include <libdevcore/Exceptions.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class JSONInterfaceChecker |
|||
{ |
|||
public: |
|||
JSONInterfaceChecker(): m_compilerStack(false) {} |
|||
|
|||
void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) |
|||
{ |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing contract failed"); |
|||
std::string generatedInterfaceString = m_compilerStack.getMetadata("", DocumentationType::ABIInterface); |
|||
Json::Value generatedInterface; |
|||
m_reader.parse(generatedInterfaceString, generatedInterface); |
|||
Json::Value expectedInterface; |
|||
m_reader.parse(_expectedInterfaceString, expectedInterface); |
|||
BOOST_CHECK_MESSAGE(expectedInterface == generatedInterface, |
|||
"Expected:\n" << expectedInterface.toStyledString() << |
|||
"\n but got:\n" << generatedInterface.toStyledString()); |
|||
} |
|||
|
|||
private: |
|||
CompilerStack m_compilerStack; |
|||
Json::Reader m_reader; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityABIJSON, JSONInterfaceChecker) |
|||
|
|||
BOOST_AUTO_TEST_CASE(basic_test) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
"}\n"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_contract) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
"}\n"; |
|||
char const* interface = "[]"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_methods) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
" function g(uint b) returns(uint e) { return b * 8; }\n" |
|||
"}\n"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"name": "g", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "e", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_params) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) returns(uint d) { return a + b; }\n" |
|||
"}\n"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_methods_order) |
|||
{ |
|||
// methods are expected to be in alpabetical order
|
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
" function c(uint b) returns(uint e) { return b * 8; }\n" |
|||
"}\n"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "c", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "e", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(const_function) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function foo(uint a, uint b) returns(uint d) { return a + b; }\n" |
|||
" function boo(uint32 a) constant returns(uint b) { return a * 4; }\n" |
|||
"}\n"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "foo", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"name": "boo", |
|||
"constant": true, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "a", |
|||
"type": "uint32" |
|||
}], |
|||
"outputs": [ |
|||
{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(exclude_fallback_function) |
|||
{ |
|||
char const* sourceCode = "contract test { function() {} }"; |
|||
|
|||
char const* interface = "[]"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(events) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
" event e1(uint b, address indexed c); \n" |
|||
" event e2(); \n" |
|||
"}\n"; |
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"name": "e1", |
|||
"type": "event", |
|||
"anonymous": false, |
|||
"inputs": [ |
|||
{ |
|||
"indexed": false, |
|||
"name": "b", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"indexed": true, |
|||
"name": "c", |
|||
"type": "address" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"name": "e2", |
|||
"type": "event", |
|||
"anonymous": false, |
|||
"inputs": [] |
|||
} |
|||
|
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(events_anonymous) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" event e() anonymous; \n" |
|||
"}\n"; |
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "e", |
|||
"type": "event", |
|||
"anonymous": true, |
|||
"inputs": [] |
|||
} |
|||
|
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(inherited) |
|||
{ |
|||
char const* sourceCode = |
|||
" contract Base { \n" |
|||
" function baseFunction(uint p) returns (uint i) { return p; } \n" |
|||
" event baseEvent(bytes32 indexed evtArgBase); \n" |
|||
" } \n" |
|||
" contract Derived is Base { \n" |
|||
" function derivedFunction(bytes32 p) returns (bytes32 i) { return p; } \n" |
|||
" event derivedEvent(uint indexed evtArgDerived); \n" |
|||
" }"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "baseFunction", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": |
|||
[{ |
|||
"name": "p", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": |
|||
[{ |
|||
"name": "i", |
|||
"type": "uint256" |
|||
}] |
|||
}, |
|||
{ |
|||
"name": "derivedFunction", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": |
|||
[{ |
|||
"name": "p", |
|||
"type": "bytes32" |
|||
}], |
|||
"outputs": |
|||
[{ |
|||
"name": "i", |
|||
"type": "bytes32" |
|||
}] |
|||
}, |
|||
{ |
|||
"name": "derivedEvent", |
|||
"type": "event", |
|||
"anonymous": false, |
|||
"inputs": |
|||
[{ |
|||
"indexed": true, |
|||
"name": "evtArgDerived", |
|||
"type": "uint256" |
|||
}] |
|||
}, |
|||
{ |
|||
"name": "baseEvent", |
|||
"type": "event", |
|||
"anonymous": false, |
|||
"inputs": |
|||
[{ |
|||
"indexed": true, |
|||
"name": "evtArgBase", |
|||
"type": "bytes32" |
|||
}] |
|||
}])"; |
|||
|
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function f(uint, uint k) returns(uint ret_k, uint ret_g){ |
|||
uint g = 8; |
|||
ret_k = k; |
|||
ret_g = g; |
|||
} |
|||
})"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"name": "k", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "ret_k", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"name": "ret_g", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
|
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_name_return_parameter) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function f(uint k) returns(uint){ |
|||
return k; |
|||
} |
|||
})"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"name": "f", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [ |
|||
{ |
|||
"name": "k", |
|||
"type": "uint256" |
|||
} |
|||
], |
|||
"outputs": [ |
|||
{ |
|||
"name": "", |
|||
"type": "uint256" |
|||
} |
|||
] |
|||
} |
|||
])"; |
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(constructor_abi) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function test(uint param1, test param2, bool param3) {} |
|||
} |
|||
)"; |
|||
|
|||
char const* interface = R"([ |
|||
{ |
|||
"inputs": [ |
|||
{ |
|||
"name": "param1", |
|||
"type": "uint256" |
|||
}, |
|||
{ |
|||
"name": "param2", |
|||
"type": "address" |
|||
}, |
|||
{ |
|||
"name": "param3", |
|||
"type": "bool" |
|||
} |
|||
], |
|||
"type": "constructor" |
|||
} |
|||
])"; |
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
|
|||
BOOST_AUTO_TEST_CASE(return_param_in_abi) |
|||
{ |
|||
// bug #1801
|
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } |
|||
function test(ActionChoices param) {} |
|||
function ret() returns(ActionChoices){ |
|||
ActionChoices action = ActionChoices.GoLeft; |
|||
return action; |
|||
} |
|||
} |
|||
)"; |
|||
|
|||
char const* interface = R"( |
|||
[ |
|||
{ |
|||
"constant" : false, |
|||
"inputs" : [], |
|||
"name" : "ret", |
|||
"outputs" : [ |
|||
{ |
|||
"name" : "", |
|||
"type" : "uint8" |
|||
} |
|||
], |
|||
"type" : "function" |
|||
}, |
|||
{ |
|||
"inputs": [ |
|||
{ |
|||
"name": "param", |
|||
"type": "uint8" |
|||
} |
|||
], |
|||
"type": "constructor" |
|||
} |
|||
] |
|||
)"; |
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(strings_and_arrays) |
|||
{ |
|||
// bug #1801
|
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function f(string a, bytes b, uint[] c) external {} |
|||
} |
|||
)"; |
|||
|
|||
char const* interface = R"( |
|||
[ |
|||
{ |
|||
"constant" : false, |
|||
"name": "f", |
|||
"inputs": [ |
|||
{ "name": "a", "type": "string" }, |
|||
{ "name": "b", "type": "bytes" }, |
|||
{ "name": "c", "type": "uint256[]" } |
|||
], |
|||
"outputs": [], |
|||
"type" : "function" |
|||
} |
|||
] |
|||
)"; |
|||
checkInterface(sourceCode, interface); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,491 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity expression compiler. |
|||
*/ |
|||
|
|||
#include <string> |
|||
|
|||
#include <libdevcore/Log.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/CompilerContext.h> |
|||
#include <libsolidity/ExpressionCompiler.h> |
|||
#include <libsolidity/AST.h> |
|||
#include "../TestHelper.h" |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
|
|||
/// Helper class that extracts the first expression in an AST.
|
|||
class FirstExpressionExtractor: private ASTVisitor |
|||
{ |
|||
public: |
|||
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } |
|||
Expression* getExpression() const { return m_expression; } |
|||
private: |
|||
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } |
|||
bool checkExpression(Expression& _expression) |
|||
{ |
|||
if (m_expression == nullptr) |
|||
m_expression = &_expression; |
|||
return false; |
|||
} |
|||
private: |
|||
Expression* m_expression; |
|||
}; |
|||
|
|||
Declaration const& resolveDeclaration( |
|||
vector<string> const& _namespacedName, NameAndTypeResolver const& _resolver) |
|||
{ |
|||
Declaration const* declaration = nullptr; |
|||
// bracers are required, cause msvc couldnt handle this macro in for statement
|
|||
for (string const& namePart: _namespacedName) |
|||
{ |
|||
auto declarations = _resolver.resolveName(namePart, declaration); |
|||
BOOST_REQUIRE(!declarations.empty()); |
|||
BOOST_REQUIRE(declaration = *declarations.begin()); |
|||
} |
|||
BOOST_REQUIRE(declaration); |
|||
return *declaration; |
|||
} |
|||
|
|||
bytes compileFirstExpression(const string& _sourceCode, vector<vector<string>> _functions = {}, |
|||
vector<vector<string>> _localVariables = {}, |
|||
vector<shared_ptr<MagicVariableDeclaration const>> _globalDeclarations = {}) |
|||
{ |
|||
Parser parser; |
|||
ASTPointer<SourceUnit> sourceUnit; |
|||
try |
|||
{ |
|||
sourceUnit = parser.parse(make_shared<Scanner>(CharStream(_sourceCode))); |
|||
} |
|||
catch(boost::exception const& _e) |
|||
{ |
|||
auto msg = std::string("Parsing source code failed with: \n") + boost::diagnostic_information(_e); |
|||
BOOST_FAIL(msg); |
|||
} |
|||
|
|||
vector<Declaration const*> declarations; |
|||
declarations.reserve(_globalDeclarations.size() + 1); |
|||
for (ASTPointer<Declaration const> const& variable: _globalDeclarations) |
|||
declarations.push_back(variable.get()); |
|||
NameAndTypeResolver resolver(declarations); |
|||
resolver.registerDeclarations(*sourceUnit); |
|||
|
|||
vector<ContractDefinition const*> inheritanceHierarchy; |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
ETH_TEST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract), "Resolving names failed"); |
|||
inheritanceHierarchy = vector<ContractDefinition const*>(1, contract); |
|||
} |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
ETH_TEST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract), "Checking type Requirements failed"); |
|||
} |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) |
|||
{ |
|||
FirstExpressionExtractor extractor(*contract); |
|||
BOOST_REQUIRE(extractor.getExpression() != nullptr); |
|||
|
|||
CompilerContext context; |
|||
context.resetVisitedNodes(contract); |
|||
context.setInheritanceHierarchy(inheritanceHierarchy); |
|||
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
|
|||
context.adjustStackOffset(parametersSize); |
|||
for (vector<string> const& variable: _localVariables) |
|||
context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver)), |
|||
parametersSize--); |
|||
|
|||
ExpressionCompiler(context).compile(*extractor.getExpression()); |
|||
|
|||
for (vector<string> const& function: _functions) |
|||
context << context.getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); |
|||
bytes instructions = context.getAssembledBytecode(); |
|||
// debug
|
|||
// cout << eth::disassemble(instructions) << endl;
|
|||
return instructions; |
|||
} |
|||
BOOST_FAIL("No contract found in source."); |
|||
return bytes(); |
|||
} |
|||
|
|||
} // end anonymous namespace
|
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_true) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = true; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_false) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = false; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_literal) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = 0x12345678901234567890; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, |
|||
0x12, 0x34, 0x56, 0x78, 0x90}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_with_wei_ether_subdenomination) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function test () |
|||
{ |
|||
var x = 1 wei; |
|||
} |
|||
})"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_with_szabo_ether_subdenomination) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function test () |
|||
{ |
|||
var x = 1 szabo; |
|||
} |
|||
})"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH5), 0xe8, 0xd4, 0xa5, 0x10, 0x00}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_with_finney_ether_subdenomination) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function test () |
|||
{ |
|||
var x = 1 finney; |
|||
} |
|||
})"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH7), 0x3, 0x8d, 0x7e, 0xa4, 0xc6, 0x80, 0x00}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_with_ether_ether_subdenomination) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
function test () |
|||
{ |
|||
var x = 1 ether; |
|||
} |
|||
})"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH8), 0xd, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(comparison) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = (0x10aa < 0x11aa) != true; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::PUSH2), 0x11, 0xaa, |
|||
byte(eth::Instruction::PUSH2), 0x10, 0xaa, |
|||
byte(eth::Instruction::LT), |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(short_circuiting) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = true != (4 <= 8 + 10 || 9 != 2); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x12, // 8 + 10
|
|||
byte(eth::Instruction::PUSH1), 0x4, |
|||
byte(eth::Instruction::GT), |
|||
byte(eth::Instruction::ISZERO), // after this we have 4 <= 8 + 10
|
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x11, |
|||
byte(eth::Instruction::JUMPI), // short-circuit if it is true
|
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::PUSH1), 0x9, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO), // after this we have 9 != 2
|
|||
byte(eth::Instruction::JUMPDEST), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(arithmetics) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); |
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::PUSH1), 0x3, |
|||
byte(eth::Instruction::PUSH1), 0x4, |
|||
byte(eth::Instruction::PUSH1), 0x5, |
|||
byte(eth::Instruction::PUSH1), 0x6, |
|||
byte(eth::Instruction::PUSH1), 0x7, |
|||
byte(eth::Instruction::PUSH1), 0x8, |
|||
byte(eth::Instruction::DUP10), |
|||
byte(eth::Instruction::XOR), |
|||
byte(eth::Instruction::AND), |
|||
byte(eth::Instruction::OR), |
|||
byte(eth::Instruction::SUB), |
|||
byte(eth::Instruction::ADD), |
|||
byte(eth::Instruction::MOD), |
|||
byte(eth::Instruction::DIV), |
|||
byte(eth::Instruction::MUL)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(unary_operators) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(int y) { var x = !(~+- y == 2); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x0, |
|||
byte(eth::Instruction::SUB), |
|||
byte(eth::Instruction::NOT), |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(unary_inc_dec) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); |
|||
|
|||
// Stack: a, x
|
|||
bytes expectation({byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a x a (a+1)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // first ++
|
|||
// Stack here: (a+1) x a
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: (a+1) x a (a+2)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), |
|||
// Stack here: (a+2) x a
|
|||
byte(eth::Instruction::DUP3), // second ++
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+2) x a^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+2) x a^(a+2) (a+2) (a+1)
|
|||
byte(eth::Instruction::SWAP4), |
|||
byte(eth::Instruction::POP), // first --
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2) a
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // second ++
|
|||
// Stack here: a x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), // will change
|
|||
byte(eth::Instruction::XOR)}); |
|||
// Stack here: a x a^(a+2)^(a+2)^a
|
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(assignment) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { (a += b) * 2; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::DUP4), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b 2 a+b
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP3), |
|||
// Stack here: a+b b 2 a+b
|
|||
byte(eth::Instruction::MUL)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_call) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" |
|||
" function g(uint a, uint b) returns (uint c) {}\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, |
|||
{{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x02, |
|||
byte(eth::Instruction::PUSH1), 0x0c, |
|||
byte(eth::Instruction::PUSH1), 0x01, |
|||
byte(eth::Instruction::DUP5), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b 2 <ret label> (a+1)
|
|||
byte(eth::Instruction::DUP4), |
|||
byte(eth::Instruction::PUSH1), 0x13, |
|||
byte(eth::Instruction::JUMP), |
|||
byte(eth::Instruction::JUMPDEST), |
|||
// Stack here: a b 2 g(a+1, b)
|
|||
byte(eth::Instruction::MUL), |
|||
// Stack here: a b g(a+1, b)*2
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b a+g(a+1, b)*2
|
|||
byte(eth::Instruction::SWAP2), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::JUMPDEST)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(negative_literals_8bits) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { int8 x = -0x80; }\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x80)); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(negative_literals_16bits) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { int64 x = ~0xabc; }\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(30, 0xff) + bytes{0xf5, 0x43}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals) |
|||
{ |
|||
// first literal itself is too large for 256 bits but it fits after all constant operations
|
|||
// have been applied
|
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = (0xffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation(bytes({byte(eth::Instruction::PUSH1), 0xbf})); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(blockhash) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() {\n" |
|||
" block.blockhash(3);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {}, |
|||
{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block))}); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x03, |
|||
byte(eth::Instruction::BLOCKHASH)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,149 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Unit tests for generating source interfaces for Solidity contracts. |
|||
*/ |
|||
|
|||
#include "../TestHelper.h" |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/AST.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class SolidityInterfaceChecker |
|||
{ |
|||
public: |
|||
SolidityInterfaceChecker(): m_compilerStack(false) {} |
|||
|
|||
/// Compiles the given code, generates the interface and parses that again.
|
|||
ContractDefinition const& checkInterface(string const& _code, string const& _contractName = "") |
|||
{ |
|||
m_code = _code; |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing failed"); |
|||
m_interface = m_compilerStack.getMetadata("", DocumentationType::ABISolidityInterface); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_reCompiler.parse(m_interface), "Interface parsing failed"); |
|||
return m_reCompiler.getContractDefinition(_contractName); |
|||
} |
|||
|
|||
string getSourcePart(ASTNode const& _node) const |
|||
{ |
|||
SourceLocation location = _node.getLocation(); |
|||
BOOST_REQUIRE(!location.isEmpty()); |
|||
return m_interface.substr(location.start, location.end - location.start); |
|||
} |
|||
|
|||
protected: |
|||
string m_code; |
|||
string m_interface; |
|||
CompilerStack m_compilerStack; |
|||
CompilerStack m_reCompiler; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityInterface, SolidityInterfaceChecker) |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_contract) |
|||
{ |
|||
ContractDefinition const& contract = checkInterface("contract test {}"); |
|||
BOOST_CHECK_EQUAL(getSourcePart(contract), "contract test{}"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(single_function) |
|||
{ |
|||
ContractDefinition const& contract = checkInterface( |
|||
"contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
"}\n"); |
|||
BOOST_REQUIRE_EQUAL(1, contract.getDefinedFunctions().size()); |
|||
BOOST_CHECK_EQUAL(getSourcePart(*contract.getDefinedFunctions().front()), |
|||
"function f(uint256 a)returns(uint256 d);"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(single_constant_function) |
|||
{ |
|||
ContractDefinition const& contract = checkInterface( |
|||
"contract test { function f(uint a) constant returns(bytes1 x) { 1==2; } }"); |
|||
BOOST_REQUIRE_EQUAL(1, contract.getDefinedFunctions().size()); |
|||
BOOST_CHECK_EQUAL(getSourcePart(*contract.getDefinedFunctions().front()), |
|||
"function f(uint256 a)constant returns(bytes1 x);"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_functions) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
" function g(uint b) returns(uint e) { return b * 8; }\n" |
|||
"}\n"; |
|||
ContractDefinition const& contract = checkInterface(sourceCode); |
|||
set<string> expectation({"function f(uint256 a)returns(uint256 d);", |
|||
"function g(uint256 b)returns(uint256 e);"}); |
|||
BOOST_REQUIRE_EQUAL(2, contract.getDefinedFunctions().size()); |
|||
BOOST_CHECK(expectation == set<string>({getSourcePart(*contract.getDefinedFunctions().at(0)), |
|||
getSourcePart(*contract.getDefinedFunctions().at(1))})); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(exclude_fallback_function) |
|||
{ |
|||
char const* sourceCode = "contract test { function() {} }"; |
|||
ContractDefinition const& contract = checkInterface(sourceCode); |
|||
BOOST_CHECK_EQUAL(getSourcePart(contract), "contract test{}"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(events) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
" event e1(uint b, address indexed c); \n" |
|||
" event e2(); \n" |
|||
"}\n"; |
|||
ContractDefinition const& contract = checkInterface(sourceCode); |
|||
// events should not appear in the Solidity Interface
|
|||
BOOST_REQUIRE_EQUAL(0, contract.getEvents().size()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(inheritance) |
|||
{ |
|||
char const* sourceCode = |
|||
" contract Base { \n" |
|||
" function baseFunction(uint p) returns (uint i) { return p; } \n" |
|||
" event baseEvent(bytes32 indexed evtArgBase); \n" |
|||
" } \n" |
|||
" contract Derived is Base { \n" |
|||
" function derivedFunction(bytes32 p) returns (bytes32 i) { return p; } \n" |
|||
" event derivedEvent(uint indexed evtArgDerived); \n" |
|||
" }"; |
|||
ContractDefinition const& contract = checkInterface(sourceCode); |
|||
set<string> expectedFunctions({"function baseFunction(uint256 p)returns(uint256 i);", |
|||
"function derivedFunction(bytes32 p)returns(bytes32 i);"}); |
|||
BOOST_REQUIRE_EQUAL(2, contract.getDefinedFunctions().size()); |
|||
BOOST_CHECK(expectedFunctions == set<string>({getSourcePart(*contract.getDefinedFunctions().at(0)), |
|||
getSourcePart(*contract.getDefinedFunctions().at(1))})); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,534 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity compiler JSON Interface output. |
|||
*/ |
|||
|
|||
#include "../TestHelper.h" |
|||
#include <string> |
|||
#include <json/json.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include <libdevcore/Exceptions.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class DocumentationChecker |
|||
{ |
|||
public: |
|||
DocumentationChecker(): m_compilerStack(false) {} |
|||
|
|||
void checkNatspec( |
|||
std::string const& _code, |
|||
std::string const& _expectedDocumentationString, |
|||
bool _userDocumentation |
|||
) |
|||
{ |
|||
std::string generatedDocumentationString; |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing failed"); |
|||
|
|||
if (_userDocumentation) |
|||
generatedDocumentationString = m_compilerStack.getMetadata("", DocumentationType::NatspecUser); |
|||
else |
|||
generatedDocumentationString = m_compilerStack.getMetadata("", DocumentationType::NatspecDev); |
|||
Json::Value generatedDocumentation; |
|||
m_reader.parse(generatedDocumentationString, generatedDocumentation); |
|||
Json::Value expectedDocumentation; |
|||
m_reader.parse(_expectedDocumentationString, expectedDocumentation); |
|||
BOOST_CHECK_MESSAGE( |
|||
expectedDocumentation == generatedDocumentation, |
|||
"Expected " << _expectedDocumentationString << |
|||
"\n but got:\n" << generatedDocumentationString |
|||
); |
|||
} |
|||
|
|||
private: |
|||
CompilerStack m_compilerStack; |
|||
Json::Reader m_reader; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE(SolidityNatspecJSON, DocumentationChecker) |
|||
|
|||
BOOST_AUTO_TEST_CASE(user_basic_test) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @notice Multiplies `a` by 7\n" |
|||
" function mul(uint a) returns(uint d) { return a * 7; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_and_user_basic_test) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @notice Multiplies `a` by 7\n" |
|||
" /// @dev Multiplies a number by 7\n" |
|||
" function mul(uint a) returns(uint d) { return a * 7; }\n" |
|||
"}\n"; |
|||
|
|||
char const* devNatspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
char const* userNatspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, devNatspec, false); |
|||
checkNatspec(sourceCode, userNatspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(user_multiline_comment) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @notice Multiplies `a` by 7\n" |
|||
" /// and then adds `b`\n" |
|||
" function mul_and_add(uint a, uint256 b) returns(uint256 d)\n" |
|||
" {\n" |
|||
" return (a * 7) + b;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul_and_add(uint256,uint256)\":{ \"notice\": \"Multiplies `a` by 7 and then adds `b`\"}" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(user_multiple_functions) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @notice Multiplies `a` by 7 and then adds `b`\n" |
|||
" function mul_and_add(uint a, uint256 b) returns(uint256 d)\n" |
|||
" {\n" |
|||
" return (a * 7) + b;\n" |
|||
" }\n" |
|||
"\n" |
|||
" /// @notice Divides `input` by `div`\n" |
|||
" function divide(uint input, uint div) returns(uint d)\n" |
|||
" {\n" |
|||
" return input / div;\n" |
|||
" }\n" |
|||
" /// @notice Subtracts 3 from `input`\n" |
|||
" function sub(int input) returns(int d)\n" |
|||
" {\n" |
|||
" return input - 3;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul_and_add(uint256,uint256)\":{ \"notice\": \"Multiplies `a` by 7 and then adds `b`\"}," |
|||
" \"divide(uint256,uint256)\":{ \"notice\": \"Divides `input` by `div`\"}," |
|||
" \"sub(int256)\":{ \"notice\": \"Subtracts 3 from `input`\"}" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(user_empty_contract) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{\"methods\":{} }"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_and_user_no_doc) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function mul(uint a) returns(uint d) { return a * 7; }\n" |
|||
" function sub(int input) returns(int d)\n" |
|||
" {\n" |
|||
" return input - 3;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
|
|||
char const* devNatspec = "{\"methods\":{}}"; |
|||
char const* userNatspec = "{\"methods\":{}}"; |
|||
|
|||
checkNatspec(sourceCode, devNatspec, false); |
|||
checkNatspec(sourceCode, userNatspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_desc_after_nl) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev\n" |
|||
" /// Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \" Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_multiple_params) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_mutiline_param_description) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter starts here.\n" |
|||
" /// Since it's a really complicated parameter we need 2 lines\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_multiple_functions) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
" \n" |
|||
" /// @dev Divides 2 numbers\n" |
|||
" /// @param input Documentation for the input parameter\n" |
|||
" /// @param div Documentation for the div parameter\n" |
|||
" function divide(uint input, uint div) returns(uint d)\n" |
|||
" {\n" |
|||
" return input / div;\n" |
|||
" }\n" |
|||
" /// @dev Subtracts 3 from `input`\n" |
|||
" /// @param input Documentation for the input parameter\n" |
|||
" function sub(int input) returns(int d)\n" |
|||
" {\n" |
|||
" return input - 3;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" }\n" |
|||
" },\n" |
|||
" \"divide(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Divides 2 numbers\",\n" |
|||
" \"params\": {\n" |
|||
" \"input\": \"Documentation for the input parameter\",\n" |
|||
" \"div\": \"Documentation for the div parameter\"\n" |
|||
" }\n" |
|||
" },\n" |
|||
" \"sub(int256)\":{ \n" |
|||
" \"details\": \"Subtracts 3 from `input`\",\n" |
|||
" \"params\": {\n" |
|||
" \"input\": \"Documentation for the input parameter\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_return) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter starts here.\n" |
|||
" /// Since it's a really complicated parameter we need 2 lines\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" /// @return The result of the multiplication\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" },\n" |
|||
" \"return\": \"The result of the multiplication\"\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter starts here.\n" |
|||
" /// Since it's a really complicated parameter we need 2 lines\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" /// @return\n" |
|||
" /// The result of the multiplication\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" },\n" |
|||
" \"return\": \" The result of the multiplication\"\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_multiline_return) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" /// @param a Documentation for the first parameter starts here.\n" |
|||
" /// Since it's a really complicated parameter we need 2 lines\n" |
|||
" /// @param second Documentation for the second parameter\n" |
|||
" /// @return The result of the multiplication\n" |
|||
" /// and cookies with nutella\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" },\n" |
|||
" \"return\": \"The result of the multiplication and cookies with nutella\"\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_multiline_comment) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /**\n" |
|||
" * @dev Multiplies a number by 7 and adds second parameter\n" |
|||
" * @param a Documentation for the first parameter starts here.\n" |
|||
" * Since it's a really complicated parameter we need 2 lines\n" |
|||
" * @param second Documentation for the second parameter\n" |
|||
" * @return The result of the multiplication\n" |
|||
" * and cookies with nutella\n" |
|||
" */" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
"\"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" |
|||
" \"params\": {\n" |
|||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" |
|||
" \"second\": \"Documentation for the second parameter\"\n" |
|||
" },\n" |
|||
" \"return\": \"The result of the multiplication and cookies with nutella\"\n" |
|||
" }\n" |
|||
"}}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_contract_no_doc) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" /// @dev Mul function\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
" \"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Mul function\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_contract_doc) |
|||
{ |
|||
char const* sourceCode = " /// @author Lefteris\n" |
|||
" /// @title Just a test contract\n" |
|||
"contract test {\n" |
|||
" /// @dev Mul function\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
" \"author\": \"Lefteris\"," |
|||
" \"title\": \"Just a test contract\"," |
|||
" \"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Mul function\"\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(dev_author_at_function) |
|||
{ |
|||
char const* sourceCode = " /// @author Lefteris\n" |
|||
" /// @title Just a test contract\n" |
|||
"contract test {\n" |
|||
" /// @dev Mul function\n" |
|||
" /// @author John Doe\n" |
|||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" |
|||
"}\n"; |
|||
|
|||
char const* natspec = "{" |
|||
" \"author\": \"Lefteris\"," |
|||
" \"title\": \"Just a test contract\"," |
|||
" \"methods\":{" |
|||
" \"mul(uint256,uint256)\":{ \n" |
|||
" \"details\": \"Mul function\",\n" |
|||
" \"author\": \"John Doe\",\n" |
|||
" }\n" |
|||
" }\n" |
|||
"}"; |
|||
|
|||
checkNatspec(sourceCode, natspec, false); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(natspec_notice_without_tag) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
/// I do something awesome
|
|||
function mul(uint a) returns(uint d) { return a * 7; } |
|||
} |
|||
)"; |
|||
|
|||
|
|||
char const* natspec = R"ABCDEF( |
|||
{ |
|||
"methods" : { |
|||
"mul(uint256)" : { |
|||
"notice" : "I do something awesome" |
|||
} |
|||
} |
|||
} |
|||
)ABCDEF"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(natspec_multiline_notice_without_tag) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
/// I do something awesome
|
|||
/// which requires two lines to explain
|
|||
function mul(uint a) returns(uint d) { return a * 7; } |
|||
} |
|||
)"; |
|||
|
|||
char const* natspec = R"ABCDEF( |
|||
{ |
|||
"methods" : { |
|||
"mul(uint256)" : { |
|||
"notice" : "I do something awesome which requires two lines to explain" |
|||
} |
|||
} |
|||
} |
|||
)ABCDEF"; |
|||
|
|||
checkNatspec(sourceCode, natspec, true); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,921 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity parser. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <memory> |
|||
#include <libdevcore/Log.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
#include "../TestHelper.h" |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
ASTPointer<ContractDefinition> parseText(std::string const& _source) |
|||
{ |
|||
Parser parser; |
|||
ASTPointer<SourceUnit> sourceUnit = parser.parse(std::make_shared<Scanner>(CharStream(_source))); |
|||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes()) |
|||
if (ASTPointer<ContractDefinition> contract = dynamic_pointer_cast<ContractDefinition>(node)) |
|||
return contract; |
|||
BOOST_FAIL("No contract found in source."); |
|||
return ASTPointer<ContractDefinition>(); |
|||
} |
|||
|
|||
static void checkFunctionNatspec(ASTPointer<FunctionDefinition> _function, |
|||
std::string const& _expectedDoc) |
|||
{ |
|||
auto doc = _function->getDocumentation(); |
|||
BOOST_CHECK_MESSAGE(doc != nullptr, "Function does not have Natspec Doc as expected"); |
|||
BOOST_CHECK_EQUAL(*doc, _expectedDoc); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityParser) |
|||
|
|||
BOOST_AUTO_TEST_CASE(smoke_test) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVariable1;\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed."); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(missing_variable_name_in_declaration) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 ;\n" |
|||
"}\n"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_function) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" function functionName(bytes20 arg1, address addr) constant\n" |
|||
" returns (int id)\n" |
|||
" { }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed."); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(no_function_params) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" function functionName() {}\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed."); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(single_function_param) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" function functionName(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed."); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_no_body) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function functionName(bytes32 input) returns (bytes32 out);\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed."); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(missing_parameter_name_in_named_args) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; }\n" |
|||
" function b() returns (uint r) { r = a({: 1, : 2, : 3}); }\n" |
|||
"}\n"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(missing_argument_in_named_args) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; }\n" |
|||
" function b() returns (uint r) { r = a({a: , b: , c: }); }\n" |
|||
"}\n"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(two_exact_functions) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun(uint a) returns(uint r) { return a; } |
|||
function fun(uint a) returns(uint r) { return a; } |
|||
} |
|||
)"; |
|||
// with support of overloaded functions, during parsing,
|
|||
// we can't determine whether they match exactly, however
|
|||
// it will throw DeclarationError in following stage.
|
|||
BOOST_CHECK_NO_THROW(parseText(text)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(overloaded_functions) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun(uint a) returns(uint r) { return a; } |
|||
function fun(uint a, uint b) returns(uint r) { return a + b; } |
|||
} |
|||
)"; |
|||
BOOST_CHECK_NO_THROW(parseText(text)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_natspec_documentation) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" /// This is a test function\n" |
|||
" function functionName(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is a test function"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_normal_comments) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" // We won't see this comment\n" |
|||
" function functionName(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr, |
|||
"Should not have gotten a Natspecc comment for this function"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" /// This is test function 1\n" |
|||
" function functionName1(bytes32 input) returns (bytes32 out) {}\n" |
|||
" /// This is test function 2\n" |
|||
" function functionName2(bytes32 input) returns (bytes32 out) {}\n" |
|||
" // nothing to see here\n" |
|||
" function functionName3(bytes32 input) returns (bytes32 out) {}\n" |
|||
" /// This is test function 4\n" |
|||
" function functionName4(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is test function 1"); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(1), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is test function 2"); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(2), "Failed to retrieve function"); |
|||
BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr, |
|||
"Should not have gotten natspec comment for functionName3()"); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(3), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is test function 4"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiline_function_documentation) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" /// This is a test function\n" |
|||
" /// and it has 2 lines\n" |
|||
" function functionName1(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is a test function\n" |
|||
" and it has 2 lines"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" /// fun1 description\n" |
|||
" function fun1(uint256 a) {\n" |
|||
" var b;\n" |
|||
" /// I should not interfere with actual natspec comments\n" |
|||
" uint256 c;\n" |
|||
" mapping(address=>bytes32) d;\n" |
|||
" bytes7 name = \"Solidity\";" |
|||
" }\n" |
|||
" /// This is a test function\n" |
|||
" /// and it has 2 lines\n" |
|||
" function fun(bytes32 input) returns (bytes32 out) {}\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "fun1 description"); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(1), "Failed to retrieve function"); |
|||
checkFunctionNatspec(function, "This is a test function\n" |
|||
" and it has 2 lines"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(natspec_docstring_between_keyword_and_signature) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" function ///I am in the wrong place \n" |
|||
" fun1(uint256 a) {\n" |
|||
" var b;\n" |
|||
" /// I should not interfere with actual natspec comments\n" |
|||
" uint256 c;\n" |
|||
" mapping(address=>bytes32) d;\n" |
|||
" bytes7 name = \"Solidity\";" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
BOOST_CHECK_MESSAGE(!function->getDocumentation(), |
|||
"Shouldn't get natspec docstring for this function"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(natspec_docstring_after_signature) |
|||
{ |
|||
ASTPointer<ContractDefinition> contract; |
|||
ASTPointer<FunctionDefinition> function; |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" function fun1(uint256 a) {\n" |
|||
" /// I should have been above the function signature\n" |
|||
" var b;\n" |
|||
" /// I should not interfere with actual natspec comments\n" |
|||
" uint256 c;\n" |
|||
" mapping(address=>bytes32) d;\n" |
|||
" bytes7 name = \"Solidity\";" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed"); |
|||
auto functions = contract->getDefinedFunctions(); |
|||
|
|||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function"); |
|||
BOOST_CHECK_MESSAGE(!function->getDocumentation(), |
|||
"Shouldn't get natspec docstring for this function"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(struct_definition) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" uint256 stateVar;\n" |
|||
" struct MyStructName {\n" |
|||
" address addr;\n" |
|||
" uint256 count;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(mapping) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" mapping(address => bytes32) names;\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(mapping_in_struct) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" struct test_struct {\n" |
|||
" address addr;\n" |
|||
" uint256 count;\n" |
|||
" mapping(bytes32 => test_struct) self_reference;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(mapping_to_mapping_in_struct) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" struct test_struct {\n" |
|||
" address addr;\n" |
|||
" mapping (uint64 => mapping (bytes32 => uint)) complex_mapping;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(variable_definition) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" var b;\n" |
|||
" uint256 c;\n" |
|||
" mapping(address=>bytes32) d;\n" |
|||
" customtype varname;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(variable_definition_with_initialization) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" var b = 2;\n" |
|||
" uint256 c = 0x87;\n" |
|||
" mapping(address=>bytes32) d;\n" |
|||
" bytes7 name = \"Solidity\";" |
|||
" customtype varname;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(variable_definition_in_function_parameter) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun(var a) {} |
|||
} |
|||
)"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(variable_definition_in_mapping) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun() { |
|||
mapping(var=>bytes32) d; |
|||
} |
|||
} |
|||
)"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(variable_definition_in_function_return) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun() returns(var d) { |
|||
return 1; |
|||
} |
|||
} |
|||
)"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(operator_expression) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" uint256 x = (1 + 4) || false && (1 - 12) + -9;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(complex_expression) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" uint256 x = (1 + 4).member(++67)[a/=9] || true;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(exp_expression) |
|||
{ |
|||
char const* text = R"( |
|||
contract test { |
|||
function fun(uint256 a) { |
|||
uint256 x = 3 ** a; |
|||
} |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(while_loop) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" while (true) { uint256 x = 1; break; continue; } x = 9;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(for_loop_vardef_initexpr) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" for (uint256 i = 0; i < 10; i++)\n" |
|||
" { uint256 x = i; break; continue; }\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(for_loop_simple_initexpr) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" uint256 i =0;\n" |
|||
" for (i = 0; i < 10; i++)\n" |
|||
" { uint256 x = i; break; continue; }\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(for_loop_simple_noexpr) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" uint256 i =0;\n" |
|||
" for (;;)\n" |
|||
" { uint256 x = i; break; continue; }\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(for_loop_single_stmt_body) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" uint256 i =0;\n" |
|||
" for (i = 0; i < 10; i++)\n" |
|||
" continue;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(if_statement) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) {\n" |
|||
" if (a >= 8) return 2; else { var b = 7; }\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(else_if_statement) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun(uint256 a) returns (address b) {\n" |
|||
" if (a < 0) b = 0x67; else if (a == 0) b = 0x12; else b = 0x78;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(statement_starting_with_type_conversion) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" uint64[7](3);\n" |
|||
" uint64[](3);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(type_conversion_to_dynamic_array) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun() {\n" |
|||
" var x = uint64[](3);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(import_directive) |
|||
{ |
|||
char const* text = "import \"abc\";\n" |
|||
"contract test {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_contracts) |
|||
{ |
|||
char const* text = "contract test {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"contract test2 {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_contracts_and_imports) |
|||
{ |
|||
char const* text = "import \"abc\";\n" |
|||
"contract test {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"import \"def\";\n" |
|||
"contract test2 {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"import \"ghi\";\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(contract_inheritance) |
|||
{ |
|||
char const* text = "contract base {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"contract derived is base {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance) |
|||
{ |
|||
char const* text = "contract base {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"contract derived is base, nonExisting {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance_with_arguments) |
|||
{ |
|||
char const* text = "contract base {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n" |
|||
"contract derived is base(2), nonExisting(\"abc\", \"def\", base.fun()) {\n" |
|||
" function fun() {\n" |
|||
" uint64(2);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(placeholder_in_function_context) |
|||
{ |
|||
char const* text = "contract c {\n" |
|||
" function fun() returns (uint r) {\n" |
|||
" var _ = 8;\n" |
|||
" return _ + 1;" |
|||
" }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(modifier) |
|||
{ |
|||
char const* text = "contract c {\n" |
|||
" modifier mod { if (msg.sender == 0) _ }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(modifier_arguments) |
|||
{ |
|||
char const* text = "contract c {\n" |
|||
" modifier mod(uint a) { if (msg.sender == a) _ }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(modifier_invocation) |
|||
{ |
|||
char const* text = "contract c {\n" |
|||
" modifier mod1(uint a) { if (msg.sender == a) _ }\n" |
|||
" modifier mod2 { if (msg.sender == 2) _ }\n" |
|||
" function f() mod1(7) mod2 { }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(fallback_function) |
|||
{ |
|||
char const* text = "contract c {\n" |
|||
" function() { }\n" |
|||
"}\n"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(event) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
event e(); |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(event_arguments) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
event e(uint a, bytes32 s); |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(event_arguments_indexed) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
event e(uint a, bytes32 indexed s, bool indexed b); |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(visibility_specifiers) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
uint private a; |
|||
uint internal b; |
|||
uint public c; |
|||
uint d; |
|||
function f() {} |
|||
function f_priv() private {} |
|||
function f_public() public {} |
|||
function f_internal() internal {} |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
uint private internal a; |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
function c () |
|||
{ |
|||
a = 1 wei; |
|||
b = 2 szabo; |
|||
c = 3 finney; |
|||
b = 4 ether; |
|||
} |
|||
uint256 a; |
|||
uint256 b; |
|||
uint256 c; |
|||
uint256 d; |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations_in_expressions) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
function c () |
|||
{ |
|||
a = 1 wei * 100 wei + 7 szabo - 3; |
|||
} |
|||
uint256 a; |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(enum_valid_declaration) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
enum validEnum { Value1, Value2, Value3, Value4 } |
|||
function c () |
|||
{ |
|||
a = foo.Value3; |
|||
} |
|||
uint256 a; |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_enum_declaration) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
enum foo { } |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(malformed_enum_declaration) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
enum foo { WARNING,} |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(external_function) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
function x() external {} |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(external_variable) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
uint external x; |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(arrays_in_storage) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
uint[10] a; |
|||
uint[] a2; |
|||
struct x { uint[2**20] b; y[0] c; } |
|||
struct y { uint d; mapping(uint=>x)[] e; } |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(arrays_in_events) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
event e(uint[10] a, bytes7[8] indexed b, c[3] x); |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(arrays_in_expressions) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
function f() { c[10] a = 7; uint8[10 * 2] x; } |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multi_arrays) |
|||
{ |
|||
char const* text = R"( |
|||
contract c { |
|||
mapping(uint => mapping(uint => int8)[8][][9])[] x; |
|||
})"; |
|||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(constant_is_keyword) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
uint constant = 4; |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(var_array) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
function f() { var[] a; } |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(location_specifiers_for_params) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
function f(uint[] storage constant x, uint[] memory y) { } |
|||
} |
|||
)"; |
|||
BOOST_CHECK_NO_THROW(parseText(text)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(location_specifiers_for_locals) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
function f() { |
|||
uint[] storage x; |
|||
uint[] memory y; |
|||
} |
|||
} |
|||
)"; |
|||
BOOST_CHECK_NO_THROW(parseText(text)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(location_specifiers_for_state) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
uint[] memory x; |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(location_specifiers_with_var) |
|||
{ |
|||
char const* text = R"( |
|||
contract Foo { |
|||
function f() { var memory x; } |
|||
})"; |
|||
BOOST_CHECK_THROW(parseText(text), ParserError); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,288 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity scanner. |
|||
*/ |
|||
|
|||
#include <libsolidity/Scanner.h> |
|||
#include <boost/test/unit_test.hpp> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityScanner) |
|||
|
|||
BOOST_AUTO_TEST_CASE(test_empty) |
|||
{ |
|||
Scanner scanner(CharStream("")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(smoke_test) |
|||
{ |
|||
Scanner scanner(CharStream("function break;765 \t \"string1\",'string2'\nidentifier1")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Function); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Break); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "765"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string1"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Comma); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string2"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "identifier1"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(string_escapes) |
|||
{ |
|||
Scanner scanner(CharStream(" { \"a\\x61\"")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBrace); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "aa"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(string_escapes_with_zero) |
|||
{ |
|||
Scanner scanner(CharStream(" { \"a\\x61\\x00abc\"")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBrace); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), std::string("aa\0abc", 6)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(string_escape_illegal) |
|||
{ |
|||
Scanner scanner(CharStream(" bla \"\\x6rf\" (illegalescape)")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), ""); |
|||
// TODO recovery from illegal tokens should be improved
|
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(hex_numbers) |
|||
{ |
|||
Scanner scanner(CharStream("var x = 0x765432536763762734623472346;")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Var); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x765432536763762734623472346"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(negative_numbers) |
|||
{ |
|||
Scanner scanner(CharStream("var x = -.2 + -0x78 + -7.3 + 8.9;")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Var); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), ".2"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x78"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "7.3"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "8.9"); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(locations) |
|||
{ |
|||
Scanner scanner(CharStream("function_identifier has ; -0x743/*comment*/\n ident //comment")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 0); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 19); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 20); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 23); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 24); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 25); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 27); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 32); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 45); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 50); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(ambiguities) |
|||
{ |
|||
// test scanning of some operators which need look-ahead
|
|||
Scanner scanner(CharStream("<=""<""+ +=a++ =>""<<")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LessThanOrEqual); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::LessThan); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::AssignAdd); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Inc); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Arrow); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin) |
|||
{ |
|||
Scanner scanner(CharStream("/// Send $(value / 1000) chocolates to the user")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed_begin) |
|||
{ |
|||
Scanner scanner(CharStream("/** Send $(value / 1000) chocolates to the user*/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(documentation_comments_parsed) |
|||
{ |
|||
Scanner scanner(CharStream("some other tokens /// Send $(value / 1000) chocolates to the user")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed) |
|||
{ |
|||
Scanner scanner(CharStream("some other tokens /**\n" |
|||
"* Send $(value / 1000) chocolates to the user\n" |
|||
"*/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiline_documentation_no_stars) |
|||
{ |
|||
Scanner scanner(CharStream("some other tokens /**\n" |
|||
" Send $(value / 1000) chocolates to the user\n" |
|||
"*/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiline_documentation_whitespace_hell) |
|||
{ |
|||
Scanner scanner(CharStream("some other tokens /** \t \r \n" |
|||
"\t \r * Send $(value / 1000) chocolates to the user\n" |
|||
"*/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(comment_before_eos) |
|||
{ |
|||
Scanner scanner(CharStream("//")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(documentation_comment_before_eos) |
|||
{ |
|||
Scanner scanner(CharStream("///")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_multiline_comment) |
|||
{ |
|||
Scanner scanner(CharStream("/**/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_multiline_documentation_comment_before_eos) |
|||
{ |
|||
Scanner scanner(CharStream("/***/")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(comments_mixed_in_sequence) |
|||
{ |
|||
Scanner scanner(CharStream("hello_world ///documentation comment \n" |
|||
"//simple comment \n" |
|||
"<<")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "documentation comment "); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(ether_subdenominations) |
|||
{ |
|||
Scanner scanner(CharStream("wei szabo finney ether")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::SubWei); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubSzabo); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubFinney); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubEther); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(time_subdenominations) |
|||
{ |
|||
Scanner scanner(CharStream("seconds minutes hours days weeks years")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::SubSecond); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubMinute); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubHour); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubDay); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubWeek); |
|||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubYear); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(time_after) |
|||
{ |
|||
Scanner scanner(CharStream("after 1")); |
|||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::After); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
@ -1,93 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Unit tests for the type system of Solidity. |
|||
*/ |
|||
|
|||
#include <libsolidity/Types.h> |
|||
#include <boost/test/unit_test.hpp> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityTypes) |
|||
|
|||
BOOST_AUTO_TEST_CASE(storage_layout_simple) |
|||
{ |
|||
MemberList members(MemberList::MemberMap({ |
|||
{string("first"), Type::fromElementaryTypeName("uint128")}, |
|||
{string("second"), Type::fromElementaryTypeName("uint120")}, |
|||
{string("wraps"), Type::fromElementaryTypeName("uint16")} |
|||
})); |
|||
BOOST_REQUIRE_EQUAL(u256(2), members.getStorageSize()); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("wraps") != nullptr); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(0), unsigned(16))); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("wraps") == make_pair(u256(1), unsigned(0))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(storage_layout_mapping) |
|||
{ |
|||
MemberList members(MemberList::MemberMap({ |
|||
{string("first"), Type::fromElementaryTypeName("uint128")}, |
|||
{string("second"), make_shared<MappingType>( |
|||
Type::fromElementaryTypeName("uint8"), |
|||
Type::fromElementaryTypeName("uint8") |
|||
)}, |
|||
{string("third"), Type::fromElementaryTypeName("uint16")}, |
|||
{string("final"), make_shared<MappingType>( |
|||
Type::fromElementaryTypeName("uint8"), |
|||
Type::fromElementaryTypeName("uint8") |
|||
)}, |
|||
})); |
|||
BOOST_REQUIRE_EQUAL(u256(4), members.getStorageSize()); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("third") != nullptr); |
|||
BOOST_REQUIRE(members.getMemberStorageOffset("final") != nullptr); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(1), unsigned(0))); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("third") == make_pair(u256(2), unsigned(0))); |
|||
BOOST_CHECK(*members.getMemberStorageOffset("final") == make_pair(u256(3), unsigned(0))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(storage_layout_arrays) |
|||
{ |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(1), 32).getStorageSize() == 1); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(1), 33).getStorageSize() == 2); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(2), 31).getStorageSize() == 2); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(7), 8).getStorageSize() == 2); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(7), 9).getStorageSize() == 3); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(31), 9).getStorageSize() == 9); |
|||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(32), 9).getStorageSize() == 9); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
@ -1,310 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Framework for executing Solidity contracts and testing them against C++ implementation. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <tuple> |
|||
#include "../TestHelper.h" |
|||
#include <libethcore/ABI.h> |
|||
#include <libethereum/State.h> |
|||
#include <libethereum/Executive.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/Exceptions.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class ExecutionFramework |
|||
{ |
|||
public: |
|||
ExecutionFramework() |
|||
{ |
|||
if (g_logVerbosity != -1) |
|||
g_logVerbosity = 0; |
|||
//m_state.resetCurrent();
|
|||
} |
|||
|
|||
bytes const& compileAndRunWithoutCheck( |
|||
std::string const& _sourceCode, |
|||
u256 const& _value = 0, |
|||
std::string const& _contractName = "", |
|||
bytes const& _arguments = bytes() |
|||
) |
|||
{ |
|||
m_compiler.reset(false, m_addStandardSources); |
|||
m_compiler.addSource("", _sourceCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); |
|||
bytes code = m_compiler.getBytecode(_contractName); |
|||
sendMessage(code + _arguments, true, _value); |
|||
return m_output; |
|||
} |
|||
|
|||
template <class Exceptiontype> |
|||
void compileRequireThrow(std::string const& _sourceCode) |
|||
{ |
|||
m_compiler.reset(false, m_addStandardSources); |
|||
m_compiler.addSource("", _sourceCode); |
|||
BOOST_REQUIRE_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), Exceptiontype); |
|||
} |
|||
|
|||
bytes const& compileAndRun( |
|||
std::string const& _sourceCode, |
|||
u256 const& _value = 0, |
|||
std::string const& _contractName = "", |
|||
bytes const& _arguments = bytes() |
|||
) |
|||
{ |
|||
compileAndRunWithoutCheck(_sourceCode, _value, _contractName, _arguments); |
|||
BOOST_REQUIRE(!m_output.empty()); |
|||
return m_output; |
|||
} |
|||
|
|||
template <class... Args> |
|||
bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) |
|||
{ |
|||
FixedHash<4> hash(dev::sha3(_sig)); |
|||
sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value); |
|||
return m_output; |
|||
} |
|||
|
|||
template <class... Args> |
|||
bytes const& callContractFunction(std::string _sig, Args const&... _arguments) |
|||
{ |
|||
return callContractFunctionWithValue(_sig, 0, _arguments...); |
|||
} |
|||
|
|||
template <class CppFunction, class... Args> |
|||
void testSolidityAgainstCpp(std::string _sig, CppFunction const& _cppFunction, Args const&... _arguments) |
|||
{ |
|||
bytes solidityResult = callContractFunction(_sig, _arguments...); |
|||
bytes cppResult = callCppAndEncodeResult(_cppFunction, _arguments...); |
|||
BOOST_CHECK_MESSAGE( |
|||
solidityResult == cppResult, |
|||
"Computed values do not match.\nSolidity: " + |
|||
toHex(solidityResult) + |
|||
"\nC++: " + |
|||
toHex(cppResult)); |
|||
} |
|||
|
|||
template <class CppFunction, class... Args> |
|||
void testSolidityAgainstCppOnRange(std::string _sig, CppFunction const& _cppFunction, u256 const& _rangeStart, u256 const& _rangeEnd) |
|||
{ |
|||
for (u256 argument = _rangeStart; argument < _rangeEnd; ++argument) |
|||
{ |
|||
bytes solidityResult = callContractFunction(_sig, argument); |
|||
bytes cppResult = callCppAndEncodeResult(_cppFunction, argument); |
|||
BOOST_CHECK_MESSAGE( |
|||
solidityResult == cppResult, |
|||
"Computed values do not match.\nSolidity: " + |
|||
toHex(solidityResult) + |
|||
"\nC++: " + |
|||
toHex(cppResult) + |
|||
"\nArgument: " + |
|||
toHex(encode(argument)) |
|||
); |
|||
} |
|||
} |
|||
|
|||
static bytes encode(bool _value) { return encode(byte(_value)); } |
|||
static bytes encode(int _value) { return encode(u256(_value)); } |
|||
static bytes encode(size_t _value) { return encode(u256(_value)); } |
|||
static bytes encode(char const* _value) { return encode(std::string(_value)); } |
|||
static bytes encode(byte _value) { return bytes(31, 0) + bytes{_value}; } |
|||
static bytes encode(u256 const& _value) { return toBigEndian(_value); } |
|||
static bytes encode(h256 const& _value) { return _value.asBytes(); } |
|||
static bytes encode(bytes const& _value, bool _padLeft = true) |
|||
{ |
|||
bytes padding = bytes((32 - _value.size() % 32) % 32, 0); |
|||
return _padLeft ? padding + _value : _value + padding; |
|||
} |
|||
static bytes encode(std::string const& _value) { return encode(asBytes(_value), false); } |
|||
template <class _T> |
|||
static bytes encode(std::vector<_T> const& _value) |
|||
{ |
|||
bytes ret; |
|||
for (auto const& v: _value) |
|||
ret += encode(v); |
|||
return ret; |
|||
} |
|||
|
|||
template <class FirstArg, class... Args> |
|||
static bytes encodeArgs(FirstArg const& _firstArg, Args const&... _followingArgs) |
|||
{ |
|||
return encode(_firstArg) + encodeArgs(_followingArgs...); |
|||
} |
|||
static bytes encodeArgs() |
|||
{ |
|||
return bytes(); |
|||
} |
|||
//@todo might be extended in the future
|
|||
template <class Arg> |
|||
static bytes encodeDyn(Arg const& _arg) |
|||
{ |
|||
return encodeArgs(u256(0x20), u256(_arg.size()), _arg); |
|||
} |
|||
|
|||
class ContractInterface |
|||
{ |
|||
public: |
|||
ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} |
|||
|
|||
void setNextValue(u256 const& _value) { m_nextValue = _value; } |
|||
|
|||
protected: |
|||
template <class... Args> |
|||
bytes const& call(std::string const& _sig, Args const&... _arguments) |
|||
{ |
|||
auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); |
|||
m_nextValue = 0; |
|||
return ret; |
|||
} |
|||
|
|||
void callString(std::string const& _name, std::string const& _arg) |
|||
{ |
|||
BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); |
|||
} |
|||
|
|||
void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) |
|||
{ |
|||
BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); |
|||
} |
|||
|
|||
void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) |
|||
{ |
|||
BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); |
|||
} |
|||
|
|||
void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) |
|||
{ |
|||
BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); |
|||
} |
|||
|
|||
u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) |
|||
{ |
|||
bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); |
|||
BOOST_REQUIRE(ret.size() == 0x20); |
|||
BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); |
|||
return eth::abiOut<u160>(ret); |
|||
} |
|||
|
|||
std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) |
|||
{ |
|||
bytesConstRef ret = ref(call(_name + "(address)", _arg)); |
|||
BOOST_REQUIRE(ret.size() >= 0x20); |
|||
u256 offset = eth::abiOut<u256>(ret); |
|||
BOOST_REQUIRE_EQUAL(offset, 0x20); |
|||
u256 len = eth::abiOut<u256>(ret); |
|||
BOOST_REQUIRE_EQUAL(ret.size(), ((len + 0x1f) / 0x20) * 0x20); |
|||
return ret.cropped(0, size_t(len)).toString(); |
|||
} |
|||
|
|||
h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) |
|||
{ |
|||
bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); |
|||
BOOST_REQUIRE(ret.size() == 0x20); |
|||
return eth::abiOut<h256>(ret); |
|||
} |
|||
|
|||
private: |
|||
u256 m_nextValue; |
|||
ExecutionFramework& m_framework; |
|||
}; |
|||
|
|||
private: |
|||
template <class CppFunction, class... Args> |
|||
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments) |
|||
-> typename std::enable_if<std::is_void<decltype(_cppFunction(_arguments...))>::value, bytes>::type |
|||
{ |
|||
_cppFunction(_arguments...); |
|||
return bytes(); |
|||
} |
|||
template <class CppFunction, class... Args> |
|||
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments) |
|||
-> typename std::enable_if<!std::is_void<decltype(_cppFunction(_arguments...))>::value, bytes>::type |
|||
{ |
|||
return encode(_cppFunction(_arguments...)); |
|||
} |
|||
|
|||
protected: |
|||
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0) |
|||
{ |
|||
m_state.addBalance(m_sender, _value); // just in case
|
|||
eth::Executive executive(m_state, m_envInfo, 0); |
|||
eth::ExecutionResult res; |
|||
executive.setResultRecipient(res); |
|||
eth::Transaction t = |
|||
_isCreation ? |
|||
eth::Transaction(_value, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) : |
|||
eth::Transaction(_value, m_gasPrice, m_gas, m_contractAddress, _data, 0, KeyPair::create().sec()); |
|||
bytes transactionRLP = t.rlp(); |
|||
try |
|||
{ |
|||
// this will throw since the transaction is invalid, but it should nevertheless store the transaction
|
|||
executive.initialize(&transactionRLP); |
|||
executive.execute(); |
|||
} |
|||
catch (...) {} |
|||
if (_isCreation) |
|||
{ |
|||
BOOST_REQUIRE(!executive.create(m_sender, _value, m_gasPrice, m_gas, &_data, m_sender)); |
|||
m_contractAddress = executive.newAddress(); |
|||
BOOST_REQUIRE(m_contractAddress); |
|||
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); |
|||
} |
|||
else |
|||
{ |
|||
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); |
|||
BOOST_REQUIRE(!executive.call(m_contractAddress, m_sender, _value, m_gasPrice, &_data, m_gas)); |
|||
} |
|||
BOOST_REQUIRE(executive.go(/* DEBUG eth::Executive::simpleTrace() */)); |
|||
m_state.noteSending(m_sender); |
|||
executive.finalize(); |
|||
m_gasUsed = res.gasUsed; |
|||
m_output = std::move(res.output); |
|||
m_logs = executive.logs(); |
|||
} |
|||
|
|||
size_t m_optimizeRuns = 200; |
|||
bool m_optimize = false; |
|||
bool m_addStandardSources = false; |
|||
dev::solidity::CompilerStack m_compiler; |
|||
Address m_sender; |
|||
Address m_contractAddress; |
|||
eth::EnvInfo m_envInfo; |
|||
eth::State m_state; |
|||
u256 const m_gasPrice = 100 * eth::szabo; |
|||
u256 const m_gas = 100000000; |
|||
bytes m_output; |
|||
eth::LogEntries m_logs; |
|||
u256 m_gasUsed; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
|||
|
Loading…
Reference in new issue