Merge pull request #1 from chriseth/master

Move libsolidity files from cpp-ethereum.
This commit is contained in:
chriseth 2015-08-19 12:57:16 +02:00
commit 22a7278fdd
51 changed files with 18332 additions and 0 deletions

1164
src/AST.cpp Normal file

File diff suppressed because it is too large Load Diff

1355
src/AST.h Normal file

File diff suppressed because it is too large Load Diff

94
src/ASTForward.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}
}

View 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*>({});
}

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

File diff suppressed because it is too large Load Diff

134
src/ExpressionCompiler.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

148
src/Parser.h Normal file
View 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
View 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
View 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;
};
}
}

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

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

File diff suppressed because it is too large Load Diff

935
src/Types.h Normal file
View 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
View 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
View 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
View 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
View 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 ')'