mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1 from chriseth/master
Move libsolidity files from cpp-ethereum.
This commit is contained in:
commit
22a7278fdd
1164
src/AST.cpp
Normal file
1164
src/AST.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
src/ASTForward.h
Normal file
94
src/ASTForward.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
}
|
437
src/ASTJsonConverter.cpp
Normal file
437
src/ASTJsonConverter.cpp
Normal file
@ -0,0 +1,437 @@
|
||||
/*
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
130
src/ASTJsonConverter.h
Normal file
130
src/ASTJsonConverter.h
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
538
src/ASTPrinter.cpp
Normal file
538
src/ASTPrinter.cpp
Normal file
@ -0,0 +1,538 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
141
src/ASTPrinter.h
Normal file
141
src/ASTPrinter.h
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
48
src/ASTUtils.cpp
Normal file
48
src/ASTUtils.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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;
|
||||
}
|
54
src/ASTUtils.h
Normal file
54
src/ASTUtils.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
286
src/ASTVisitor.h
Normal file
286
src/ASTVisitor.h
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
685
src/AST_accept.h
Normal file
685
src/AST_accept.h
Normal file
@ -0,0 +1,685 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
786
src/ArrayUtils.cpp
Normal file
786
src/ArrayUtils.cpp
Normal file
@ -0,0 +1,786 @@
|
||||
/*
|
||||
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;
|
||||
}
|
98
src/ArrayUtils.h
Normal file
98
src/ArrayUtils.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
25
src/CMakeLists.txt
Normal file
25
src/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
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} )
|
||||
|
726
src/Compiler.cpp
Normal file
726
src/Compiler.cpp
Normal file
@ -0,0 +1,726 @@
|
||||
/*
|
||||
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;
|
||||
}
|
143
src/Compiler.h
Normal file
143
src/Compiler.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
214
src/CompilerContext.cpp
Normal file
214
src/CompilerContext.cpp
Normal file
@ -0,0 +1,214 @@
|
||||
/*
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
183
src/CompilerContext.h
Normal file
183
src/CompilerContext.h
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
391
src/CompilerStack.cpp
Normal file
391
src/CompilerStack.cpp
Normal file
@ -0,0 +1,391 @@
|
||||
/*
|
||||
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>()) {}
|
||||
|
||||
}
|
||||
}
|
198
src/CompilerStack.h
Normal file
198
src/CompilerStack.h
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
734
src/CompilerUtils.cpp
Normal file
734
src/CompilerUtils.cpp
Normal file
@ -0,0 +1,734 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
178
src/CompilerUtils.h
Normal file
178
src/CompilerUtils.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
85
src/DeclarationContainer.cpp
Normal file
85
src/DeclarationContainer.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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*>({});
|
||||
}
|
65
src/DeclarationContainer.h
Normal file
65
src/DeclarationContainer.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
60
src/Exceptions.h
Normal file
60
src/Exceptions.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
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>;
|
||||
|
||||
}
|
||||
}
|
1271
src/ExpressionCompiler.cpp
Normal file
1271
src/ExpressionCompiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
src/ExpressionCompiler.h
Normal file
134
src/ExpressionCompiler.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
191
src/GasEstimator.cpp
Normal file
191
src/GasEstimator.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
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;
|
||||
}
|
83
src/GasEstimator.h
Normal file
83
src/GasEstimator.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
96
src/GlobalContext.cpp
Normal file
96
src/GlobalContext.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
64
src/GlobalContext.h
Normal file
64
src/GlobalContext.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
446
src/InterfaceHandler.cpp
Normal file
446
src/InterfaceHandler.cpp
Normal file
@ -0,0 +1,446 @@
|
||||
|
||||
#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
|
132
src/InterfaceHandler.h
Normal file
132
src/InterfaceHandler.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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
|
457
src/LValue.cpp
Normal file
457
src/LValue.cpp
Normal file
@ -0,0 +1,457 @@
|
||||
/*
|
||||
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);
|
||||
}
|
196
src/LValue.h
Normal file
196
src/LValue.h
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
530
src/NameAndTypeResolver.cpp
Normal file
530
src/NameAndTypeResolver.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
160
src/NameAndTypeResolver.h
Normal file
160
src/NameAndTypeResolver.h
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
1082
src/Parser.cpp
Normal file
1082
src/Parser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
148
src/Parser.h
Normal file
148
src/Parser.h
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
769
src/Scanner.cpp
Normal file
769
src/Scanner.cpp
Normal file
@ -0,0 +1,769 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
224
src/Scanner.h
Normal file
224
src/Scanner.h
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
126
src/SourceReferenceFormatter.cpp
Normal file
126
src/SourceReferenceFormatter.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
54
src/SourceReferenceFormatter.h
Normal file
54
src/SourceReferenceFormatter.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
100
src/Token.cpp
Normal file
100
src/Token.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
// 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
|
||||
|
||||
}
|
||||
}
|
406
src/Token.h
Normal file
406
src/Token.h
Normal file
@ -0,0 +1,406 @@
|
||||
// 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];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
1758
src/Types.cpp
Normal file
1758
src/Types.cpp
Normal file
File diff suppressed because it is too large
Load Diff
935
src/Types.h
Normal file
935
src/Types.h
Normal file
@ -0,0 +1,935 @@
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
30
src/Utils.h
Normal file
30
src/Utils.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
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)
|
||||
|
39
src/Version.cpp
Normal file
39
src/Version.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
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);
|
||||
|
36
src/Version.h
Normal file
36
src/Version.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
47
src/grammar.txt
Normal file
47
src/grammar.txt
Normal file
@ -0,0 +1,47 @@
|
||||
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 ')'
|
Loading…
Reference in New Issue
Block a user