aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore31
-rw-r--r--CMakeLists.txt50
-rw-r--r--LICENSE22
-rw-r--r--README.md2
-rw-r--r--libsolidity/AST.cpp1164
-rw-r--r--libsolidity/AST.h1355
-rw-r--r--libsolidity/ASTForward.h94
-rw-r--r--libsolidity/ASTJsonConverter.cpp437
-rw-r--r--libsolidity/ASTJsonConverter.h130
-rw-r--r--libsolidity/ASTPrinter.cpp538
-rw-r--r--libsolidity/ASTPrinter.h141
-rw-r--r--libsolidity/ASTUtils.cpp48
-rw-r--r--libsolidity/ASTUtils.h54
-rw-r--r--libsolidity/ASTVisitor.h286
-rw-r--r--libsolidity/AST_accept.h685
-rw-r--r--libsolidity/ArrayUtils.cpp786
-rw-r--r--libsolidity/ArrayUtils.h98
-rw-r--r--libsolidity/CMakeLists.txt25
-rw-r--r--libsolidity/Compiler.cpp726
-rw-r--r--libsolidity/Compiler.h143
-rw-r--r--libsolidity/CompilerContext.cpp214
-rw-r--r--libsolidity/CompilerContext.h183
-rw-r--r--libsolidity/CompilerStack.cpp391
-rw-r--r--libsolidity/CompilerStack.h198
-rw-r--r--libsolidity/CompilerUtils.cpp734
-rw-r--r--libsolidity/CompilerUtils.h178
-rw-r--r--libsolidity/DeclarationContainer.cpp85
-rw-r--r--libsolidity/DeclarationContainer.h65
-rw-r--r--libsolidity/Exceptions.h60
-rw-r--r--libsolidity/ExpressionCompiler.cpp1271
-rw-r--r--libsolidity/ExpressionCompiler.h134
-rw-r--r--libsolidity/GasEstimator.cpp191
-rw-r--r--libsolidity/GasEstimator.h83
-rw-r--r--libsolidity/GlobalContext.cpp96
-rw-r--r--libsolidity/GlobalContext.h64
-rw-r--r--libsolidity/InterfaceHandler.cpp446
-rw-r--r--libsolidity/InterfaceHandler.h132
-rw-r--r--libsolidity/LValue.cpp457
-rw-r--r--libsolidity/LValue.h196
-rw-r--r--libsolidity/NameAndTypeResolver.cpp530
-rw-r--r--libsolidity/NameAndTypeResolver.h160
-rw-r--r--libsolidity/Parser.cpp1082
-rw-r--r--libsolidity/Parser.h148
-rw-r--r--libsolidity/Scanner.cpp769
-rw-r--r--libsolidity/Scanner.h224
-rw-r--r--libsolidity/SourceReferenceFormatter.cpp126
-rw-r--r--libsolidity/SourceReferenceFormatter.h54
-rw-r--r--libsolidity/Token.cpp100
-rw-r--r--libsolidity/Token.h406
-rw-r--r--libsolidity/Types.cpp1758
-rw-r--r--libsolidity/Types.h935
-rw-r--r--libsolidity/Utils.h30
-rw-r--r--libsolidity/Version.cpp39
-rw-r--r--libsolidity/Version.h36
-rw-r--r--libsolidity/grammar.txt47
-rw-r--r--solc/CMakeLists.txt29
-rw-r--r--solc/CommandLineInterface.cpp635
-rw-r--r--solc/CommandLineInterface.h74
-rw-r--r--solc/docker_emscripten/Dockerfile70
-rw-r--r--solc/jsonCompiler.cpp196
-rw-r--r--solc/main.cpp35
61 files changed, 19476 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..675407ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# Build directory
+build/ \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..e3c48341
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 2.8.12)
+
+cmake_policy(SET CMP0015 NEW)
+# let cmake autolink dependencies on windows
+cmake_policy(SET CMP0020 NEW)
+# this policy was introduced in cmake 3.0
+# remove if, once 3.0 will be used on unix
+if (${CMAKE_MAJOR_VERSION} GREATER 2)
+ cmake_policy(SET CMP0043 OLD)
+endif()
+
+# TODO use version from Version.h
+set(PROJECT_VERSION "0.9.42")
+if (${CMAKE_VERSION} VERSION_GREATER 3.0)
+ cmake_policy(SET CMP0042 OLD) # fix MACOSX_RPATH
+ cmake_policy(SET CMP0048 NEW) # allow VERSION argument in project()
+ project(solidity VERSION ${PROJECT_VERSION})
+else()
+ project(solidity)
+endif()
+
+# Figure out environment.
+set(CPPETHEREUM_BUILD "${CMAKE_CURRENT_SOURCE_DIR}/../cpp-ethereum/${BUILD_DIR_NAME}" CACHE STRING "The absolute build directory of cpp-ethereum.")
+set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/../cpp-ethereum-cmake" CACHE PATH "The the path to the cmake directory")
+set(ETH_DIR "${CMAKE_CURRENT_LIST_DIR}/../cpp-ethereum" CACHE PATH "The path to the cpp-ethereum directory")
+set(BUILD_DIR_NAME "build" CACHE STRING "The name of the build directory in cpp-ethereum")
+set(ETH_BUILD_DIR "${ETH_DIR}/${BUILD_DIR_NAME}")
+
+list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR})
+
+include(EthDependencies)
+include(EthExecutableHelper)
+include(EthCompilerSettings)
+
+include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS})
+include_directories(BEFORE ..)
+include_directories(${Boost_INCLUDE_DIRS})
+include_directories(libsolidity)
+# Include a directory with BuildInfo.h
+include_directories(${ETH_BUILD_DIR})
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../cpp-ethereum)
+include_directories(${CPPETHEREUM_BUILD})
+
+file(GLOB_RECURSE SRC_LIST "libsolidity/*.cpp")
+file(GLOB_RECURSE HEADERS "libsolidity/*.h")
+
+set(EXECUTABLE solidity)
+add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
+
+# TODO installation and packaging rules
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..20efd1b3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..f3068674
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# solidity
+The Solidity Contract-Oriented Programming Langauge
diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp
new file mode 100644
index 00000000..8bad6ccf
--- /dev/null
+++ b/libsolidity/AST.cpp
@@ -0,0 +1,1164 @@
+/*
+ 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 abstract syntax tree.
+ */
+
+#include <algorithm>
+#include <functional>
+#include <boost/range/adaptor/reversed.hpp>
+#include <libsolidity/Utils.h>
+#include <libsolidity/AST.h>
+#include <libsolidity/ASTVisitor.h>
+#include <libsolidity/Exceptions.h>
+#include <libsolidity/AST_accept.h>
+
+#include <libdevcore/SHA3.h>
+
+using namespace std;
+
+namespace dev
+{
+namespace solidity
+{
+
+TypeError ASTNode::createTypeError(string const& _description) const
+{
+ return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description);
+}
+
+TypePointer ContractDefinition::getType(ContractDefinition const* _currentContract) const
+{
+ return make_shared<TypeType>(make_shared<ContractType>(*this), _currentContract);
+}
+
+void ContractDefinition::checkTypeRequirements()
+{
+ for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: getBaseContracts())
+ baseSpecifier->checkTypeRequirements();
+
+ checkDuplicateFunctions();
+ checkIllegalOverrides();
+ checkAbstractFunctions();
+ checkAbstractConstructors();
+
+ FunctionDefinition const* constructor = getConstructor();
+ if (constructor && !constructor->getReturnParameters().empty())
+ BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError(
+ "Non-empty \"returns\" directive for constructor."));
+
+ FunctionDefinition const* fallbackFunction = nullptr;
+ for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
+ {
+ if (function->getName().empty())
+ {
+ if (fallbackFunction)
+ BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_comment("Only one fallback function is allowed."));
+ else
+ {
+ fallbackFunction = function.get();
+ if (!fallbackFunction->getParameters().empty())
+ BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters."));
+ }
+ }
+ if (!function->isFullyImplemented())
+ setFullyImplemented(false);
+ }
+ for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers())
+ modifier->checkTypeRequirements();
+
+ for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
+ function->checkTypeRequirements();
+
+ for (ASTPointer<VariableDeclaration> const& variable: m_stateVariables)
+ variable->checkTypeRequirements();
+
+ checkExternalTypeClashes();
+ // check for hash collisions in function signatures
+ set<FixedHash<4>> hashes;
+ for (auto const& it: getInterfaceFunctionList())
+ {
+ FixedHash<4> const& hash = it.first;
+ if (hashes.count(hash))
+ BOOST_THROW_EXCEPTION(createTypeError(
+ string("Function signature hash collision for ") + it.second->externalSignature()
+ ));
+ hashes.insert(hash);
+ }
+}
+
+map<FixedHash<4>, FunctionTypePointer> ContractDefinition::getInterfaceFunctions() const
+{
+ auto exportedFunctionList = getInterfaceFunctionList();
+
+ map<FixedHash<4>, FunctionTypePointer> exportedFunctions;
+ for (auto const& it: exportedFunctionList)
+ exportedFunctions.insert(it);
+
+ solAssert(exportedFunctionList.size() == exportedFunctions.size(),
+ "Hash collision at Function Definition Hash calculation");
+
+ return exportedFunctions;
+}
+
+FunctionDefinition const* ContractDefinition::getConstructor() const
+{
+ for (ASTPointer<FunctionDefinition> const& f: m_definedFunctions)
+ if (f->isConstructor())
+ return f.get();
+ return nullptr;
+}
+
+FunctionDefinition const* ContractDefinition::getFallbackFunction() const
+{
+ for (ContractDefinition const* contract: getLinearizedBaseContracts())
+ for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
+ if (f->getName().empty())
+ return f.get();
+ return nullptr;
+}
+
+void ContractDefinition::checkDuplicateFunctions() const
+{
+ /// Checks that two functions with the same name defined in this contract have different
+ /// argument types and that there is at most one constructor.
+ map<string, vector<FunctionDefinition const*>> functions;
+ for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
+ functions[function->getName()].push_back(function.get());
+
+ if (functions[getName()].size() > 1)
+ {
+ SecondarySourceLocation ssl;
+ auto it = functions[getName()].begin();
+ ++it;
+ for (; it != functions[getName()].end(); ++it)
+ ssl.append("Another declaration is here:", (*it)->getLocation());
+
+ BOOST_THROW_EXCEPTION(
+ DeclarationError() <<
+ errinfo_sourceLocation(functions[getName()].front()->getLocation()) <<
+ errinfo_comment("More than one constructor defined.") <<
+ errinfo_secondarySourceLocation(ssl)
+ );
+ }
+ for (auto const& it: functions)
+ {
+ vector<FunctionDefinition const*> const& overloads = it.second;
+ for (size_t i = 0; i < overloads.size(); ++i)
+ for (size_t j = i + 1; j < overloads.size(); ++j)
+ if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
+ BOOST_THROW_EXCEPTION(
+ DeclarationError() <<
+ errinfo_sourceLocation(overloads[j]->getLocation()) <<
+ errinfo_comment("Function with same name and arguments defined twice.") <<
+ errinfo_secondarySourceLocation(SecondarySourceLocation().append(
+ "Other declaration is here:", overloads[i]->getLocation()))
+ );
+ }
+}
+
+void ContractDefinition::checkAbstractFunctions()
+{
+ // Mapping from name to function definition (exactly one per argument type equality class) and
+ // flag to indicate whether it is fully implemented.
+ using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
+ map<string, vector<FunTypeAndFlag>> functions;
+
+ // Search from base to derived
+ for (ContractDefinition const* contract: boost::adaptors::reverse(getLinearizedBaseContracts()))
+ for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions())
+ {
+ auto& overloads = functions[function->getName()];
+ FunctionTypePointer funType = make_shared<FunctionType>(*function);
+ auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
+ {
+ return funType->hasEqualArgumentTypes(*_funAndFlag.first);
+ });
+ if (it == overloads.end())
+ overloads.push_back(make_pair(funType, function->isFullyImplemented()));
+ else if (it->second)
+ {
+ if (!function->isFullyImplemented())
+ BOOST_THROW_EXCEPTION(function->createTypeError("Redeclaring an already implemented function as abstract"));
+ }
+ else if (function->isFullyImplemented())
+ it->second = true;
+ }
+
+ // Set to not fully implemented if at least one flag is false.
+ for (auto const& it: functions)
+ for (auto const& funAndFlag: it.second)
+ if (!funAndFlag.second)
+ {
+ setFullyImplemented(false);
+ return;
+ }
+}
+
+void ContractDefinition::checkAbstractConstructors()
+{
+ set<ContractDefinition const*> argumentsNeeded;
+ // check that we get arguments for all base constructors that need it.
+ // If not mark the contract as abstract (not fully implemented)
+
+ vector<ContractDefinition const*> const& bases = getLinearizedBaseContracts();
+ for (ContractDefinition const* contract: bases)
+ if (FunctionDefinition const* constructor = contract->getConstructor())
+ if (contract != this && !constructor->getParameters().empty())
+ argumentsNeeded.insert(contract);
+
+ 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)
+ argumentsNeeded.erase(baseContract);
+ }
+
+
+ for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts())
+ {
+ auto baseContract = dynamic_cast<ContractDefinition const*>(
+ &base->getName()->getReferencedDeclaration()
+ );
+ solAssert(baseContract, "");
+ if (!base->getArguments().empty())
+ argumentsNeeded.erase(baseContract);
+ }
+ }
+ if (!argumentsNeeded.empty())
+ setFullyImplemented(false);
+}
+
+void ContractDefinition::checkIllegalOverrides() const
+{
+ // TODO unify this at a later point. for this we need to put the constness and the access specifier
+ // into the types
+ map<string, vector<FunctionDefinition const*>> functions;
+ map<string, ModifierDefinition const*> modifiers;
+
+ // We search from derived to base, so the stored item causes the error.
+ for (ContractDefinition const* contract: getLinearizedBaseContracts())
+ {
+ for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions())
+ {
+ if (function->isConstructor())
+ continue; // constructors can neither be overridden nor override anything
+ string const& name = function->getName();
+ if (modifiers.count(name))
+ BOOST_THROW_EXCEPTION(modifiers[name]->createTypeError("Override changes function to modifier."));
+ FunctionType functionType(*function);
+ // function should not change the return type
+ for (FunctionDefinition const* overriding: functions[name])
+ {
+ FunctionType overridingType(*overriding);
+ if (!overridingType.hasEqualArgumentTypes(functionType))
+ continue;
+ if (
+ overriding->getVisibility() != function->getVisibility() ||
+ overriding->isDeclaredConst() != function->isDeclaredConst() ||
+ overridingType != functionType
+ )
+ BOOST_THROW_EXCEPTION(overriding->createTypeError("Override changes extended function signature."));
+ }
+ functions[name].push_back(function.get());
+ }
+ for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers())
+ {
+ string const& name = modifier->getName();
+ ModifierDefinition const*& override = modifiers[name];
+ if (!override)
+ override = modifier.get();
+ else if (ModifierType(*override) != ModifierType(*modifier))
+ BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier signature."));
+ if (!functions[name].empty())
+ BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier to function."));
+ }
+ }
+}
+
+void ContractDefinition::checkExternalTypeClashes() const
+{
+ map<string, vector<pair<Declaration const*, shared_ptr<FunctionType>>>> externalDeclarations;
+ for (ContractDefinition const* contract: getLinearizedBaseContracts())
+ {
+ for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
+ if (f->isPartOfExternalInterface())
+ {
+ auto functionType = make_shared<FunctionType>(*f);
+ externalDeclarations[functionType->externalSignature(f->getName())].push_back(
+ make_pair(f.get(), functionType)
+ );
+ }
+ for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
+ if (v->isPartOfExternalInterface())
+ {
+ auto functionType = make_shared<FunctionType>(*v);
+ externalDeclarations[functionType->externalSignature(v->getName())].push_back(
+ make_pair(v.get(), functionType)
+ );
+ }
+ }
+ for (auto const& it: externalDeclarations)
+ for (size_t i = 0; i < it.second.size(); ++i)
+ for (size_t j = i + 1; j < it.second.size(); ++j)
+ if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second))
+ BOOST_THROW_EXCEPTION(it.second[j].first->createTypeError(
+ "Function overload clash during conversion to external types for arguments."
+ ));
+}
+
+vector<ASTPointer<EventDefinition>> const& ContractDefinition::getInterfaceEvents() const
+{
+ if (!m_interfaceEvents)
+ {
+ set<string> eventsSeen;
+ m_interfaceEvents.reset(new vector<ASTPointer<EventDefinition>>());
+ for (ContractDefinition const* contract: getLinearizedBaseContracts())
+ for (ASTPointer<EventDefinition> const& e: contract->getEvents())
+ if (eventsSeen.count(e->getName()) == 0)
+ {
+ eventsSeen.insert(e->getName());
+ m_interfaceEvents->push_back(e);
+ }
+ }
+ return *m_interfaceEvents;
+}
+
+vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getInterfaceFunctionList() const
+{
+ if (!m_interfaceFunctionList)
+ {
+ set<string> functionsSeen;
+ set<string> signaturesSeen;
+ m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>());
+ for (ContractDefinition const* contract: getLinearizedBaseContracts())
+ {
+ for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
+ {
+ if (!f->isPartOfExternalInterface())
+ continue;
+ string functionSignature = f->externalSignature();
+ if (signaturesSeen.count(functionSignature) == 0)
+ {
+ functionsSeen.insert(f->getName());
+ signaturesSeen.insert(functionSignature);
+ FixedHash<4> hash(dev::sha3(functionSignature));
+ m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false)));
+ }
+ }
+
+ for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
+ if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
+ {
+ FunctionType ftype(*v);
+ solAssert(v->getType().get(), "");
+ functionsSeen.insert(v->getName());
+ FixedHash<4> hash(dev::sha3(ftype.externalSignature(v->getName())));
+ m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v)));
+ }
+ }
+ }
+ return *m_interfaceFunctionList;
+}
+
+string const& ContractDefinition::devDocumentation() const
+{
+ return m_devDocumentation;
+}
+
+string const& ContractDefinition::userDocumentation() const
+{
+ return m_userDocumentation;
+}
+
+void ContractDefinition::setDevDocumentation(string const& _devDocumentation)
+{
+ m_devDocumentation = _devDocumentation;
+}
+
+void ContractDefinition::setUserDocumentation(string const& _userDocumentation)
+{
+ m_userDocumentation = _userDocumentation;
+}
+
+
+vector<Declaration const*> const& ContractDefinition::getInheritableMembers() const
+{
+ if (!m_inheritableMembers)
+ {
+ set<string> memberSeen;
+ m_inheritableMembers.reset(new vector<Declaration const*>());
+ auto addInheritableMember = [&](Declaration const* _decl)
+ {
+ if (memberSeen.count(_decl->getName()) == 0 && _decl->isVisibleInDerivedContracts())
+ {
+ memberSeen.insert(_decl->getName());
+ m_inheritableMembers->push_back(_decl);
+ }
+ };
+
+ for (ASTPointer<FunctionDefinition> const& f: getDefinedFunctions())
+ addInheritableMember(f.get());
+
+ for (ASTPointer<VariableDeclaration> const& v: getStateVariables())
+ addInheritableMember(v.get());
+
+ for (ASTPointer<StructDefinition> const& s: getDefinedStructs())
+ addInheritableMember(s.get());
+ }
+ return *m_inheritableMembers;
+}
+
+TypePointer EnumValue::getType(ContractDefinition const*) const
+{
+ EnumDefinition const* parentDef = dynamic_cast<EnumDefinition const*>(getScope());
+ solAssert(parentDef, "Enclosing Scope of EnumValue was not set");
+ return make_shared<EnumType>(*parentDef);
+}
+
+void InheritanceSpecifier::checkTypeRequirements()
+{
+ m_baseName->checkTypeRequirements(nullptr);
+ for (ASTPointer<Expression> const& argument: m_arguments)
+ argument->checkTypeRequirements(nullptr);
+
+ ContractDefinition const* base = dynamic_cast<ContractDefinition const*>(&m_baseName->getReferencedDeclaration());
+ solAssert(base, "Base contract not available.");
+ TypePointers parameterTypes = ContractType(*base).getConstructorType()->getParameterTypes();
+ if (!m_arguments.empty() && parameterTypes.size() != m_arguments.size())
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Wrong argument count for constructor call: " +
+ toString(m_arguments.size()) +
+ " arguments given but expected " +
+ toString(parameterTypes.size()) +
+ "."
+ ));
+
+ for (size_t i = 0; i < m_arguments.size(); ++i)
+ if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i]))
+ BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError(
+ "Invalid type for argument in constructor call. "
+ "Invalid implicit conversion from " +
+ m_arguments[i]->getType()->toString() +
+ " to " +
+ parameterTypes[i]->toString() +
+ " requested."
+ ));
+}
+
+TypePointer StructDefinition::getType(ContractDefinition const*) const
+{
+ return make_shared<TypeType>(make_shared<StructType>(*this));
+}
+
+void StructDefinition::checkMemberTypes() const
+{
+ for (ASTPointer<VariableDeclaration> const& member: getMembers())
+ if (!member->getType()->canBeStored())
+ BOOST_THROW_EXCEPTION(member->createTypeError("Type cannot be used in struct."));
+}
+
+void StructDefinition::checkRecursion() const
+{
+ using StructPointer = StructDefinition const*;
+ using StructPointersSet = set<StructPointer>;
+ function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents)
+ {
+ if (_parents.count(_struct))
+ BOOST_THROW_EXCEPTION(
+ ParserError() <<
+ errinfo_sourceLocation(_struct->getLocation()) <<
+ errinfo_comment("Recursive struct definition.")
+ );
+ set<StructDefinition const*> parents = _parents;
+ parents.insert(_struct);
+ for (ASTPointer<VariableDeclaration> const& member: _struct->getMembers())
+ if (member->getType()->getCategory() == Type::Category::Struct)
+ {
+ auto const& typeName = dynamic_cast<UserDefinedTypeName const&>(*member->getTypeName());
+ check(
+ &dynamic_cast<StructDefinition const&>(*typeName.getReferencedDeclaration()),
+ parents
+ );
+ }
+ };
+ check(this, StructPointersSet{});
+}
+
+TypePointer EnumDefinition::getType(ContractDefinition const*) const
+{
+ return make_shared<TypeType>(make_shared<EnumType>(*this));
+}
+
+TypePointer FunctionDefinition::getType(ContractDefinition const*) const
+{
+ return make_shared<FunctionType>(*this);
+}
+
+void FunctionDefinition::checkTypeRequirements()
+{
+ for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
+ {
+ if (!var->getType()->canLiveOutsideStorage())
+ BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
+ if (getVisibility() >= Visibility::Public && !(var->getType()->externalType()))
+ BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions."));
+ }
+ for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
+ modifier->checkTypeRequirements(isConstructor() ?
+ dynamic_cast<ContractDefinition const&>(*getScope()).getLinearizedBaseContracts() :
+ vector<ContractDefinition const*>());
+ if (m_body)
+ m_body->checkTypeRequirements();
+}
+
+string FunctionDefinition::externalSignature() const
+{
+ return FunctionType(*this).externalSignature(getName());
+}
+
+bool VariableDeclaration::isLValue() const
+{
+ // External function parameters and constant declared variables are Read-Only
+ return !isExternalCallableParameter() && !m_isConstant;
+}
+
+void VariableDeclaration::checkTypeRequirements()
+{
+ // Variables can be declared without type (with "var"), in which case the first assignment
+ // sets the type.
+ // Note that assignments before the first declaration are legal because of the special scoping
+ // rules inherited from JavaScript.
+ if (m_isConstant)
+ {
+ if (!dynamic_cast<ContractDefinition const*>(getScope()))
+ BOOST_THROW_EXCEPTION(createTypeError("Illegal use of \"constant\" specifier."));
+ if ((m_type && !m_type->isValueType()) || !m_value)
+ BOOST_THROW_EXCEPTION(createTypeError("Unitialized \"constant\" variable."));
+ }
+ if (m_type)
+ {
+ if (m_value)
+ m_value->expectType(*m_type);
+ }
+ else
+ {
+ if (!m_value)
+ // This feature might be extended in the future.
+ BOOST_THROW_EXCEPTION(createTypeError("Assignment necessary for type detection."));
+ m_value->checkTypeRequirements(nullptr);
+
+ TypePointer const& type = m_value->getType();
+ if (
+ type->getCategory() == Type::Category::IntegerConstant &&
+ !dynamic_pointer_cast<IntegerConstantType const>(type)->getIntegerType()
+ )
+ BOOST_THROW_EXCEPTION(m_value->createTypeError("Invalid integer constant " + type->toString() + "."));
+ else if (type->getCategory() == Type::Category::Void)
+ BOOST_THROW_EXCEPTION(createTypeError("Variable cannot have void type."));
+ m_type = type->mobileType();
+ }
+ solAssert(!!m_type, "");
+ if (!m_isStateVariable)
+ {
+ if (m_type->dataStoredIn(DataLocation::Memory) || m_type->dataStoredIn(DataLocation::CallData))
+ if (!m_type->canLiveOutsideStorage())
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Type " + m_type->toString() + " is only valid in storage."
+ ));
+ }
+ else if (getVisibility() >= Visibility::Public && !FunctionType(*this).externalType())
+ BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables."));
+}
+
+bool VariableDeclaration::isCallableParameter() const
+{
+ auto const* callable = dynamic_cast<CallableDeclaration const*>(getScope());
+ if (!callable)
+ return false;
+ for (auto const& variable: callable->getParameters())
+ if (variable.get() == this)
+ return true;
+ if (callable->getReturnParameterList())
+ for (auto const& variable: callable->getReturnParameterList()->getParameters())
+ if (variable.get() == this)
+ return true;
+ return false;
+}
+
+bool VariableDeclaration::isExternalCallableParameter() const
+{
+ auto const* callable = dynamic_cast<CallableDeclaration const*>(getScope());
+ if (!callable || callable->getVisibility() != Declaration::Visibility::External)
+ return false;
+ for (auto const& variable: callable->getParameters())
+ if (variable.get() == this)
+ return true;
+ return false;
+}
+
+TypePointer ModifierDefinition::getType(ContractDefinition const*) const
+{
+ return make_shared<ModifierType>(*this);
+}
+
+void ModifierDefinition::checkTypeRequirements()
+{
+ m_body->checkTypeRequirements();
+}
+
+void ModifierInvocation::checkTypeRequirements(vector<ContractDefinition const*> const& _bases)
+{
+ TypePointers argumentTypes;
+ for (ASTPointer<Expression> const& argument: m_arguments)
+ {
+ argument->checkTypeRequirements(nullptr);
+ argumentTypes.push_back(argument->getType());
+ }
+ m_modifierName->checkTypeRequirements(&argumentTypes);
+
+ auto const* declaration = &m_modifierName->getReferencedDeclaration();
+ vector<ASTPointer<VariableDeclaration>> emptyParameterList;
+ vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
+ if (auto modifier = dynamic_cast<ModifierDefinition const*>(declaration))
+ parameters = &modifier->getParameters();
+ else
+ // check parameters for Base constructors
+ for (ContractDefinition const* base: _bases)
+ if (declaration == base)
+ {
+ if (auto referencedConstructor = base->getConstructor())
+ parameters = &referencedConstructor->getParameters();
+ else
+ parameters = &emptyParameterList;
+ break;
+ }
+ if (!parameters)
+ BOOST_THROW_EXCEPTION(createTypeError("Referenced declaration is neither modifier nor base class."));
+ if (parameters->size() != m_arguments.size())
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Wrong argument count for modifier invocation: " +
+ toString(m_arguments.size()) +
+ " arguments given but expected " +
+ toString(parameters->size()) +
+ "."
+ ));
+ for (size_t i = 0; i < m_arguments.size(); ++i)
+ if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*(*parameters)[i]->getType()))
+ BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError(
+ "Invalid type for argument in modifier invocation. "
+ "Invalid implicit conversion from " +
+ m_arguments[i]->getType()->toString() +
+ " to " +
+ (*parameters)[i]->getType()->toString() +
+ " requested."
+ ));
+}
+
+void EventDefinition::checkTypeRequirements()
+{
+ int numIndexed = 0;
+ for (ASTPointer<VariableDeclaration> const& var: getParameters())
+ {
+ if (var->isIndexed())
+ numIndexed++;
+ if (!var->getType()->canLiveOutsideStorage())
+ BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
+ if (!var->getType()->externalType())
+ BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed as event parameter type."));
+ }
+ if (numIndexed > 3)
+ BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event."));
+}
+
+void Block::checkTypeRequirements()
+{
+ for (shared_ptr<Statement> const& statement: m_statements)
+ statement->checkTypeRequirements();
+}
+
+void IfStatement::checkTypeRequirements()
+{
+ m_condition->expectType(BoolType());
+ m_trueBody->checkTypeRequirements();
+ if (m_falseBody)
+ m_falseBody->checkTypeRequirements();
+}
+
+void WhileStatement::checkTypeRequirements()
+{
+ m_condition->expectType(BoolType());
+ m_body->checkTypeRequirements();
+}
+
+void ForStatement::checkTypeRequirements()
+{
+ if (m_initExpression)
+ m_initExpression->checkTypeRequirements();
+ if (m_condExpression)
+ m_condExpression->expectType(BoolType());
+ if (m_loopExpression)
+ m_loopExpression->checkTypeRequirements();
+ m_body->checkTypeRequirements();
+}
+
+void Return::checkTypeRequirements()
+{
+ if (!m_expression)
+ return;
+ if (!m_returnParameters)
+ BOOST_THROW_EXCEPTION(createTypeError("Return arguments not allowed."));
+ if (m_returnParameters->getParameters().size() != 1)
+ BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement "
+ "than in returns declaration."));
+ // this could later be changed such that the paramaters type is an anonymous struct type,
+ // but for now, we only allow one return parameter
+ m_expression->expectType(*m_returnParameters->getParameters().front()->getType());
+}
+
+void VariableDeclarationStatement::checkTypeRequirements()
+{
+ m_variable->checkTypeRequirements();
+}
+
+void Assignment::checkTypeRequirements(TypePointers const*)
+{
+ m_leftHandSide->checkTypeRequirements(nullptr);
+ m_leftHandSide->requireLValue();
+ if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping)
+ BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to."));
+ m_type = m_leftHandSide->getType();
+ if (m_assigmentOperator == Token::Assign)
+ m_rightHandSide->expectType(*m_type);
+ else
+ {
+ // compound assignment
+ m_rightHandSide->checkTypeRequirements(nullptr);
+ TypePointer resultType = m_type->binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator),
+ m_rightHandSide->getType());
+ if (!resultType || *resultType != *m_type)
+ BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_assigmentOperator)) +
+ " not compatible with types " +
+ m_type->toString() + " and " +
+ m_rightHandSide->getType()->toString()));
+ }
+}
+
+void ExpressionStatement::checkTypeRequirements()
+{
+ m_expression->checkTypeRequirements(nullptr);
+ if (m_expression->getType()->getCategory() == Type::Category::IntegerConstant)
+ if (!dynamic_pointer_cast<IntegerConstantType const>(m_expression->getType())->getIntegerType())
+ BOOST_THROW_EXCEPTION(m_expression->createTypeError("Invalid integer constant."));
+}
+
+void Expression::expectType(Type const& _expectedType)
+{
+ checkTypeRequirements(nullptr);
+ Type const& type = *getType();
+ if (!type.isImplicitlyConvertibleTo(_expectedType))
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Type " +
+ type.toString() +
+ " is not implicitly convertible to expected type " +
+ _expectedType.toString() +
+ "."
+ ));
+}
+
+void Expression::requireLValue()
+{
+ if (!isLValue())
+ BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue."));
+ m_lvalueRequested = true;
+}
+
+void UnaryOperation::checkTypeRequirements(TypePointers const*)
+{
+ // Inc, Dec, Add, Sub, Not, BitNot, Delete
+ m_subExpression->checkTypeRequirements(nullptr);
+ if (m_operator == Token::Value::Inc || m_operator == Token::Value::Dec || m_operator == Token::Value::Delete)
+ m_subExpression->requireLValue();
+ m_type = m_subExpression->getType()->unaryOperatorResult(m_operator);
+ if (!m_type)
+ BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type."));
+}
+
+void BinaryOperation::checkTypeRequirements(TypePointers const*)
+{
+ m_left->checkTypeRequirements(nullptr);
+ m_right->checkTypeRequirements(nullptr);
+ m_commonType = m_left->getType()->binaryOperatorResult(m_operator, m_right->getType());
+ if (!m_commonType)
+ BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) +
+ " not compatible with types " +
+ m_left->getType()->toString() + " and " +
+ m_right->getType()->toString()));
+ m_type = Token::isCompareOp(m_operator) ? make_shared<BoolType>() : m_commonType;
+}
+
+void FunctionCall::checkTypeRequirements(TypePointers const*)
+{
+ bool isPositionalCall = m_names.empty();
+
+ // we need to check arguments' type first as they will be forwarded to
+ // m_expression->checkTypeRequirements
+ TypePointers argumentTypes;
+ for (ASTPointer<Expression> const& argument: m_arguments)
+ {
+ argument->checkTypeRequirements(nullptr);
+ // only store them for positional calls
+ if (isPositionalCall)
+ argumentTypes.push_back(argument->getType());
+ }
+
+ m_expression->checkTypeRequirements(isPositionalCall ? &argumentTypes : nullptr);
+
+ TypePointer const& expressionType = m_expression->getType();
+ FunctionTypePointer functionType;
+ if (isTypeConversion())
+ {
+ TypeType const& type = dynamic_cast<TypeType const&>(*expressionType);
+ if (m_arguments.size() != 1)
+ BOOST_THROW_EXCEPTION(createTypeError("Exactly one argument expected for explicit type conversion."));
+ if (!isPositionalCall)
+ BOOST_THROW_EXCEPTION(createTypeError("Type conversion cannot allow named arguments."));
+ m_type = type.getActualType();
+ auto argType = m_arguments.front()->getType();
+ if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get()))
+ // do not change the data location when converting
+ // (data location cannot yet be specified for type conversions)
+ m_type = ReferenceType::copyForLocationIfReference(argRefType->location(), m_type);
+ if (!argType->isExplicitlyConvertibleTo(*m_type))
+ BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed."));
+
+ return;
+ }
+
+ /// For error message: Struct members that were removed during conversion to memory.
+ set<string> membersRemovedForStructConstructor;
+ if (isStructConstructorCall())
+ {
+ TypeType const& type = dynamic_cast<TypeType const&>(*expressionType);
+ auto const& structType = dynamic_cast<StructType const&>(*type.getActualType());
+ functionType = structType.constructorType();
+ membersRemovedForStructConstructor = structType.membersMissingInMemory();
+ }
+ else
+ functionType = dynamic_pointer_cast<FunctionType const>(expressionType);
+
+ if (!functionType)
+ BOOST_THROW_EXCEPTION(createTypeError("Type is not callable."));
+
+ //@todo would be nice to create a struct type from the arguments
+ // and then ask if that is implicitly convertible to the struct represented by the
+ // function parameters
+ TypePointers const& parameterTypes = functionType->getParameterTypes();
+ if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size())
+ {
+ string msg =
+ "Wrong argument count for function call: " +
+ toString(m_arguments.size()) +
+ " arguments given but expected " +
+ toString(parameterTypes.size()) +
+ ".";
+ // Extend error message in case we try to construct a struct with mapping member.
+ if (isStructConstructorCall() && !membersRemovedForStructConstructor.empty())
+ {
+ msg += " Members that have to be skipped in memory:";
+ for (auto const& member: membersRemovedForStructConstructor)
+ msg += " " + member;
+ }
+ BOOST_THROW_EXCEPTION(createTypeError(msg));
+ }
+
+ if (isPositionalCall)
+ {
+ // call by positional arguments
+ for (size_t i = 0; i < m_arguments.size(); ++i)
+ if (
+ !functionType->takesArbitraryParameters() &&
+ !m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])
+ )
+ BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError(
+ "Invalid type for argument in function call. "
+ "Invalid implicit conversion from " +
+ m_arguments[i]->getType()->toString() +
+ " to " +
+ parameterTypes[i]->toString() +
+ " requested."
+ ));
+ }
+ else
+ {
+ // call by named arguments
+ if (functionType->takesArbitraryParameters())
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Named arguments cannnot be used for functions that take arbitrary parameters."
+ ));
+ auto const& parameterNames = functionType->getParameterNames();
+ if (parameterNames.size() != m_names.size())
+ BOOST_THROW_EXCEPTION(createTypeError("Some argument names are missing."));
+
+ // check duplicate names
+ for (size_t i = 0; i < m_names.size(); i++)
+ for (size_t j = i + 1; j < m_names.size(); j++)
+ if (*m_names[i] == *m_names[j])
+ BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError("Duplicate named argument."));
+
+ for (size_t i = 0; i < m_names.size(); i++) {
+ bool found = false;
+ for (size_t j = 0; j < parameterNames.size(); j++) {
+ if (parameterNames[j] == *m_names[i]) {
+ // check type convertible
+ if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[j]))
+ BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError(
+ "Invalid type for argument in function call. "
+ "Invalid implicit conversion from " +
+ m_arguments[i]->getType()->toString() +
+ " to " +
+ parameterTypes[i]->toString() +
+ " requested."
+ ));
+
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ BOOST_THROW_EXCEPTION(createTypeError("Named argument does not match function declaration."));
+ }
+ }
+
+ // @todo actually the return type should be an anonymous struct,
+ // but we change it to the type of the first return value until we have anonymous
+ // structs and tuples
+ if (functionType->getReturnParameterTypes().empty())
+ m_type = make_shared<VoidType>();
+ else
+ m_type = functionType->getReturnParameterTypes().front();
+}
+
+bool FunctionCall::isTypeConversion() const
+{
+ return m_expression->getType()->getCategory() == Type::Category::TypeType && !isStructConstructorCall();
+}
+
+bool FunctionCall::isStructConstructorCall() const
+{
+ if (auto const* type = dynamic_cast<TypeType const*>(m_expression->getType().get()))
+ return type->getActualType()->getCategory() == Type::Category::Struct;
+ else
+ return false;
+}
+
+void NewExpression::checkTypeRequirements(TypePointers const*)
+{
+ m_contractName->checkTypeRequirements(nullptr);
+ m_contract = dynamic_cast<ContractDefinition const*>(&m_contractName->getReferencedDeclaration());
+ if (!m_contract)
+ BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
+ if (!m_contract->isFullyImplemented())
+ BOOST_THROW_EXCEPTION(createTypeError("Trying to create an instance of an abstract contract."));
+ shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract);
+ TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes();
+ m_type = make_shared<FunctionType>(
+ parameterTypes,
+ TypePointers{contractType},
+ strings(),
+ strings(),
+ FunctionType::Location::Creation);
+}
+
+void MemberAccess::checkTypeRequirements(TypePointers const* _argumentTypes)
+{
+ m_expression->checkTypeRequirements(nullptr);
+ Type const& type = *m_expression->getType();
+
+ MemberList::MemberMap possibleMembers = type.getMembers().membersByName(*m_memberName);
+ if (possibleMembers.size() > 1 && _argumentTypes)
+ {
+ // do override resolution
+ for (auto it = possibleMembers.begin(); it != possibleMembers.end();)
+ if (
+ it->type->getCategory() == Type::Category::Function &&
+ !dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*_argumentTypes)
+ )
+ it = possibleMembers.erase(it);
+ else
+ ++it;
+ }
+ if (possibleMembers.size() == 0)
+ {
+ auto storageType = ReferenceType::copyForLocationIfReference(
+ DataLocation::Storage,
+ m_expression->getType()
+ );
+ if (!storageType->getMembers().membersByName(*m_memberName).empty())
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Member \"" + *m_memberName + "\" is not available in " +
+ type.toString() +
+ " outside of storage."
+ ));
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Member \"" + *m_memberName + "\" not found or not visible "
+ "after argument-dependent lookup in " + type.toString()
+ ));
+ }
+ else if (possibleMembers.size() > 1)
+ BOOST_THROW_EXCEPTION(createTypeError(
+ "Member \"" + *m_memberName + "\" not unique "
+ "after argument-dependent lookup in " + type.toString()
+ ));
+
+ m_referencedDeclaration = possibleMembers.front().declaration;
+ m_type = possibleMembers.front().type;
+ if (type.getCategory() == Type::Category::Struct)
+ m_isLValue = true;
+ else if (type.getCategory() == Type::Category::Array)
+ {
+ auto const& arrayType(dynamic_cast<ArrayType const&>(type));
+ m_isLValue = (
+ *m_memberName == "length" &&
+ arrayType.location() == DataLocation::Storage &&
+ arrayType.isDynamicallySized()
+ );
+ }
+ else
+ m_isLValue = false;
+}
+
+void IndexAccess::checkTypeRequirements(TypePointers const*)
+{
+ m_base->checkTypeRequirements(nullptr);
+ switch (m_base->getType()->getCategory())
+ {
+ case Type::Category::Array:
+ {
+ ArrayType const& type = dynamic_cast<ArrayType const&>(*m_base->getType());
+ if (!m_index)
+ BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted."));
+ if (type.isString())
+ BOOST_THROW_EXCEPTION(createTypeError("Index access for string is not possible."));
+ m_index->expectType(IntegerType(256));
+ if (type.isByteArray())
+ m_type = make_shared<FixedBytesType>(1);
+ else
+ m_type = type.getBaseType();
+ m_isLValue = type.location() != DataLocation::CallData;
+ break;
+ }
+ case Type::Category::Mapping:
+ {
+ MappingType const& type = dynamic_cast<MappingType const&>(*m_base->getType());
+ if (!m_index)
+ BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted."));
+ m_index->expectType(*type.getKeyType());
+ m_type = type.getValueType();
+ m_isLValue = true;
+ break;
+ }
+ case Type::Category::TypeType:
+ {
+ TypeType const& type = dynamic_cast<TypeType const&>(*m_base->getType());
+ if (!m_index)
+ m_type = make_shared<TypeType>(make_shared<ArrayType>(DataLocation::Memory, type.getActualType()));
+ else
+ {
+ m_index->checkTypeRequirements(nullptr);
+ auto length = dynamic_cast<IntegerConstantType const*>(m_index->getType().get());
+ if (!length)
+ BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected."));
+ m_type = make_shared<TypeType>(make_shared<ArrayType>(
+ DataLocation::Memory, type.getActualType(),
+ length->literalValue(nullptr)
+ ));
+ }
+ break;
+ }
+ default:
+ BOOST_THROW_EXCEPTION(m_base->createTypeError(
+ "Indexed expression has to be a type, mapping or array (is " + m_base->getType()->toString() + ")"));
+ }
+}
+
+void Identifier::checkTypeRequirements(TypePointers const* _argumentTypes)
+{
+ if (!m_referencedDeclaration)
+ {
+ if (!_argumentTypes)
+ BOOST_THROW_EXCEPTION(createTypeError("Unable to determine overloaded type."));
+ overloadResolution(*_argumentTypes);
+ }
+ solAssert(!!m_referencedDeclaration, "Referenced declaration is null after overload resolution.");
+ m_isLValue = m_referencedDeclaration->isLValue();
+ m_type = m_referencedDeclaration->getType(m_currentContract);
+ if (!m_type)
+ BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined."));
+}
+
+Declaration const& Identifier::getReferencedDeclaration() const
+{
+ solAssert(!!m_referencedDeclaration, "Identifier not resolved.");
+ return *m_referencedDeclaration;
+}
+
+void Identifier::overloadResolution(TypePointers const& _argumentTypes)
+{
+ solAssert(!m_referencedDeclaration, "Referenced declaration should be null before overload resolution.");
+ solAssert(!m_overloadedDeclarations.empty(), "No candidates for overload resolution found.");
+
+ vector<Declaration const*> possibles;
+ if (m_overloadedDeclarations.size() == 1)
+ m_referencedDeclaration = *m_overloadedDeclarations.begin();
+
+ for (Declaration const* declaration: m_overloadedDeclarations)
+ {
+ TypePointer const& function = declaration->getType();
+ auto const* functionType = dynamic_cast<FunctionType const*>(function.get());
+ if (functionType && functionType->canTakeArguments(_argumentTypes))
+ possibles.push_back(declaration);
+ }
+ if (possibles.size() == 1)
+ m_referencedDeclaration = possibles.front();
+ else if (possibles.empty())
+ BOOST_THROW_EXCEPTION(createTypeError("No matching declaration found after argument-dependent lookup."));
+ else
+ BOOST_THROW_EXCEPTION(createTypeError("No unique declaration found after argument-dependent lookup."));
+}
+
+void ElementaryTypeNameExpression::checkTypeRequirements(TypePointers const*)
+{
+ m_type = make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken));
+}
+
+void Literal::checkTypeRequirements(TypePointers const*)
+{
+ m_type = Type::forLiteral(*this);
+ if (!m_type)
+ BOOST_THROW_EXCEPTION(createTypeError("Invalid literal value."));
+}
+
+}
+}
diff --git a/libsolidity/AST.h b/libsolidity/AST.h
new file mode 100644
index 00000000..fb83d4e1
--- /dev/null
+++ b/libsolidity/AST.h
@@ -0,0 +1,1355 @@
+/*
+ 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 abstract syntax tree.
+ */
+
+#pragma once
+
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <boost/noncopyable.hpp>
+#include <libevmasm/SourceLocation.h>
+#include <libsolidity/Utils.h>
+#include <libsolidity/ASTForward.h>
+#include <libsolidity/Token.h>
+#include <libsolidity/Types.h>
+#include <libsolidity/Exceptions.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ASTVisitor;
+class ASTConstVisitor;
+
+
+/**
+ * The root (abstract) class of the AST inheritance tree.
+ * It is possible to traverse all direct and indirect children of an AST node by calling
+ * accept, providing an ASTVisitor.
+ */
+class ASTNode: private boost::noncopyable
+{
+public:
+ explicit ASTNode(SourceLocation const& _location): m_location(_location) {}
+
+ virtual ~ASTNode() {}
+
+ virtual void accept(ASTVisitor& _visitor) = 0;
+ virtual void accept(ASTConstVisitor& _visitor) const = 0;
+ template <class T>
+ static void listAccept(std::vector<ASTPointer<T>>& _list, ASTVisitor& _visitor)
+ {
+ for (ASTPointer<T>& element: _list)
+ element->accept(_visitor);
+ }
+ template <class T>
+ static void listAccept(std::vector<ASTPointer<T>> const& _list, ASTConstVisitor& _visitor)
+ {
+ for (ASTPointer<T> const& element: _list)
+ element->accept(_visitor);
+ }
+
+ /// Returns the source code location of this node.
+ SourceLocation const& getLocation() const { return m_location; }
+
+ /// Creates a @ref TypeError exception and decorates it with the location of the node and
+ /// the given description
+ TypeError createTypeError(std::string const& _description) const;
+
+ ///@{
+ ///@name equality operators
+ /// Equality relies on the fact that nodes cannot be copied.
+ bool operator==(ASTNode const& _other) const { return this == &_other; }
+ bool operator!=(ASTNode const& _other) const { return !operator==(_other); }
+ ///@}
+
+private:
+ SourceLocation m_location;
+};
+
+/**
+ * Source unit containing import directives and contract definitions.
+ */
+class SourceUnit: public ASTNode
+{
+public:
+ SourceUnit(SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> const& _nodes):
+ ASTNode(_location), m_nodes(_nodes) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ std::vector<ASTPointer<ASTNode>> getNodes() const { return m_nodes; }
+
+private:
+ std::vector<ASTPointer<ASTNode>> m_nodes;
+};
+
+/**
+ * Import directive for referencing other files / source objects.
+ * Example: import "abc.sol"
+ * Source objects are identified by a string which can be a file name but does not have to be.
+ */
+class ImportDirective: public ASTNode
+{
+public:
+ ImportDirective(SourceLocation const& _location, ASTPointer<ASTString> const& _identifier):
+ ASTNode(_location), m_identifier(_identifier) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ ASTString const& getIdentifier() const { return *m_identifier; }
+
+private:
+ ASTPointer<ASTString> m_identifier;
+};
+
+/**
+ * Abstract AST class for a declaration (contract, function, struct, variable).
+ */
+class Declaration: public ASTNode
+{
+public:
+ /// Visibility ordered from restricted to unrestricted.
+ enum class Visibility { Default, Private, Internal, Public, External };
+
+ Declaration(SourceLocation const& _location, ASTPointer<ASTString> const& _name,
+ Visibility _visibility = Visibility::Default):
+ ASTNode(_location), m_name(_name), m_visibility(_visibility), m_scope(nullptr) {}
+
+ /// @returns the declared name.
+ ASTString const& getName() const { return *m_name; }
+ Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; }
+ bool isPublic() const { return getVisibility() >= Visibility::Public; }
+ virtual bool isVisibleInContract() const { return getVisibility() != Visibility::External; }
+ bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Internal; }
+
+ /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
+ /// Available only after name and type resolution step.
+ Declaration const* getScope() const { return m_scope; }
+ void setScope(Declaration const* _scope) { m_scope = _scope; }
+
+ /// @returns the type of expressions referencing this declaration.
+ /// The current contract has to be given since this context can change the type, especially of
+ /// contract types.
+ virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0;
+ virtual bool isLValue() const { return false; }
+ virtual bool isPartOfExternalInterface() const { return false; }
+
+protected:
+ virtual Visibility getDefaultVisibility() const { return Visibility::Public; }
+
+private:
+ ASTPointer<ASTString> m_name;
+ Visibility m_visibility;
+ Declaration const* m_scope;
+};
+
+/**
+ * Abstract class that is added to each AST node that can store local variables.
+ */
+class VariableScope
+{
+public:
+ void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); }
+ std::vector<VariableDeclaration const*> const& getLocalVariables() const { return m_localVariables; }
+
+private:
+ std::vector<VariableDeclaration const*> m_localVariables;
+};
+
+/**
+ * Abstract class that is added to each AST node that can receive documentation.
+ */
+class Documented
+{
+public:
+ explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {}
+
+ /// @return A shared pointer of an ASTString.
+ /// Can contain a nullptr in which case indicates absence of documentation
+ ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; }
+
+protected:
+ ASTPointer<ASTString> m_documentation;
+};
+
+/**
+ * Abstract class that is added to AST nodes that can be marked as not being fully implemented
+ */
+class ImplementationOptional
+{
+public:
+ explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {}
+
+ /// @return whether this node is fully implemented or not
+ bool isFullyImplemented() const { return m_implemented; }
+ void setFullyImplemented(bool _implemented) { m_implemented = _implemented; }
+
+protected:
+ bool m_implemented;
+};
+
+/// @}
+
+/**
+ * Definition of a contract. This is the only AST nodes where child nodes are not visited in
+ * document order. It first visits all struct declarations, then all variable declarations and
+ * finally all function declarations.
+ */
+class ContractDefinition: public Declaration, public Documented, public ImplementationOptional
+{
+public:
+ ContractDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<ASTString> const& _documentation,
+ std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
+ std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
+ std::vector<ASTPointer<EnumDefinition>> const& _definedEnums,
+ std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
+ std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions,
+ std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers,
+ std::vector<ASTPointer<EventDefinition>> const& _events
+ ):
+ Declaration(_location, _name),
+ Documented(_documentation),
+ ImplementationOptional(true),
+ m_baseContracts(_baseContracts),
+ m_definedStructs(_definedStructs),
+ m_definedEnums(_definedEnums),
+ m_stateVariables(_stateVariables),
+ m_definedFunctions(_definedFunctions),
+ m_functionModifiers(_functionModifiers),
+ m_events(_events)
+ {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ std::vector<ASTPointer<InheritanceSpecifier>> const& getBaseContracts() const { return m_baseContracts; }
+ std::vector<ASTPointer<StructDefinition>> const& getDefinedStructs() const { return m_definedStructs; }
+ std::vector<ASTPointer<EnumDefinition>> const& getDefinedEnums() const { return m_definedEnums; }
+ std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; }
+ std::vector<ASTPointer<ModifierDefinition>> const& getFunctionModifiers() const { return m_functionModifiers; }
+ std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; }
+ std::vector<ASTPointer<EventDefinition>> const& getEvents() const { return m_events; }
+ std::vector<ASTPointer<EventDefinition>> const& getInterfaceEvents() const;
+
+ virtual TypePointer getType(ContractDefinition const* m_currentContract) const override;
+
+ /// Checks that there are no illegal overrides, that the constructor does not have a "returns"
+ /// and calls checkTypeRequirements on all its functions.
+ void checkTypeRequirements();
+
+ /// @returns a map of canonical function signatures to FunctionDefinitions
+ /// as intended for use by the ABI.
+ std::map<FixedHash<4>, FunctionTypePointer> getInterfaceFunctions() const;
+
+ /// @returns a list of the inheritable members of this contract
+ std::vector<Declaration const*> const& getInheritableMembers() const;
+
+ /// List of all (direct and indirect) base contracts in order from derived to base, including
+ /// the contract itself. Available after name resolution
+ std::vector<ContractDefinition const*> const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; }
+ void setLinearizedBaseContracts(std::vector<ContractDefinition const*> const& _bases) { m_linearizedBaseContracts = _bases; }
+
+ /// Returns the constructor or nullptr if no constructor was specified.
+ FunctionDefinition const* getConstructor() const;
+ /// Returns the fallback function or nullptr if no fallback function was specified.
+ FunctionDefinition const* getFallbackFunction() const;
+
+ std::string const& userDocumentation() const;
+ void setUserDocumentation(std::string const& _userDocumentation);
+
+ std::string const& devDocumentation() const;
+ void setDevDocumentation(std::string const& _devDocumentation);
+
+private:
+ /// Checks that two functions defined in this contract with the same name have different
+ /// arguments and that there is at most one constructor.
+ void checkDuplicateFunctions() const;
+ void checkIllegalOverrides() const;
+ void checkAbstractFunctions();
+ void checkAbstractConstructors();
+ /// Checks that different functions with external visibility end up having different
+ /// external argument types (i.e. different signature).
+ void checkExternalTypeClashes() const;
+
+ std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const;
+
+ std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
+ std::vector<ASTPointer<StructDefinition>> m_definedStructs;
+ std::vector<ASTPointer<EnumDefinition>> m_definedEnums;
+ std::vector<ASTPointer<VariableDeclaration>> m_stateVariables;
+ std::vector<ASTPointer<FunctionDefinition>> m_definedFunctions;
+ std::vector<ASTPointer<ModifierDefinition>> m_functionModifiers;
+ std::vector<ASTPointer<EventDefinition>> m_events;
+
+ // parsed Natspec documentation of the contract.
+ std::string m_userDocumentation;
+ std::string m_devDocumentation;
+
+ std::vector<ContractDefinition const*> m_linearizedBaseContracts;
+ mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
+ mutable std::unique_ptr<std::vector<ASTPointer<EventDefinition>>> m_interfaceEvents;
+ mutable std::unique_ptr<std::vector<Declaration const*>> m_inheritableMembers;
+};
+
+class InheritanceSpecifier: public ASTNode
+{
+public:
+ InheritanceSpecifier(SourceLocation const& _location, ASTPointer<Identifier> const& _baseName,
+ std::vector<ASTPointer<Expression>> _arguments):
+ ASTNode(_location), m_baseName(_baseName), m_arguments(_arguments) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ ASTPointer<Identifier> const& getName() const { return m_baseName; }
+ std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; }
+
+ void checkTypeRequirements();
+
+private:
+ ASTPointer<Identifier> m_baseName;
+ std::vector<ASTPointer<Expression>> m_arguments;
+};
+
+class StructDefinition: public Declaration
+{
+public:
+ StructDefinition(SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ std::vector<ASTPointer<VariableDeclaration>> const& _members):
+ Declaration(_location, _name), m_members(_members) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ std::vector<ASTPointer<VariableDeclaration>> const& getMembers() const { return m_members; }
+
+ virtual TypePointer getType(ContractDefinition const*) const override;
+
+ /// Checks that the members do not include any recursive structs and have valid types
+ /// (e.g. no functions).
+ void checkValidityOfMembers() const;
+
+private:
+ void checkMemberTypes() const;
+ void checkRecursion() const;
+
+ std::vector<ASTPointer<VariableDeclaration>> m_members;
+};
+
+class EnumDefinition: public Declaration
+{
+public:
+ EnumDefinition(SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ std::vector<ASTPointer<EnumValue>> const& _members):
+ Declaration(_location, _name), m_members(_members) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ std::vector<ASTPointer<EnumValue>> const& getMembers() const { return m_members; }
+
+ virtual TypePointer getType(ContractDefinition const*) const override;
+
+private:
+ std::vector<ASTPointer<EnumValue>> m_members;
+};
+
+/**
+ * Declaration of an Enum Value
+ */
+class EnumValue: public Declaration
+{
+ public:
+ EnumValue(SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name):
+ Declaration(_location, _name) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual TypePointer getType(ContractDefinition const* = nullptr) const override;
+};
+
+/**
+ * Parameter list, used as function parameter list and return list.
+ * None of the parameters is allowed to contain mappings (not even recursively
+ * inside structs).
+ */
+class ParameterList: public ASTNode
+{
+public:
+ ParameterList(SourceLocation const& _location,
+ std::vector<ASTPointer<VariableDeclaration>> const& _parameters):
+ ASTNode(_location), m_parameters(_parameters) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters; }
+
+private:
+ std::vector<ASTPointer<VariableDeclaration>> m_parameters;
+};
+
+/**
+ * Base class for all nodes that define function-like objects, i.e. FunctionDefinition,
+ * EventDefinition and ModifierDefinition.
+ */
+class CallableDeclaration: public Declaration, public VariableScope
+{
+public:
+ CallableDeclaration(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ Declaration::Visibility _visibility,
+ ASTPointer<ParameterList> const& _parameters,
+ ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
+ ):
+ Declaration(_location, _name, _visibility),
+ m_parameters(_parameters),
+ m_returnParameters(_returnParameters)
+ {
+ }
+
+ std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
+ ParameterList const& getParameterList() const { return *m_parameters; }
+ ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
+
+protected:
+ ASTPointer<ParameterList> m_parameters;
+ ASTPointer<ParameterList> m_returnParameters;
+};
+
+class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional
+{
+public:
+ FunctionDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ Declaration::Visibility _visibility,
+ bool _isConstructor,
+ ASTPointer<ASTString> const& _documentation,
+ ASTPointer<ParameterList> const& _parameters,
+ bool _isDeclaredConst,
+ std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
+ ASTPointer<ParameterList> const& _returnParameters,
+ ASTPointer<Block> const& _body
+ ):
+ CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters),
+ Documented(_documentation),
+ ImplementationOptional(_body != nullptr),
+ m_isConstructor(_isConstructor),
+ m_isDeclaredConst(_isDeclaredConst),
+ m_functionModifiers(_modifiers),
+ m_body(_body)
+ {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ bool isConstructor() const { return m_isConstructor; }
+ bool isDeclaredConst() const { return m_isDeclaredConst; }
+ std::vector<ASTPointer<ModifierInvocation>> const& getModifiers() const { return m_functionModifiers; }
+ std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); }
+ Block const& getBody() const { return *m_body; }
+
+ virtual bool isVisibleInContract() const override
+ {
+ return Declaration::isVisibleInContract() && !isConstructor() && !getName().empty();
+ }
+ virtual TypePointer getType(ContractDefinition const*) const override;
+ virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstructor && !getName().empty(); }
+
+ /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
+ void checkTypeRequirements();
+
+ /// @returns the external signature of the function
+ /// That consists of the name of the function followed by the types of the
+ /// arguments separated by commas all enclosed in parentheses without any spaces.
+ std::string externalSignature() const;
+
+private:
+ bool m_isConstructor;
+ bool m_isDeclaredConst;
+ std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
+ ASTPointer<Block> m_body;
+};
+
+/**
+ * Declaration of a variable. This can be used in various places, e.g. in function parameter
+ * lists, struct definitions and even function bodys.
+ */
+class VariableDeclaration: public Declaration
+{
+public:
+ enum Location { Default, Storage, Memory };
+
+ VariableDeclaration(
+ SourceLocation const& _sourceLocation,
+ ASTPointer<TypeName> const& _type,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<Expression> _value,
+ Visibility _visibility,
+ bool _isStateVar = false,
+ bool _isIndexed = false,
+ bool _isConstant = false,
+ Location _referenceLocation = Location::Default
+ ):
+ Declaration(_sourceLocation, _name, _visibility),
+ m_typeName(_type),
+ m_value(_value),
+ m_isStateVariable(_isStateVar),
+ m_isIndexed(_isIndexed),
+ m_isConstant(_isConstant),
+ m_location(_referenceLocation) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ TypeName* getTypeName() { return m_typeName.get(); }
+ ASTPointer<Expression> const& getValue() const { return m_value; }
+
+ /// Returns the declared or inferred type. Can be an empty pointer if no type was explicitly
+ /// declared and there is no assignment to the variable that fixes the type.
+ TypePointer getType(ContractDefinition const* = nullptr) const override { return m_type; }
+ void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; }
+
+ virtual bool isLValue() const override;
+ virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstant; }
+
+ void checkTypeRequirements();
+ bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); }
+ /// @returns true if this variable is a parameter or return parameter of a function.
+ bool isCallableParameter() const;
+ /// @returns true if this variable is a parameter (not return parameter) of an external function.
+ bool isExternalCallableParameter() const;
+ bool isStateVariable() const { return m_isStateVariable; }
+ bool isIndexed() const { return m_isIndexed; }
+ bool isConstant() const { return m_isConstant; }
+ Location referenceLocation() const { return m_location; }
+
+protected:
+ Visibility getDefaultVisibility() const override { return Visibility::Internal; }
+
+private:
+ ASTPointer<TypeName> m_typeName; ///< can be empty ("var")
+ ASTPointer<Expression> m_value; ///< the assigned value, can be missing
+ bool m_isStateVariable; ///< Whether or not this is a contract state variable
+ bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
+ bool m_isConstant; ///< Whether the variable is a compile-time constant.
+ Location m_location; ///< Location of the variable if it is of reference type.
+
+ std::shared_ptr<Type const> m_type; ///< derived type, initially empty
+};
+
+/**
+ * Definition of a function modifier.
+ */
+class ModifierDefinition: public CallableDeclaration, public Documented
+{
+public:
+ ModifierDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<ASTString> const& _documentation,
+ ASTPointer<ParameterList> const& _parameters,
+ ASTPointer<Block> const& _body
+ ):
+ CallableDeclaration(_location, _name, Visibility::Default, _parameters),
+ Documented(_documentation),
+ m_body(_body)
+ {
+ }
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ Block const& getBody() const { return *m_body; }
+
+ virtual TypePointer getType(ContractDefinition const* = nullptr) const override;
+
+ void checkTypeRequirements();
+
+private:
+ ASTPointer<Block> m_body;
+};
+
+/**
+ * Invocation/usage of a modifier in a function header or a base constructor call.
+ */
+class ModifierInvocation: public ASTNode
+{
+public:
+ ModifierInvocation(SourceLocation const& _location, ASTPointer<Identifier> const& _name,
+ std::vector<ASTPointer<Expression>> _arguments):
+ ASTNode(_location), m_modifierName(_name), m_arguments(_arguments) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ ASTPointer<Identifier> const& getName() const { return m_modifierName; }
+ std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; }
+
+ /// @param _bases is the list of base contracts for base constructor calls. For modifiers an empty vector should be passed.
+ void checkTypeRequirements(std::vector<ContractDefinition const*> const& _bases);
+
+private:
+ ASTPointer<Identifier> m_modifierName;
+ std::vector<ASTPointer<Expression>> m_arguments;
+};
+
+/**
+ * Definition of a (loggable) event.
+ */
+class EventDefinition: public CallableDeclaration, public Documented
+{
+public:
+ EventDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<ASTString> const& _documentation,
+ ASTPointer<ParameterList> const& _parameters,
+ bool _anonymous = false
+ ):
+ CallableDeclaration(_location, _name, Visibility::Default, _parameters),
+ Documented(_documentation),
+ m_anonymous(_anonymous)
+ {
+ }
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ bool isAnonymous() const { return m_anonymous; }
+
+ virtual TypePointer getType(ContractDefinition const* = nullptr) const override
+ {
+ return std::make_shared<FunctionType>(*this);
+ }
+
+ void checkTypeRequirements();
+
+private:
+ bool m_anonymous = false;
+};
+
+/**
+ * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global
+ * functions when such an identifier is encountered. Will never have a valid location in the source code.
+ */
+class MagicVariableDeclaration: public Declaration
+{
+public:
+ MagicVariableDeclaration(ASTString const& _name, std::shared_ptr<Type const> const& _type):
+ Declaration(SourceLocation(), std::make_shared<ASTString>(_name)), m_type(_type) {}
+ virtual void accept(ASTVisitor&) override { BOOST_THROW_EXCEPTION(InternalCompilerError()
+ << errinfo_comment("MagicVariableDeclaration used inside real AST.")); }
+ virtual void accept(ASTConstVisitor&) const override { BOOST_THROW_EXCEPTION(InternalCompilerError()
+ << errinfo_comment("MagicVariableDeclaration used inside real AST.")); }
+
+ virtual TypePointer getType(ContractDefinition const* = nullptr) const override { return m_type; }
+
+private:
+ std::shared_ptr<Type const> m_type;
+};
+
+/// Types
+/// @{
+
+/**
+ * Abstract base class of a type name, can be any built-in or user-defined type.
+ */
+class TypeName: public ASTNode
+{
+public:
+ explicit TypeName(SourceLocation const& _location): ASTNode(_location) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ /// Retrieve the element of the type hierarchy this node refers to. Can return an empty shared
+ /// pointer until the types have been resolved using the @ref NameAndTypeResolver.
+ /// If it returns an empty shared pointer after that, this indicates that the type was not found.
+ virtual std::shared_ptr<Type const> toType() = 0;
+};
+
+/**
+ * Any pre-defined type name represented by a single keyword, i.e. it excludes mappings,
+ * contracts, functions, etc.
+ */
+class ElementaryTypeName: public TypeName
+{
+public:
+ explicit ElementaryTypeName(SourceLocation const& _location, Token::Value _type):
+ TypeName(_location), m_type(_type)
+ {
+ solAssert(Token::isElementaryTypeName(_type), "");
+ }
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual std::shared_ptr<Type const> toType() override { return Type::fromElementaryTypeName(m_type); }
+
+ Token::Value getTypeName() const { return m_type; }
+
+private:
+ Token::Value m_type;
+};
+
+/**
+ * Name referring to a user-defined type (i.e. a struct, contract, etc.).
+ */
+class UserDefinedTypeName: public TypeName
+{
+public:
+ UserDefinedTypeName(SourceLocation const& _location, ASTPointer<ASTString> const& _name):
+ TypeName(_location), m_name(_name), m_referencedDeclaration(nullptr) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual std::shared_ptr<Type const> toType() override { return Type::fromUserDefinedTypeName(*this); }
+
+ ASTString const& getName() const { return *m_name; }
+ void setReferencedDeclaration(Declaration const& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; }
+ Declaration const* getReferencedDeclaration() const { return m_referencedDeclaration; }
+
+private:
+ ASTPointer<ASTString> m_name;
+
+ Declaration const* m_referencedDeclaration;
+};
+
+/**
+ * A mapping type. Its source form is "mapping('keyType' => 'valueType')"
+ */
+class Mapping: public TypeName
+{
+public:
+ Mapping(SourceLocation const& _location, ASTPointer<ElementaryTypeName> const& _keyType,
+ ASTPointer<TypeName> const& _valueType):
+ TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual TypePointer toType() override { return Type::fromMapping(*m_keyType, *m_valueType); }
+
+ ElementaryTypeName const& getKeyType() const { return *m_keyType; }
+ TypeName const& getValueType() const { return *m_valueType; }
+
+private:
+ ASTPointer<ElementaryTypeName> m_keyType;
+ ASTPointer<TypeName> m_valueType;
+};
+
+/**
+ * An array type, can be "typename[]" or "typename[<expression>]".
+ */
+class ArrayTypeName: public TypeName
+{
+public:
+ ArrayTypeName(SourceLocation const& _location, ASTPointer<TypeName> const& _baseType,
+ ASTPointer<Expression> const& _length):
+ TypeName(_location), m_baseType(_baseType), m_length(_length) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual std::shared_ptr<Type const> toType() override { return Type::fromArrayTypeName(*m_baseType, m_length.get()); }
+
+ TypeName const& getBaseType() const { return *m_baseType; }
+ Expression const* getLength() const { return m_length.get(); }
+
+private:
+ ASTPointer<TypeName> m_baseType;
+ ASTPointer<Expression> m_length; ///< Length of the array, might be empty.
+};
+
+/// @}
+
+/// Statements
+/// @{
+
+
+/**
+ * Abstract base class for statements.
+ */
+class Statement: public ASTNode
+{
+public:
+ explicit Statement(SourceLocation const& _location): ASTNode(_location) {}
+
+ /// Check all type requirements, throws exception if some requirement is not met.
+ /// This includes checking that operators are applicable to their arguments but also that
+ /// the number of function call arguments matches the number of formal parameters and so forth.
+ virtual void checkTypeRequirements() = 0;
+};
+
+/**
+ * Brace-enclosed block containing zero or more statements.
+ */
+class Block: public Statement
+{
+public:
+ Block(SourceLocation const& _location, std::vector<ASTPointer<Statement>> const& _statements):
+ Statement(_location), m_statements(_statements) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ virtual void checkTypeRequirements() override;
+
+private:
+ std::vector<ASTPointer<Statement>> m_statements;
+};
+
+/**
+ * Special placeholder statement denoted by "_" used in function modifiers. This is replaced by
+ * the original function when the modifier is applied.
+ */
+class PlaceholderStatement: public Statement
+{
+public:
+ PlaceholderStatement(SourceLocation const& _location): Statement(_location) {}
+
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+
+ virtual void checkTypeRequirements() override { }
+};
+
+/**
+ * If-statement with an optional "else" part. Note that "else if" is modeled by having a new
+ * if-statement as the false (else) body.
+ */
+class IfStatement: public Statement
+{
+public:
+ IfStatement(SourceLocation const& _location, ASTPointer<Expression> const& _condition,
+ ASTPointer<Statement> const& _trueBody, ASTPointer<Statement> const& _falseBody):
+ Statement(_location),
+ m_condition(_condition), m_trueBody(_trueBody), m_falseBody(_falseBody) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ Expression const& getCondition() const { return *m_condition; }
+ Statement const& getTrueStatement() const { return *m_trueBody; }
+ /// @returns the "else" part of the if statement or nullptr if there is no "else" part.
+ Statement const* getFalseStatement() const { return m_falseBody.get(); }
+
+private:
+ ASTPointer<Expression> m_condition;
+ ASTPointer<Statement> m_trueBody;
+ ASTPointer<Statement> m_falseBody; ///< "else" part, optional
+};
+
+/**
+ * Statement in which a break statement is legal (abstract class).
+ */
+class BreakableStatement: public Statement
+{
+public:
+ BreakableStatement(SourceLocation const& _location): Statement(_location) {}
+};
+
+class WhileStatement: public BreakableStatement
+{
+public:
+ WhileStatement(SourceLocation const& _location, ASTPointer<Expression> const& _condition,
+ ASTPointer<Statement> const& _body):
+ BreakableStatement(_location), m_condition(_condition), m_body(_body) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ Expression const& getCondition() const { return *m_condition; }
+ Statement const& getBody() const { return *m_body; }
+
+private:
+ ASTPointer<Expression> m_condition;
+ ASTPointer<Statement> m_body;
+};
+
+/**
+ * For loop statement
+ */
+class ForStatement: public BreakableStatement
+{
+public:
+ ForStatement(SourceLocation const& _location,
+ ASTPointer<Statement> const& _initExpression,
+ ASTPointer<Expression> const& _conditionExpression,
+ ASTPointer<ExpressionStatement> const& _loopExpression,
+ ASTPointer<Statement> const& _body):
+ BreakableStatement(_location),
+ m_initExpression(_initExpression),
+ m_condExpression(_conditionExpression),
+ m_loopExpression(_loopExpression),
+ m_body(_body) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ Statement const* getInitializationExpression() const { return m_initExpression.get(); }
+ Expression const* getCondition() const { return m_condExpression.get(); }
+ ExpressionStatement const* getLoopExpression() const { return m_loopExpression.get(); }
+ Statement const& getBody() const { return *m_body; }
+
+private:
+ /// For statement's initialization expresion. for(XXX; ; ). Can be empty
+ ASTPointer<Statement> m_initExpression;
+ /// For statement's condition expresion. for(; XXX ; ). Can be empty
+ ASTPointer<Expression> m_condExpression;
+ /// For statement's loop expresion. for(;;XXX). Can be empty
+ ASTPointer<ExpressionStatement> m_loopExpression;
+ /// The body of the loop
+ ASTPointer<Statement> m_body;
+};
+
+class Continue: public Statement
+{
+public:
+ Continue(SourceLocation const& _location): Statement(_location) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override {}
+};
+
+class Break: public Statement
+{
+public:
+ Break(SourceLocation const& _location): Statement(_location) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override {}
+};
+
+class Return: public Statement
+{
+public:
+ Return(SourceLocation const& _location, ASTPointer<Expression> _expression):
+ Statement(_location), m_expression(_expression), m_returnParameters(nullptr) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ void setFunctionReturnParameters(ParameterList const* _parameters) { m_returnParameters = _parameters; }
+ ParameterList const* getFunctionReturnParameters() const { return m_returnParameters; }
+ Expression const* getExpression() const { return m_expression.get(); }
+
+private:
+ ASTPointer<Expression> m_expression; ///< value to return, optional
+
+ /// Pointer to the parameter list of the function, filled by the @ref NameAndTypeResolver.
+ ParameterList const* m_returnParameters;
+};
+
+/**
+ * Definition of a variable as a statement inside a function. It requires a type name (which can
+ * also be "var") but the actual assignment can be missing.
+ * Examples: var a = 2; uint256 a;
+ */
+class VariableDeclarationStatement: public Statement
+{
+public:
+ VariableDeclarationStatement(SourceLocation const& _location, ASTPointer<VariableDeclaration> _variable):
+ Statement(_location), m_variable(_variable) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ VariableDeclaration const& getDeclaration() const { return *m_variable; }
+ Expression const* getExpression() const { return m_variable->getValue().get(); }
+
+private:
+ ASTPointer<VariableDeclaration> m_variable;
+};
+
+/**
+ * A statement that contains only an expression (i.e. an assignment, function call, ...).
+ */
+class ExpressionStatement: public Statement
+{
+public:
+ ExpressionStatement(SourceLocation const& _location, ASTPointer<Expression> _expression):
+ Statement(_location), m_expression(_expression) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements() override;
+
+ Expression const& getExpression() const { return *m_expression; }
+
+private:
+ ASTPointer<Expression> m_expression;
+};
+
+/// @}
+
+/// Expressions
+/// @{
+
+/**
+ * An expression, i.e. something that has a value (which can also be of type "void" in case
+ * of some function calls).
+ * @abstract
+ */
+class Expression: public ASTNode
+{
+public:
+ Expression(SourceLocation const& _location): ASTNode(_location) {}
+ /// Performs type checking after which m_type should be set.
+ /// @arg _argumentTypes if set, provides the argument types for the case that this expression
+ /// is used in the context of a call, used for function overload resolution.
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) = 0;
+
+ std::shared_ptr<Type const> const& getType() const { return m_type; }
+ bool isLValue() const { return m_isLValue; }
+
+ /// Helper function, infer the type via @ref checkTypeRequirements and then check that it
+ /// is implicitly convertible to @a _expectedType. If not, throw exception.
+ void expectType(Type const& _expectedType);
+ /// Checks that this expression is an lvalue and also registers that an address and
+ /// not a value is generated during compilation. Can be called after checkTypeRequirements()
+ /// by an enclosing expression.
+ void requireLValue();
+ /// Returns true if @a requireLValue was previously called on this expression.
+ bool lvalueRequested() const { return m_lvalueRequested; }
+
+protected:
+ //! Inferred type of the expression, only filled after a call to checkTypeRequirements().
+ std::shared_ptr<Type const> m_type;
+ //! If this expression is an lvalue (i.e. something that can be assigned to).
+ //! This is set during calls to @a checkTypeRequirements()
+ bool m_isLValue = false;
+ //! Whether the outer expression requested the address (true) or the value (false) of this expression.
+ bool m_lvalueRequested = false;
+};
+
+/// Assignment, can also be a compound assignment.
+/// Examples: (a = 7 + 8) or (a *= 2)
+class Assignment: public Expression
+{
+public:
+ Assignment(SourceLocation const& _location, ASTPointer<Expression> const& _leftHandSide,
+ Token::Value _assignmentOperator, ASTPointer<Expression> const& _rightHandSide):
+ Expression(_location), m_leftHandSide(_leftHandSide),
+ m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide)
+ {
+ solAssert(Token::isAssignmentOp(_assignmentOperator), "");
+ }
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Expression const& getLeftHandSide() const { return *m_leftHandSide; }
+ Token::Value getAssignmentOperator() const { return m_assigmentOperator; }
+ Expression const& getRightHandSide() const { return *m_rightHandSide; }
+
+private:
+ ASTPointer<Expression> m_leftHandSide;
+ Token::Value m_assigmentOperator;
+ ASTPointer<Expression> m_rightHandSide;
+};
+
+/**
+ * Operation involving a unary operator, pre- or postfix.
+ * Examples: ++i, delete x or !true
+ */
+class UnaryOperation: public Expression
+{
+public:
+ UnaryOperation(SourceLocation const& _location, Token::Value _operator,
+ ASTPointer<Expression> const& _subExpression, bool _isPrefix):
+ Expression(_location), m_operator(_operator),
+ m_subExpression(_subExpression), m_isPrefix(_isPrefix)
+ {
+ solAssert(Token::isUnaryOp(_operator), "");
+ }
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Token::Value getOperator() const { return m_operator; }
+ bool isPrefixOperation() const { return m_isPrefix; }
+ Expression const& getSubExpression() const { return *m_subExpression; }
+
+private:
+ Token::Value m_operator;
+ ASTPointer<Expression> m_subExpression;
+ bool m_isPrefix;
+};
+
+/**
+ * Operation involving a binary operator.
+ * Examples: 1 + 2, true && false or 1 <= 4
+ */
+class BinaryOperation: public Expression
+{
+public:
+ BinaryOperation(SourceLocation const& _location, ASTPointer<Expression> const& _left,
+ Token::Value _operator, ASTPointer<Expression> const& _right):
+ Expression(_location), m_left(_left), m_operator(_operator), m_right(_right)
+ {
+ solAssert(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator), "");
+ }
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Expression const& getLeftExpression() const { return *m_left; }
+ Expression const& getRightExpression() const { return *m_right; }
+ Token::Value getOperator() const { return m_operator; }
+ Type const& getCommonType() const { return *m_commonType; }
+
+private:
+ ASTPointer<Expression> m_left;
+ Token::Value m_operator;
+ ASTPointer<Expression> m_right;
+
+ /// The common type that is used for the operation, not necessarily the result type (e.g. for
+ /// comparisons, this is always bool).
+ std::shared_ptr<Type const> m_commonType;
+};
+
+/**
+ * Can be ordinary function call, type cast or struct construction.
+ */
+class FunctionCall: public Expression
+{
+public:
+ FunctionCall(SourceLocation const& _location, ASTPointer<Expression> const& _expression,
+ std::vector<ASTPointer<Expression>> const& _arguments, std::vector<ASTPointer<ASTString>> const& _names):
+ Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Expression const& getExpression() const { return *m_expression; }
+ std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; }
+ std::vector<ASTPointer<ASTString>> const& getNames() const { return m_names; }
+
+ /// @returns true if this is not an actual function call, but an explicit type conversion.
+ /// Returns false for struct constructor calls.
+ bool isTypeConversion() const;
+ /// @return true if this is a constructor call for a struct, i.e. StructName(...).
+ bool isStructConstructorCall() const;
+
+private:
+ ASTPointer<Expression> m_expression;
+ std::vector<ASTPointer<Expression>> m_arguments;
+ std::vector<ASTPointer<ASTString>> m_names;
+};
+
+/**
+ * Expression that creates a new contract, e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
+ */
+class NewExpression: public Expression
+{
+public:
+ NewExpression(SourceLocation const& _location, ASTPointer<Identifier> const& _contractName):
+ Expression(_location), m_contractName(_contractName) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ /// Returns the referenced contract. Can only be called after type checking.
+ ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; }
+
+private:
+ ASTPointer<Identifier> m_contractName;
+
+ ContractDefinition const* m_contract = nullptr;
+};
+
+/**
+ * Access to a member of an object. Example: x.name
+ */
+class MemberAccess: public Expression
+{
+public:
+ MemberAccess(SourceLocation const& _location, ASTPointer<Expression> _expression,
+ ASTPointer<ASTString> const& _memberName):
+ Expression(_location), m_expression(_expression), m_memberName(_memberName) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ Expression const& getExpression() const { return *m_expression; }
+ ASTString const& getMemberName() const { return *m_memberName; }
+ /// @returns the declaration referenced by this expression. Might return nullptr even if the
+ /// expression is valid, e.g. if the member does not correspond to an AST node.
+ Declaration const* referencedDeclaration() const { return m_referencedDeclaration; }
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+private:
+ ASTPointer<Expression> m_expression;
+ ASTPointer<ASTString> m_memberName;
+
+ /// Pointer to the referenced declaration, this is sometimes needed to resolve function over
+ /// loads in the type-checking phase.
+ Declaration const* m_referencedDeclaration = nullptr;
+};
+
+/**
+ * Index access to an array. Example: a[2]
+ */
+class IndexAccess: public Expression
+{
+public:
+ IndexAccess(SourceLocation const& _location, ASTPointer<Expression> const& _base,
+ ASTPointer<Expression> const& _index):
+ Expression(_location), m_base(_base), m_index(_index) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Expression const& getBaseExpression() const { return *m_base; }
+ Expression const* getIndexExpression() const { return m_index.get(); }
+
+private:
+ ASTPointer<Expression> m_base;
+ ASTPointer<Expression> m_index;
+};
+
+/**
+ * Primary expression, i.e. an expression that cannot be divided any further. Examples are literals
+ * or variable references.
+ */
+class PrimaryExpression: public Expression
+{
+public:
+ PrimaryExpression(SourceLocation const& _location): Expression(_location) {}
+};
+
+/**
+ * An identifier, i.e. a reference to a declaration by name like a variable or function.
+ */
+class Identifier: public PrimaryExpression
+{
+public:
+ Identifier(SourceLocation const& _location, ASTPointer<ASTString> const& _name):
+ PrimaryExpression(_location), m_name(_name) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ ASTString const& getName() const { return *m_name; }
+
+ void setReferencedDeclaration(
+ Declaration const& _referencedDeclaration,
+ ContractDefinition const* _currentContract = nullptr
+ )
+ {
+ m_referencedDeclaration = &_referencedDeclaration;
+ m_currentContract = _currentContract;
+ }
+ Declaration const& getReferencedDeclaration() const;
+
+ /// Stores a set of possible declarations referenced by this identifier. Has to be resolved
+ /// providing argument types using overloadResolution before the referenced declaration
+ /// is accessed.
+ void setOverloadedDeclarations(std::vector<Declaration const*> const& _declarations)
+ {
+ m_overloadedDeclarations = _declarations;
+ }
+
+ /// Tries to find exactly one of the possible referenced declarations provided the given
+ /// argument types in a call context.
+ void overloadResolution(TypePointers const& _argumentTypes);
+
+private:
+ ASTPointer<ASTString> m_name;
+
+ /// Declaration the name refers to.
+ Declaration const* m_referencedDeclaration = nullptr;
+ /// Stores a reference to the current contract. This is needed because types of base contracts
+ /// change depending on the context.
+ ContractDefinition const* m_currentContract = nullptr;
+ /// A vector of overloaded declarations, right now only FunctionDefinition has overloaded declarations.
+ std::vector<Declaration const*> m_overloadedDeclarations;
+};
+
+/**
+ * An elementary type name expression is used in expressions like "a = uint32(2)" to change the
+ * type of an expression explicitly. Here, "uint32" is the elementary type name expression and
+ * "uint32(2)" is a @ref FunctionCall.
+ */
+class ElementaryTypeNameExpression: public PrimaryExpression
+{
+public:
+ ElementaryTypeNameExpression(SourceLocation const& _location, Token::Value _typeToken):
+ PrimaryExpression(_location), m_typeToken(_typeToken)
+ {
+ solAssert(Token::isElementaryTypeName(_typeToken), "");
+ }
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Token::Value getTypeToken() const { return m_typeToken; }
+
+private:
+ Token::Value m_typeToken;
+};
+
+/**
+ * A literal string or number. @see ExpressionCompiler::endVisit() is used to actually parse its value.
+ */
+class Literal: public PrimaryExpression
+{
+public:
+ enum class SubDenomination
+ {
+ None = Token::Illegal,
+ Wei = Token::SubWei,
+ Szabo = Token::SubSzabo,
+ Finney = Token::SubFinney,
+ Ether = Token::SubEther,
+ Second = Token::SubSecond,
+ Minute = Token::SubMinute,
+ Hour = Token::SubHour,
+ Day = Token::SubDay,
+ Week = Token::SubWeek,
+ Year = Token::SubYear
+ };
+ Literal(SourceLocation const& _location, Token::Value _token,
+ ASTPointer<ASTString> const& _value,
+ SubDenomination _sub = SubDenomination::None):
+ PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {}
+ virtual void accept(ASTVisitor& _visitor) override;
+ virtual void accept(ASTConstVisitor& _visitor) const override;
+ virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
+
+ Token::Value getToken() const { return m_token; }
+ /// @returns the non-parsed value of the literal
+ ASTString const& getValue() const { return *m_value; }
+
+ SubDenomination getSubDenomination() const { return m_subDenomination; }
+
+private:
+ Token::Value m_token;
+ ASTPointer<ASTString> m_value;
+ SubDenomination m_subDenomination;
+};
+
+/// @}
+
+
+}
+}
diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h
new file mode 100644
index 00000000..0ba485a2
--- /dev/null
+++ b/libsolidity/ASTForward.h
@@ -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;
+
+
+}
+}
diff --git a/libsolidity/ASTJsonConverter.cpp b/libsolidity/ASTJsonConverter.cpp
new file mode 100644
index 00000000..122b1c0d
--- /dev/null
+++ b/libsolidity/ASTJsonConverter.cpp
@@ -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";
+}
+
+}
+}
diff --git a/libsolidity/ASTJsonConverter.h b/libsolidity/ASTJsonConverter.h
new file mode 100644
index 00000000..aaa54f7f
--- /dev/null
+++ b/libsolidity/ASTJsonConverter.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp
new file mode 100644
index 00000000..d29ace17
--- /dev/null
+++ b/libsolidity/ASTPrinter.cpp
@@ -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;
+}
+
+}
+}
diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h
new file mode 100644
index 00000000..cdf651f3
--- /dev/null
+++ b/libsolidity/ASTPrinter.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/ASTUtils.cpp b/libsolidity/ASTUtils.cpp
new file mode 100644
index 00000000..081d6b31
--- /dev/null
+++ b/libsolidity/ASTUtils.cpp
@@ -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;
+}
diff --git a/libsolidity/ASTUtils.h b/libsolidity/ASTUtils.h
new file mode 100644
index 00000000..b24a3404
--- /dev/null
+++ b/libsolidity/ASTUtils.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h
new file mode 100644
index 00000000..f7847220
--- /dev/null
+++ b/libsolidity/ASTVisitor.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h
new file mode 100644
index 00000000..3557f877
--- /dev/null
+++ b/libsolidity/AST_accept.h
@@ -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);
+}
+
+}
+}
diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp
new file mode 100644
index 00000000..48ee5a05
--- /dev/null
+++ b/libsolidity/ArrayUtils.cpp
@@ -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;
+}
diff --git a/libsolidity/ArrayUtils.h b/libsolidity/ArrayUtils.h
new file mode 100644
index 00000000..80ffc008
--- /dev/null
+++ b/libsolidity/ArrayUtils.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
new file mode 100644
index 00000000..787e5576
--- /dev/null
+++ b/libsolidity/CMakeLists.txt
@@ -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} )
+
diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp
new file mode 100644
index 00000000..f0d1b38e
--- /dev/null
+++ b/libsolidity/Compiler.cpp
@@ -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;
+}
diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h
new file mode 100644
index 00000000..bec2b064
--- /dev/null
+++ b/libsolidity/Compiler.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp
new file mode 100644
index 00000000..0f6f5fe7
--- /dev/null
+++ b/libsolidity/CompilerContext.cpp
@@ -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());
+}
+
+}
+}
diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h
new file mode 100644
index 00000000..3f97d900
--- /dev/null
+++ b/libsolidity/CompilerContext.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp
new file mode 100644
index 00000000..a85738eb
--- /dev/null
+++ b/libsolidity/CompilerStack.cpp
@@ -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>()) {}
+
+}
+}
diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h
new file mode 100644
index 00000000..735c4d15
--- /dev/null
+++ b/libsolidity/CompilerStack.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp
new file mode 100644
index 00000000..49247581
--- /dev/null
+++ b/libsolidity/CompilerUtils.cpp
@@ -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;
+}
+
+}
+}
diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h
new file mode 100644
index 00000000..dbb00a1d
--- /dev/null
+++ b/libsolidity/CompilerUtils.h
@@ -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;
+}
+
+}
+}
diff --git a/libsolidity/DeclarationContainer.cpp b/libsolidity/DeclarationContainer.cpp
new file mode 100644
index 00000000..3e23d93b
--- /dev/null
+++ b/libsolidity/DeclarationContainer.cpp
@@ -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*>({});
+}
diff --git a/libsolidity/DeclarationContainer.h b/libsolidity/DeclarationContainer.h
new file mode 100644
index 00000000..0f0b5717
--- /dev/null
+++ b/libsolidity/DeclarationContainer.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/Exceptions.h b/libsolidity/Exceptions.h
new file mode 100644
index 00000000..4bb6644b
--- /dev/null
+++ b/libsolidity/Exceptions.h
@@ -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>;
+
+}
+}
diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp
new file mode 100644
index 00000000..0841089b
--- /dev/null
+++ b/libsolidity/ExpressionCompiler.cpp
@@ -0,0 +1,1271 @@
+/*
+ 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 for expressions.
+ */
+
+#include <utility>
+#include <numeric>
+#include <boost/range/adaptor/reversed.hpp>
+#include <libevmcore/Params.h>
+#include <libdevcore/Common.h>
+#include <libdevcore/SHA3.h>
+#include <libsolidity/AST.h>
+#include <libsolidity/ExpressionCompiler.h>
+#include <libsolidity/CompilerContext.h>
+#include <libsolidity/CompilerUtils.h>
+#include <libsolidity/LValue.h>
+
+using namespace std;
+
+namespace dev
+{
+namespace solidity
+{
+
+void ExpressionCompiler::compile(Expression const& _expression)
+{
+ _expression.accept(*this);
+}
+
+void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl)
+{
+ if (!_varDecl.getValue())
+ return;
+ TypePointer type = _varDecl.getValue()->getType();
+ solAssert(!!type, "Type information not available.");
+ CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
+ _varDecl.getValue()->accept(*this);
+
+ if (_varDecl.getType()->dataStoredIn(DataLocation::Storage))
+ {
+ // reference type, only convert value to mobile type and do final conversion in storeValue.
+ utils().convertType(*type, *type->mobileType());
+ type = type->mobileType();
+ }
+ else
+ {
+ utils().convertType(*type, *_varDecl.getType());
+ type = _varDecl.getType();
+ }
+ StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.getLocation(), true);
+}
+
+void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
+ FunctionType accessorType(_varDecl);
+
+ TypePointers const& paramTypes = accessorType.getParameterTypes();
+
+ // retrieve the position of the variable
+ auto const& location = m_context.getStorageLocationOfVariable(_varDecl);
+ m_context << location.first << u256(location.second);
+
+ TypePointer returnType = _varDecl.getType();
+
+ for (size_t i = 0; i < paramTypes.size(); ++i)
+ {
+ if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get()))
+ {
+ solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
+ solAssert(
+ !paramTypes[i]->isDynamicallySized(),
+ "Accessors for mapping with dynamically-sized keys not yet implemented."
+ );
+ // pop offset
+ m_context << eth::Instruction::POP;
+ // move storage offset to memory.
+ utils().storeInMemory(32);
+ // move key to memory.
+ utils().copyToStackTop(paramTypes.size() - i, 1);
+ utils().storeInMemory(0);
+ m_context << u256(64) << u256(0) << eth::Instruction::SHA3;
+ // push offset
+ m_context << u256(0);
+ returnType = mappingType->getValueType();
+ }
+ else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get()))
+ {
+ // pop offset
+ m_context << eth::Instruction::POP;
+ utils().copyToStackTop(paramTypes.size() - i + 1, 1);
+ ArrayUtils(m_context).accessIndex(*arrayType);
+ returnType = arrayType->getBaseType();
+ }
+ else
+ solAssert(false, "Index access is allowed only for \"mapping\" and \"array\" types.");
+ }
+ // remove index arguments.
+ if (paramTypes.size() == 1)
+ m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::SWAP1;
+ else if (paramTypes.size() >= 2)
+ {
+ m_context << eth::swapInstruction(paramTypes.size());
+ m_context << eth::Instruction::POP;
+ m_context << eth::swapInstruction(paramTypes.size());
+ utils().popStackSlots(paramTypes.size() - 1);
+ }
+ unsigned retSizeOnStack = 0;
+ solAssert(accessorType.getReturnParameterTypes().size() >= 1, "");
+ auto const& returnTypes = accessorType.getReturnParameterTypes();
+ if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
+ {
+ // remove offset
+ m_context << eth::Instruction::POP;
+ auto const& names = accessorType.getReturnParameterNames();
+ // struct
+ for (size_t i = 0; i < names.size(); ++i)
+ {
+ if (returnTypes[i]->getCategory() == Type::Category::Mapping)
+ continue;
+ if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i].get()))
+ if (!arrayType->isByteArray())
+ continue;
+ pair<u256, unsigned> const& offsets = structType->getStorageOffsetsOfMember(names[i]);
+ m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second);
+ TypePointer memberType = structType->getMemberType(names[i]);
+ StorageItem(m_context, *memberType).retrieveValue(SourceLocation(), true);
+ utils().convertType(*memberType, *returnTypes[i]);
+ utils().moveToStackTop(returnTypes[i]->getSizeOnStack());
+ retSizeOnStack += returnTypes[i]->getSizeOnStack();
+ }
+ // remove slot
+ m_context << eth::Instruction::POP;
+ }
+ else
+ {
+ // simple value or array
+ solAssert(returnTypes.size() == 1, "");
+ StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
+ utils().convertType(*returnType, *returnTypes.front());
+ retSizeOnStack = returnTypes.front()->getSizeOnStack();
+ }
+ solAssert(retSizeOnStack == utils().getSizeOnStack(returnTypes), "");
+ solAssert(retSizeOnStack <= 15, "Stack is too deep.");
+ m_context << eth::dupInstruction(retSizeOnStack + 1);
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+}
+
+bool ExpressionCompiler::visit(Assignment const& _assignment)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _assignment);
+ _assignment.getRightHandSide().accept(*this);
+ TypePointer type = _assignment.getRightHandSide().getType();
+ if (!_assignment.getType()->dataStoredIn(DataLocation::Storage))
+ {
+ utils().convertType(*type, *_assignment.getType());
+ type = _assignment.getType();
+ }
+ else
+ {
+ utils().convertType(*type, *type->mobileType());
+ type = type->mobileType();
+ }
+
+ _assignment.getLeftHandSide().accept(*this);
+ solAssert(!!m_currentLValue, "LValue not retrieved.");
+
+ Token::Value op = _assignment.getAssignmentOperator();
+ if (op != Token::Assign) // compound assignment
+ {
+ solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types.");
+ unsigned lvalueSize = m_currentLValue->sizeOnStack();
+ unsigned itemSize = _assignment.getType()->getSizeOnStack();
+ if (lvalueSize > 0)
+ {
+ utils().copyToStackTop(lvalueSize + itemSize, itemSize);
+ utils().copyToStackTop(itemSize + lvalueSize, lvalueSize);
+ // value lvalue_ref value lvalue_ref
+ }
+ m_currentLValue->retrieveValue(_assignment.getLocation(), true);
+ appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType());
+ if (lvalueSize > 0)
+ {
+ solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables.");
+ // value [lvalue_ref] updated_value
+ for (unsigned i = 0; i < itemSize; ++i)
+ m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP;
+ }
+ }
+ m_currentLValue->storeValue(*type, _assignment.getLocation());
+ m_currentLValue.reset();
+ return false;
+}
+
+bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
+ //@todo type checking and creating code for an operator should be in the same place:
+ // the operator should know how to convert itself and to which types it applies, so
+ // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that
+ // represents the operator
+ if (_unaryOperation.getType()->getCategory() == Type::Category::IntegerConstant)
+ {
+ m_context << _unaryOperation.getType()->literalValue(nullptr);
+ return false;
+ }
+
+ _unaryOperation.getSubExpression().accept(*this);
+
+ switch (_unaryOperation.getOperator())
+ {
+ case Token::Not: // !
+ m_context << eth::Instruction::ISZERO;
+ break;
+ case Token::BitNot: // ~
+ m_context << eth::Instruction::NOT;
+ break;
+ case Token::After: // after
+ m_context << eth::Instruction::TIMESTAMP << eth::Instruction::ADD;
+ break;
+ case Token::Delete: // delete
+ solAssert(!!m_currentLValue, "LValue not retrieved.");
+ m_currentLValue->setToZero(_unaryOperation.getLocation());
+ m_currentLValue.reset();
+ break;
+ case Token::Inc: // ++ (pre- or postfix)
+ case Token::Dec: // -- (pre- or postfix)
+ solAssert(!!m_currentLValue, "LValue not retrieved.");
+ m_currentLValue->retrieveValue(_unaryOperation.getLocation());
+ if (!_unaryOperation.isPrefixOperation())
+ {
+ // store value for later
+ solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented.");
+ m_context << eth::Instruction::DUP1;
+ if (m_currentLValue->sizeOnStack() > 0)
+ for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
+ }
+ m_context << u256(1);
+ if (_unaryOperation.getOperator() == Token::Inc)
+ m_context << eth::Instruction::ADD;
+ else
+ m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB;
+ // Stack for prefix: [ref...] (*ref)+-1
+ // Stack for postfix: *ref [ref...] (*ref)+-1
+ for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
+ m_currentLValue->storeValue(
+ *_unaryOperation.getType(), _unaryOperation.getLocation(),
+ !_unaryOperation.isPrefixOperation());
+ m_currentLValue.reset();
+ break;
+ case Token::Add: // +
+ // unary add, so basically no-op
+ break;
+ case Token::Sub: // -
+ m_context << u256(0) << eth::Instruction::SUB;
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " +
+ string(Token::toString(_unaryOperation.getOperator()))));
+ }
+ return false;
+}
+
+bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation);
+ Expression const& leftExpression = _binaryOperation.getLeftExpression();
+ Expression const& rightExpression = _binaryOperation.getRightExpression();
+ Type const& commonType = _binaryOperation.getCommonType();
+ Token::Value const c_op = _binaryOperation.getOperator();
+
+ if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting
+ appendAndOrOperatorCode(_binaryOperation);
+ else if (commonType.getCategory() == Type::Category::IntegerConstant)
+ m_context << commonType.literalValue(nullptr);
+ else
+ {
+ bool cleanupNeeded = commonType.getCategory() == Type::Category::Integer &&
+ (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod);
+
+ // for commutative operators, push the literal as late as possible to allow improved optimization
+ auto isLiteral = [](Expression const& _e)
+ {
+ return dynamic_cast<Literal const*>(&_e) || _e.getType()->getCategory() == Type::Category::IntegerConstant;
+ };
+ bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression);
+ if (swap)
+ {
+ leftExpression.accept(*this);
+ utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded);
+ rightExpression.accept(*this);
+ utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded);
+ }
+ else
+ {
+ rightExpression.accept(*this);
+ utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded);
+ leftExpression.accept(*this);
+ utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded);
+ }
+ if (Token::isCompareOp(c_op))
+ appendCompareOperatorCode(c_op, commonType);
+ else
+ appendOrdinaryBinaryOperatorCode(c_op, commonType);
+ }
+
+ // do not visit the child nodes, we already did that explicitly
+ return false;
+}
+
+bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
+ using Location = FunctionType::Location;
+ if (_functionCall.isTypeConversion())
+ {
+ solAssert(_functionCall.getArguments().size() == 1, "");
+ solAssert(_functionCall.getNames().empty(), "");
+ Expression const& firstArgument = *_functionCall.getArguments().front();
+ firstArgument.accept(*this);
+ utils().convertType(*firstArgument.getType(), *_functionCall.getType());
+ return false;
+ }
+
+ FunctionTypePointer functionType;
+ if (_functionCall.isStructConstructorCall())
+ {
+ auto const& type = dynamic_cast<TypeType const&>(*_functionCall.getExpression().getType());
+ auto const& structType = dynamic_cast<StructType const&>(*type.getActualType());
+ functionType = structType.constructorType();
+ }
+ else
+ functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.getExpression().getType());
+
+ TypePointers const& parameterTypes = functionType->getParameterTypes();
+ vector<ASTPointer<Expression const>> const& callArguments = _functionCall.getArguments();
+ vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.getNames();
+ if (!functionType->takesArbitraryParameters())
+ solAssert(callArguments.size() == parameterTypes.size(), "");
+
+ vector<ASTPointer<Expression const>> arguments;
+ if (callArgumentNames.empty())
+ // normal arguments
+ arguments = callArguments;
+ else
+ // named arguments
+ for (auto const& parameterName: functionType->getParameterNames())
+ {
+ bool found = false;
+ for (size_t j = 0; j < callArgumentNames.size() && !found; j++)
+ if ((found = (parameterName == *callArgumentNames[j])))
+ // we found the actual parameter position
+ arguments.push_back(callArguments[j]);
+ solAssert(found, "");
+ }
+
+ if (_functionCall.isStructConstructorCall())
+ {
+ TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.getExpression().getType());
+ auto const& structType = dynamic_cast<StructType const&>(*type.getActualType());
+
+ m_context << u256(max(32u, structType.getCalldataEncodedSize(true)));
+ utils().allocateMemory();
+ m_context << eth::Instruction::DUP1;
+
+ for (unsigned i = 0; i < arguments.size(); ++i)
+ {
+ arguments[i]->accept(*this);
+ utils().convertType(*arguments[i]->getType(), *functionType->getParameterTypes()[i]);
+ utils().storeInMemoryDynamic(*functionType->getParameterTypes()[i]);
+ }
+ m_context << eth::Instruction::POP;
+ }
+ else
+ {
+ FunctionType const& function = *functionType;
+ switch (function.getLocation())
+ {
+ case Location::Internal:
+ {
+ // Calling convention: Caller pushes return address and arguments
+ // Callee removes them and pushes return values
+
+ eth::AssemblyItem returnLabel = m_context.pushNewTag();
+ for (unsigned i = 0; i < arguments.size(); ++i)
+ {
+ arguments[i]->accept(*this);
+ utils().convertType(*arguments[i]->getType(), *function.getParameterTypes()[i]);
+ }
+ _functionCall.getExpression().accept(*this);
+
+ m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction);
+ m_context << returnLabel;
+
+ unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes());
+ // callee adds return parameters, but removes arguments and return label
+ m_context.adjustStackOffset(returnParametersSize - CompilerUtils::getSizeOnStack(function.getParameterTypes()) - 1);
+
+ // @todo for now, the return value of a function is its first return value, so remove
+ // all others
+ for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i)
+ utils().popStackElement(*function.getReturnParameterTypes()[i]);
+ break;
+ }
+ case Location::External:
+ case Location::CallCode:
+ case Location::Bare:
+ case Location::BareCallCode:
+ _functionCall.getExpression().accept(*this);
+ appendExternalFunctionCall(function, arguments);
+ break;
+ case Location::Creation:
+ {
+ _functionCall.getExpression().accept(*this);
+ solAssert(!function.gasSet(), "Gas limit set for contract creation.");
+ solAssert(function.getReturnParameterTypes().size() == 1, "");
+ TypePointers argumentTypes;
+ for (auto const& arg: arguments)
+ {
+ arg->accept(*this);
+ argumentTypes.push_back(arg->getType());
+ }
+ ContractDefinition const& contract = dynamic_cast<ContractType const&>(
+ *function.getReturnParameterTypes().front()).getContractDefinition();
+ // copy the contract's code into memory
+ bytes const& bytecode = m_context.getCompiledContract(contract);
+ utils().fetchFreeMemoryPointer();
+ m_context << u256(bytecode.size()) << eth::Instruction::DUP1;
+ //@todo could be done by actually appending the Assembly, but then we probably need to compile
+ // multiple times. Will revisit once external fuctions are inlined.
+ m_context.appendData(bytecode);
+ m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY;
+
+ m_context << eth::Instruction::ADD;
+ utils().encodeToMemory(argumentTypes, function.getParameterTypes());
+ // now on stack: memory_end_ptr
+ // need: size, offset, endowment
+ utils().toSizeAfterFreeMemoryPointer();
+ if (function.valueSet())
+ m_context << eth::dupInstruction(3);
+ else
+ m_context << u256(0);
+ m_context << eth::Instruction::CREATE;
+ if (function.valueSet())
+ m_context << eth::swapInstruction(1) << eth::Instruction::POP;
+ break;
+ }
+ case Location::SetGas:
+ {
+ // stack layout: contract_address function_id [gas] [value]
+ _functionCall.getExpression().accept(*this);
+
+ arguments.front()->accept(*this);
+ utils().convertType(*arguments.front()->getType(), IntegerType(256), true);
+ // Note that function is not the original function, but the ".gas" function.
+ // Its values of gasSet and valueSet is equal to the original function's though.
+ unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0);
+ if (stackDepth > 0)
+ m_context << eth::swapInstruction(stackDepth);
+ if (function.gasSet())
+ m_context << eth::Instruction::POP;
+ break;
+ }
+ case Location::SetValue:
+ // stack layout: contract_address function_id [gas] [value]
+ _functionCall.getExpression().accept(*this);
+ // Note that function is not the original function, but the ".value" function.
+ // Its values of gasSet and valueSet is equal to the original function's though.
+ if (function.valueSet())
+ m_context << eth::Instruction::POP;
+ arguments.front()->accept(*this);
+ break;
+ case Location::Send:
+ _functionCall.getExpression().accept(*this);
+ m_context << u256(0); // do not send gas (there still is the stipend)
+ arguments.front()->accept(*this);
+ utils().convertType(
+ *arguments.front()->getType(),
+ *function.getParameterTypes().front(), true
+ );
+ appendExternalFunctionCall(
+ FunctionType(
+ TypePointers{},
+ TypePointers{},
+ strings(),
+ strings(),
+ Location::Bare,
+ false,
+ nullptr,
+ true,
+ true
+ ),
+ {}
+ );
+ break;
+ case Location::Suicide:
+ arguments.front()->accept(*this);
+ utils().convertType(*arguments.front()->getType(), *function.getParameterTypes().front(), true);
+ m_context << eth::Instruction::SUICIDE;
+ break;
+ case Location::SHA3:
+ {
+ TypePointers argumentTypes;
+ for (auto const& arg: arguments)
+ {
+ arg->accept(*this);
+ argumentTypes.push_back(arg->getType());
+ }
+ utils().fetchFreeMemoryPointer();
+ utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true);
+ utils().toSizeAfterFreeMemoryPointer();
+ m_context << eth::Instruction::SHA3;
+ break;
+ }
+ case Location::Log0:
+ case Location::Log1:
+ case Location::Log2:
+ case Location::Log3:
+ case Location::Log4:
+ {
+ unsigned logNumber = int(function.getLocation()) - int(Location::Log0);
+ for (unsigned arg = logNumber; arg > 0; --arg)
+ {
+ arguments[arg]->accept(*this);
+ utils().convertType(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true);
+ }
+ arguments.front()->accept(*this);
+ utils().fetchFreeMemoryPointer();
+ utils().encodeToMemory(
+ {arguments.front()->getType()},
+ {function.getParameterTypes().front()},
+ false,
+ true);
+ utils().toSizeAfterFreeMemoryPointer();
+ m_context << eth::logInstruction(logNumber);
+ break;
+ }
+ case Location::Event:
+ {
+ _functionCall.getExpression().accept(*this);
+ auto const& event = dynamic_cast<EventDefinition const&>(function.getDeclaration());
+ unsigned numIndexed = 0;
+ // All indexed arguments go to the stack
+ for (unsigned arg = arguments.size(); arg > 0; --arg)
+ if (event.getParameters()[arg - 1]->isIndexed())
+ {
+ ++numIndexed;
+ arguments[arg - 1]->accept(*this);
+ utils().convertType(
+ *arguments[arg - 1]->getType(),
+ *function.getParameterTypes()[arg - 1],
+ true
+ );
+ }
+ if (!event.isAnonymous())
+ {
+ m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName()))));
+ ++numIndexed;
+ }
+ solAssert(numIndexed <= 4, "Too many indexed arguments.");
+ // Copy all non-indexed arguments to memory (data)
+ // Memory position is only a hack and should be removed once we have free memory pointer.
+ TypePointers nonIndexedArgTypes;
+ TypePointers nonIndexedParamTypes;
+ for (unsigned arg = 0; arg < arguments.size(); ++arg)
+ if (!event.getParameters()[arg]->isIndexed())
+ {
+ arguments[arg]->accept(*this);
+ nonIndexedArgTypes.push_back(arguments[arg]->getType());
+ nonIndexedParamTypes.push_back(function.getParameterTypes()[arg]);
+ }
+ utils().fetchFreeMemoryPointer();
+ utils().encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes);
+ // need: topic1 ... topicn memsize memstart
+ utils().toSizeAfterFreeMemoryPointer();
+ m_context << eth::logInstruction(numIndexed);
+ break;
+ }
+ case Location::BlockHash:
+ {
+ arguments[0]->accept(*this);
+ utils().convertType(*arguments[0]->getType(), *function.getParameterTypes()[0], true);
+ m_context << eth::Instruction::BLOCKHASH;
+ break;
+ }
+ case Location::ECRecover:
+ case Location::SHA256:
+ case Location::RIPEMD160:
+ {
+ _functionCall.getExpression().accept(*this);
+ static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
+ {Location::SHA256, 2},
+ {Location::RIPEMD160, 3}};
+ m_context << contractAddresses.find(function.getLocation())->second;
+ for (unsigned i = function.getSizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
+ appendExternalFunctionCall(function, arguments);
+ break;
+ }
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type."));
+ }
+ }
+ return false;
+}
+
+bool ExpressionCompiler::visit(NewExpression const&)
+{
+ // code is created for the function call (CREATION) only
+ return false;
+}
+
+void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _memberAccess);
+ ASTString const& member = _memberAccess.getMemberName();
+ switch (_memberAccess.getExpression().getType()->getCategory())
+ {
+ case Type::Category::Contract:
+ {
+ bool alsoSearchInteger = false;
+ ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType());
+ if (type.isSuper())
+ {
+ solAssert(!!_memberAccess.referencedDeclaration(), "Referenced declaration not resolved.");
+ m_context << m_context.getSuperFunctionEntryLabel(
+ dynamic_cast<FunctionDefinition const&>(*_memberAccess.referencedDeclaration()),
+ type.getContractDefinition()
+ ).pushTag();
+ }
+ else
+ {
+ // ordinary contract type
+ if (Declaration const* declaration = _memberAccess.referencedDeclaration())
+ {
+ u256 identifier;
+ if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ identifier = FunctionType(*variable).externalIdentifier();
+ else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
+ identifier = FunctionType(*function).externalIdentifier();
+ else
+ solAssert(false, "Contract member is neither variable nor function.");
+ utils().convertType(type, IntegerType(0, IntegerType::Modifier::Address), true);
+ m_context << identifier;
+ }
+ else
+ // not found in contract, search in members inherited from address
+ alsoSearchInteger = true;
+ }
+ if (!alsoSearchInteger)
+ break;
+ }
+ case Type::Category::Integer:
+ if (member == "balance")
+ {
+ utils().convertType(
+ *_memberAccess.getExpression().getType(),
+ IntegerType(0, IntegerType::Modifier::Address),
+ true
+ );
+ m_context << eth::Instruction::BALANCE;
+ }
+ else if ((set<string>{"send", "call", "callcode"}).count(member))
+ utils().convertType(
+ *_memberAccess.getExpression().getType(),
+ IntegerType(0, IntegerType::Modifier::Address),
+ true
+ );
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer."));
+ break;
+ case Type::Category::Function:
+ solAssert(!!_memberAccess.getExpression().getType()->getMemberType(member),
+ "Invalid member access to function.");
+ break;
+ case Type::Category::Magic:
+ // we can ignore the kind of magic and only look at the name of the member
+ if (member == "coinbase")
+ m_context << eth::Instruction::COINBASE;
+ else if (member == "timestamp")
+ m_context << eth::Instruction::TIMESTAMP;
+ else if (member == "difficulty")
+ m_context << eth::Instruction::DIFFICULTY;
+ else if (member == "number")
+ m_context << eth::Instruction::NUMBER;
+ else if (member == "gaslimit")
+ m_context << eth::Instruction::GASLIMIT;
+ else if (member == "sender")
+ m_context << eth::Instruction::CALLER;
+ else if (member == "value")
+ m_context << eth::Instruction::CALLVALUE;
+ else if (member == "origin")
+ m_context << eth::Instruction::ORIGIN;
+ else if (member == "gas")
+ m_context << eth::Instruction::GAS;
+ else if (member == "gasprice")
+ m_context << eth::Instruction::GASPRICE;
+ else if (member == "data")
+ m_context << u256(0) << eth::Instruction::CALLDATASIZE;
+ else if (member == "sig")
+ m_context << u256(0) << eth::Instruction::CALLDATALOAD
+ << (u256(0xffffffff) << (256 - 32)) << eth::Instruction::AND;
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
+ break;
+ case Type::Category::Struct:
+ {
+ StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
+ switch (type.location())
+ {
+ case DataLocation::Storage:
+ {
+ pair<u256, unsigned> const& offsets = type.getStorageOffsetsOfMember(member);
+ m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second);
+ setLValueToStorageItem(_memberAccess);
+ break;
+ }
+ case DataLocation::Memory:
+ {
+ m_context << type.memoryOffsetOfMember(member) << eth::Instruction::ADD;
+ setLValue<MemoryItem>(_memberAccess, *_memberAccess.getType());
+ break;
+ }
+ default:
+ solAssert(false, "Illegal data location for struct.");
+ }
+ break;
+ }
+ case Type::Category::Enum:
+ {
+ EnumType const& type = dynamic_cast<EnumType const&>(*_memberAccess.getExpression().getType());
+ m_context << type.getMemberValue(_memberAccess.getMemberName());
+ break;
+ }
+ case Type::Category::TypeType:
+ {
+ TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.getExpression().getType());
+ solAssert(
+ !type.getMembers().membersByName(_memberAccess.getMemberName()).empty(),
+ "Invalid member access to " + type.toString(false)
+ );
+
+ if (dynamic_cast<ContractType const*>(type.getActualType().get()))
+ {
+ auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.referencedDeclaration());
+ solAssert(!!function, "Function not found in member access");
+ m_context << m_context.getFunctionEntryLabel(*function).pushTag();
+ }
+ else if (auto enumType = dynamic_cast<EnumType const*>(type.getActualType().get()))
+ m_context << enumType->getMemberValue(_memberAccess.getMemberName());
+ break;
+ }
+ case Type::Category::Array:
+ {
+ solAssert(member == "length", "Illegal array member.");
+ auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.getExpression().getType());
+ if (!type.isDynamicallySized())
+ {
+ utils().popStackElement(type);
+ m_context << type.getLength();
+ }
+ else
+ switch (type.location())
+ {
+ case DataLocation::CallData:
+ m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
+ break;
+ case DataLocation::Storage:
+ setLValue<StorageArrayLength>(_memberAccess, type);
+ break;
+ case DataLocation::Memory:
+ m_context << eth::Instruction::MLOAD;
+ break;
+ }
+ break;
+ }
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type."));
+ }
+}
+
+bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
+ _indexAccess.getBaseExpression().accept(*this);
+
+ Type const& baseType = *_indexAccess.getBaseExpression().getType();
+ if (baseType.getCategory() == Type::Category::Mapping)
+ {
+ // stack: storage_base_ref
+ TypePointer keyType = dynamic_cast<MappingType const&>(baseType).getKeyType();
+ solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
+ if (keyType->isDynamicallySized())
+ {
+ _indexAccess.getIndexExpression()->accept(*this);
+ utils().fetchFreeMemoryPointer();
+ // stack: base index mem
+ // note: the following operations must not allocate memory!
+ utils().encodeToMemory(
+ TypePointers{_indexAccess.getIndexExpression()->getType()},
+ TypePointers{keyType},
+ false,
+ true
+ );
+ m_context << eth::Instruction::SWAP1;
+ utils().storeInMemoryDynamic(IntegerType(256));
+ utils().toSizeAfterFreeMemoryPointer();
+ }
+ else
+ {
+ m_context << u256(0); // memory position
+ appendExpressionCopyToMemory(*keyType, *_indexAccess.getIndexExpression());
+ m_context << eth::Instruction::SWAP1;
+ solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
+ utils().storeInMemoryDynamic(IntegerType(256));
+ m_context << u256(0);
+ }
+ m_context << eth::Instruction::SHA3;
+ m_context << u256(0);
+ setLValueToStorageItem(_indexAccess);
+ }
+ else if (baseType.getCategory() == Type::Category::Array)
+ {
+ ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
+ solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
+
+ _indexAccess.getIndexExpression()->accept(*this);
+ // stack layout: <base_ref> [<length>] <index>
+ ArrayUtils(m_context).accessIndex(arrayType);
+ switch (arrayType.location())
+ {
+ case DataLocation::Storage:
+ if (arrayType.isByteArray())
+ {
+ solAssert(!arrayType.isString(), "Index access to string is not allowed.");
+ setLValue<StorageByteArrayElement>(_indexAccess);
+ }
+ else
+ setLValueToStorageItem(_indexAccess);
+ break;
+ case DataLocation::Memory:
+ setLValue<MemoryItem>(_indexAccess, *_indexAccess.getType(), !arrayType.isByteArray());
+ break;
+ case DataLocation::CallData:
+ //@todo if we implement this, the value in calldata has to be added to the base offset
+ solAssert(!arrayType.getBaseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
+ if (arrayType.getBaseType()->isValueType())
+ CompilerUtils(m_context).loadFromMemoryDynamic(
+ *arrayType.getBaseType(),
+ true,
+ !arrayType.isByteArray(),
+ false
+ );
+ break;
+ }
+ }
+ else
+ solAssert(false, "Index access only allowed for mappings or arrays.");
+
+ return false;
+}
+
+void ExpressionCompiler::endVisit(Identifier const& _identifier)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _identifier);
+ Declaration const* declaration = &_identifier.getReferencedDeclaration();
+ if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
+ {
+ switch (magicVar->getType()->getCategory())
+ {
+ case Type::Category::Contract:
+ // "this" or "super"
+ if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper())
+ m_context << eth::Instruction::ADDRESS;
+ break;
+ case Type::Category::Integer:
+ // "now"
+ m_context << eth::Instruction::TIMESTAMP;
+ break;
+ default:
+ break;
+ }
+ }
+ else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
+ m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag();
+ else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ {
+ if (!variable->isConstant())
+ setLValueFromDeclaration(*declaration, _identifier);
+ else
+ variable->getValue()->accept(*this);
+ }
+ else if (dynamic_cast<ContractDefinition const*>(declaration))
+ {
+ // no-op
+ }
+ else if (dynamic_cast<EventDefinition const*>(declaration))
+ {
+ // no-op
+ }
+ else if (dynamic_cast<EnumDefinition const*>(declaration))
+ {
+ // no-op
+ }
+ else
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context."));
+ }
+}
+
+void ExpressionCompiler::endVisit(Literal const& _literal)
+{
+ CompilerContext::LocationSetter locationSetter(m_context, _literal);
+ TypePointer type = _literal.getType();
+ switch (type->getCategory())
+ {
+ case Type::Category::IntegerConstant:
+ case Type::Category::Bool:
+ m_context << type->literalValue(&_literal);
+ break;
+ case Type::Category::StringLiteral:
+ break; // will be done during conversion
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now."));
+ }
+}
+
+void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation)
+{
+ Token::Value const c_op = _binaryOperation.getOperator();
+ solAssert(c_op == Token::Or || c_op == Token::And, "");
+
+ _binaryOperation.getLeftExpression().accept(*this);
+ m_context << eth::Instruction::DUP1;
+ if (c_op == Token::And)
+ m_context << eth::Instruction::ISZERO;
+ eth::AssemblyItem endLabel = m_context.appendConditionalJump();
+ m_context << eth::Instruction::POP;
+ _binaryOperation.getRightExpression().accept(*this);
+ m_context << endLabel;
+}
+
+void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
+{
+ if (_operator == Token::Equal || _operator == Token::NotEqual)
+ {
+ m_context << eth::Instruction::EQ;
+ if (_operator == Token::NotEqual)
+ m_context << eth::Instruction::ISZERO;
+ }
+ else
+ {
+ bool isSigned = false;
+ if (auto type = dynamic_cast<IntegerType const*>(&_type))
+ isSigned = type->isSigned();
+
+ switch (_operator)
+ {
+ case Token::GreaterThanOrEqual:
+ m_context <<
+ (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) <<
+ eth::Instruction::ISZERO;
+ break;
+ case Token::LessThanOrEqual:
+ m_context <<
+ (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) <<
+ eth::Instruction::ISZERO;
+ break;
+ case Token::GreaterThan:
+ m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
+ break;
+ case Token::LessThan:
+ m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator."));
+ }
+ }
+}
+
+void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
+{
+ if (Token::isArithmeticOp(_operator))
+ appendArithmeticOperatorCode(_operator, _type);
+ else if (Token::isBitOp(_operator))
+ appendBitOperatorCode(_operator);
+ else if (Token::isShiftOp(_operator))
+ appendShiftOperatorCode(_operator);
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator."));
+}
+
+void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
+{
+ IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
+ bool const c_isSigned = type.isSigned();
+
+ switch (_operator)
+ {
+ case Token::Add:
+ m_context << eth::Instruction::ADD;
+ break;
+ case Token::Sub:
+ m_context << eth::Instruction::SUB;
+ break;
+ case Token::Mul:
+ m_context << eth::Instruction::MUL;
+ break;
+ case Token::Div:
+ m_context << (c_isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
+ break;
+ case Token::Mod:
+ m_context << (c_isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
+ break;
+ case Token::Exp:
+ m_context << eth::Instruction::EXP;
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator."));
+ }
+}
+
+void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
+{
+ switch (_operator)
+ {
+ case Token::BitOr:
+ m_context << eth::Instruction::OR;
+ break;
+ case Token::BitAnd:
+ m_context << eth::Instruction::AND;
+ break;
+ case Token::BitXor:
+ m_context << eth::Instruction::XOR;
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator."));
+ }
+}
+
+void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
+{
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented."));
+ switch (_operator)
+ {
+ case Token::SHL:
+ break;
+ case Token::SAR:
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator."));
+ }
+}
+
+void ExpressionCompiler::appendExternalFunctionCall(
+ FunctionType const& _functionType,
+ vector<ASTPointer<Expression const>> const& _arguments
+)
+{
+ solAssert(_functionType.takesArbitraryParameters() ||
+ _arguments.size() == _functionType.getParameterTypes().size(), "");
+
+ // Assumed stack content here:
+ // <stack top>
+ // value [if _functionType.valueSet()]
+ // gas [if _functionType.gasSet()]
+ // function identifier [unless bare]
+ // contract address
+
+ unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0);
+
+ unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (_functionType.isBareCall() ? 0 : 1));
+ unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize);
+ unsigned valueStackPos = m_context.currentToBaseStackOffset(1);
+
+ using FunctionKind = FunctionType::Location;
+ FunctionKind funKind = _functionType.getLocation();
+ bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
+ bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode;
+
+ //@todo only return the first return value for now
+ Type const* firstReturnType =
+ _functionType.getReturnParameterTypes().empty() ?
+ nullptr :
+ _functionType.getReturnParameterTypes().front().get();
+ unsigned retSize = 0;
+ if (returnSuccessCondition)
+ retSize = 0; // return value actually is success condition
+ else if (firstReturnType)
+ {
+ retSize = firstReturnType->getCalldataEncodedSize();
+ solAssert(retSize > 0, "Unable to return dynamic type from external call.");
+ }
+
+ // Evaluate arguments.
+ TypePointers argumentTypes;
+ bool manualFunctionId =
+ (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) &&
+ !_arguments.empty() &&
+ _arguments.front()->getType()->mobileType()->getCalldataEncodedSize(false) ==
+ CompilerUtils::dataStartOffset;
+ if (manualFunctionId)
+ {
+ // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as
+ // function identifier.
+ _arguments.front()->accept(*this);
+ utils().convertType(
+ *_arguments.front()->getType(),
+ IntegerType(8 * CompilerUtils::dataStartOffset),
+ true
+ );
+ for (unsigned i = 0; i < gasValueSize; ++i)
+ m_context << eth::swapInstruction(gasValueSize - i);
+ gasStackPos++;
+ valueStackPos++;
+ }
+ for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i)
+ {
+ _arguments[i]->accept(*this);
+ argumentTypes.push_back(_arguments[i]->getType());
+ }
+
+ // Copy function identifier to memory.
+ utils().fetchFreeMemoryPointer();
+ if (!_functionType.isBareCall() || manualFunctionId)
+ {
+ m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::getSizeOnStack(argumentTypes));
+ utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false);
+ }
+ // If the function takes arbitrary parameters, copy dynamic length data in place.
+ // Move argumenst to memory, will not update the free memory pointer (but will update the memory
+ // pointer on the stack).
+ utils().encodeToMemory(
+ argumentTypes,
+ _functionType.getParameterTypes(),
+ _functionType.padArguments(),
+ _functionType.takesArbitraryParameters()
+ );
+
+ // Stack now:
+ // <stack top>
+ // input_memory_end
+ // value [if _functionType.valueSet()]
+ // gas [if _functionType.gasSet()]
+ // function identifier [unless bare]
+ // contract address
+
+ // Output data will replace input data.
+ // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input>
+ m_context << u256(retSize);
+ utils().fetchFreeMemoryPointer();
+ m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB;
+ m_context << eth::Instruction::DUP2;
+
+ // CALL arguments: outSize, outOff, inSize, inOff (already present up to here)
+ // value, addr, gas (stack top)
+ if (_functionType.valueSet())
+ m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
+ else
+ m_context << u256(0);
+ m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos));
+
+ if (_functionType.gasSet())
+ m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
+ else
+ {
+ // send all gas except the amount needed to execute "SUB" and "CALL"
+ // @todo this retains too much gas for now, needs to be fine-tuned.
+ u256 gasNeededByCaller = eth::c_callGas + 10;
+ if (_functionType.valueSet())
+ gasNeededByCaller += eth::c_callValueTransferGas;
+ if (!isCallCode)
+ gasNeededByCaller += eth::c_callNewAccountGas; // we never know
+ m_context <<
+ gasNeededByCaller <<
+ eth::Instruction::GAS <<
+ eth::Instruction::SUB;
+ }
+ if (isCallCode)
+ m_context << eth::Instruction::CALLCODE;
+ else
+ m_context << eth::Instruction::CALL;
+
+ unsigned remainsSize =
+ 2 + // contract address, input_memory_end
+ _functionType.valueSet() +
+ _functionType.gasSet() +
+ (!_functionType.isBareCall() || manualFunctionId);
+
+ if (returnSuccessCondition)
+ m_context << eth::swapInstruction(remainsSize);
+ else
+ {
+ //Propagate error condition (if CALL pushes 0 on stack).
+ m_context << eth::Instruction::ISZERO;
+ m_context.appendConditionalJumpTo(m_context.errorTag());
+ }
+
+ utils().popStackSlots(remainsSize);
+
+ if (returnSuccessCondition)
+ {
+ // already there
+ }
+ else if (funKind == FunctionKind::RIPEMD160)
+ {
+ // fix: built-in contract returns right-aligned data
+ utils().fetchFreeMemoryPointer();
+ utils().loadFromMemoryDynamic(IntegerType(160), false, true, false);
+ utils().convertType(IntegerType(160), FixedBytesType(20));
+ }
+ else if (firstReturnType)
+ {
+ //@todo manually update free memory pointer if we accept returning memory-stored objects
+ utils().fetchFreeMemoryPointer();
+ utils().loadFromMemoryDynamic(*firstReturnType, false, true, false);
+
+ }
+}
+
+void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)
+{
+ solAssert(_expectedType.isValueType(), "Not implemented for non-value types.");
+ _expression.accept(*this);
+ utils().convertType(*_expression.getType(), _expectedType, true);
+ utils().storeInMemoryDynamic(_expectedType);
+}
+
+void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression)
+{
+ if (m_context.isLocalVariable(&_declaration))
+ setLValue<StackVariable>(_expression, _declaration);
+ else if (m_context.isStateVariable(&_declaration))
+ setLValue<StorageItem>(_expression, _declaration);
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError()
+ << errinfo_sourceLocation(_expression.getLocation())
+ << errinfo_comment("Identifier type not supported or identifier not found."));
+}
+
+void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
+{
+ setLValue<StorageItem>(_expression, *_expression.getType());
+}
+
+CompilerUtils ExpressionCompiler::utils()
+{
+ return CompilerUtils(m_context);
+}
+
+}
+}
diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h
new file mode 100644
index 00000000..642560c6
--- /dev/null
+++ b/libsolidity/ExpressionCompiler.h
@@ -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);
+}
+
+}
+}
diff --git a/libsolidity/GasEstimator.cpp b/libsolidity/GasEstimator.cpp
new file mode 100644
index 00000000..01219a65
--- /dev/null
+++ b/libsolidity/GasEstimator.cpp
@@ -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;
+}
diff --git a/libsolidity/GasEstimator.h b/libsolidity/GasEstimator.h
new file mode 100644
index 00000000..4020d60b
--- /dev/null
+++ b/libsolidity/GasEstimator.h
@@ -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);
+};
+
+}
+}
diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp
new file mode 100644
index 00000000..80cebd76
--- /dev/null
+++ b/libsolidity/GlobalContext.cpp
@@ -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();
+}
+
+}
+}
diff --git a/libsolidity/GlobalContext.h b/libsolidity/GlobalContext.h
new file mode 100644
index 00000000..f861c67d
--- /dev/null
+++ b/libsolidity/GlobalContext.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp
new file mode 100644
index 00000000..c6f8553d
--- /dev/null
+++ b/libsolidity/InterfaceHandler.cpp
@@ -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
diff --git a/libsolidity/InterfaceHandler.h b/libsolidity/InterfaceHandler.h
new file mode 100644
index 00000000..7784dbd7
--- /dev/null
+++ b/libsolidity/InterfaceHandler.h
@@ -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
diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp
new file mode 100644
index 00000000..5c43fb82
--- /dev/null
+++ b/libsolidity/LValue.cpp
@@ -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);
+}
diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h
new file mode 100644
index 00000000..a93df9f9
--- /dev/null
+++ b/libsolidity/LValue.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp
new file mode 100644
index 00000000..87f9da7e
--- /dev/null
+++ b/libsolidity/NameAndTypeResolver.cpp
@@ -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;
+}
+
+}
+}
diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h
new file mode 100644
index 00000000..d7a0a3b2
--- /dev/null
+++ b/libsolidity/NameAndTypeResolver.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp
new file mode 100644
index 00000000..fbf8478d
--- /dev/null
+++ b/libsolidity/Parser.cpp
@@ -0,0 +1,1082 @@
+/*
+ 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.
+ */
+
+#include <vector>
+#include <libdevcore/Log.h>
+#include <libevmasm/SourceLocation.h>
+#include <libsolidity/Parser.h>
+#include <libsolidity/Scanner.h>
+#include <libsolidity/Exceptions.h>
+#include <libsolidity/InterfaceHandler.h>
+
+using namespace std;
+
+namespace dev
+{
+namespace solidity
+{
+
+/// AST node factory that also tracks the begin and end position of an AST node
+/// while it is being parsed
+class Parser::ASTNodeFactory
+{
+public:
+ ASTNodeFactory(Parser const& _parser):
+ m_parser(_parser), m_location(_parser.getPosition(), -1, _parser.getSourceName()) {}
+ ASTNodeFactory(Parser const& _parser, ASTPointer<ASTNode> const& _childNode):
+ m_parser(_parser), m_location(_childNode->getLocation()) {}
+
+ void markEndPosition() { m_location.end = m_parser.getEndPosition(); }
+ void setLocation(SourceLocation const& _location) { m_location = _location; }
+ void setLocationEmpty() { m_location.end = m_location.start; }
+ /// Set the end position to the one of the given node.
+ void setEndPositionFromNode(ASTPointer<ASTNode> const& _node) { m_location.end = _node->getLocation().end; }
+
+ template <class NodeType, typename... Args>
+ ASTPointer<NodeType> createNode(Args&& ... _args)
+ {
+ if (m_location.end < 0)
+ markEndPosition();
+ return make_shared<NodeType>(m_location, forward<Args>(_args)...);
+ }
+
+private:
+ Parser const& m_parser;
+ SourceLocation m_location;
+};
+
+ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
+{
+ m_scanner = _scanner;
+ ASTNodeFactory nodeFactory(*this);
+ vector<ASTPointer<ASTNode>> nodes;
+ while (_scanner->getCurrentToken() != Token::EOS)
+ {
+ switch (m_scanner->getCurrentToken())
+ {
+ case Token::Import:
+ nodes.push_back(parseImportDirective());
+ break;
+ case Token::Contract:
+ nodes.push_back(parseContractDefinition());
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(createParserError(std::string("Expected import directive or contract definition.")));
+ }
+ }
+ return nodeFactory.createNode<SourceUnit>(nodes);
+}
+
+std::shared_ptr<const string> const& Parser::getSourceName() const
+{
+ return m_scanner->getSourceName();
+}
+
+int Parser::getPosition() const
+{
+ return m_scanner->getCurrentLocation().start;
+}
+
+int Parser::getEndPosition() const
+{
+ return m_scanner->getCurrentLocation().end;
+}
+
+ASTPointer<ImportDirective> Parser::parseImportDirective()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::Import);
+ if (m_scanner->getCurrentToken() != Token::StringLiteral)
+ BOOST_THROW_EXCEPTION(createParserError("Expected string literal (URL)."));
+ ASTPointer<ASTString> url = getLiteralAndAdvance();
+ nodeFactory.markEndPosition();
+ expectToken(Token::Semicolon);
+ return nodeFactory.createNode<ImportDirective>(url);
+}
+
+ASTPointer<ContractDefinition> Parser::parseContractDefinition()
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<ASTString> docString;
+ if (m_scanner->getCurrentCommentLiteral() != "")
+ docString = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral());
+ expectToken(Token::Contract);
+ ASTPointer<ASTString> name = expectIdentifierToken();
+ vector<ASTPointer<InheritanceSpecifier>> baseContracts;
+ vector<ASTPointer<StructDefinition>> structs;
+ vector<ASTPointer<EnumDefinition>> enums;
+ vector<ASTPointer<VariableDeclaration>> stateVariables;
+ vector<ASTPointer<FunctionDefinition>> functions;
+ vector<ASTPointer<ModifierDefinition>> modifiers;
+ vector<ASTPointer<EventDefinition>> events;
+ if (m_scanner->getCurrentToken() == Token::Is)
+ do
+ {
+ m_scanner->next();
+ baseContracts.push_back(parseInheritanceSpecifier());
+ }
+ while (m_scanner->getCurrentToken() == Token::Comma);
+ expectToken(Token::LBrace);
+ while (true)
+ {
+ Token::Value currentToken = m_scanner->getCurrentToken();
+ if (currentToken == Token::RBrace)
+ break;
+ else if (currentToken == Token::Function)
+ functions.push_back(parseFunctionDefinition(name.get()));
+ else if (currentToken == Token::Struct)
+ structs.push_back(parseStructDefinition());
+ else if (currentToken == Token::Enum)
+ enums.push_back(parseEnumDefinition());
+ else if (currentToken == Token::Identifier || currentToken == Token::Mapping ||
+ Token::isElementaryTypeName(currentToken))
+ {
+ VarDeclParserOptions options;
+ options.isStateVariable = true;
+ options.allowInitialValue = true;
+ stateVariables.push_back(parseVariableDeclaration(options));
+ expectToken(Token::Semicolon);
+ }
+ else if (currentToken == Token::Modifier)
+ modifiers.push_back(parseModifierDefinition());
+ else if (currentToken == Token::Event)
+ events.push_back(parseEventDefinition());
+ else
+ BOOST_THROW_EXCEPTION(createParserError("Function, variable, struct or modifier declaration expected."));
+ }
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrace);
+ return nodeFactory.createNode<ContractDefinition>(
+ name,
+ docString,
+ baseContracts,
+ structs,
+ enums,
+ stateVariables,
+ functions,
+ modifiers,
+ events
+ );
+}
+
+ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<Identifier> name(parseIdentifier());
+ vector<ASTPointer<Expression>> arguments;
+ if (m_scanner->getCurrentToken() == Token::LParen)
+ {
+ m_scanner->next();
+ arguments = parseFunctionCallListArguments();
+ nodeFactory.markEndPosition();
+ expectToken(Token::RParen);
+ }
+ else
+ nodeFactory.setEndPositionFromNode(name);
+ return nodeFactory.createNode<InheritanceSpecifier>(name, arguments);
+}
+
+Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
+{
+ Declaration::Visibility visibility(Declaration::Visibility::Default);
+ if (_token == Token::Public)
+ visibility = Declaration::Visibility::Public;
+ else if (_token == Token::Internal)
+ visibility = Declaration::Visibility::Internal;
+ else if (_token == Token::Private)
+ visibility = Declaration::Visibility::Private;
+ else if (_token == Token::External)
+ visibility = Declaration::Visibility::External;
+ else
+ solAssert(false, "Invalid visibility specifier.");
+ m_scanner->next();
+ return visibility;
+}
+
+ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* _contractName)
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<ASTString> docstring;
+ if (m_scanner->getCurrentCommentLiteral() != "")
+ docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral());
+
+ expectToken(Token::Function);
+ ASTPointer<ASTString> name;
+ if (m_scanner->getCurrentToken() == Token::LParen)
+ name = make_shared<ASTString>(); // anonymous function
+ else
+ name = expectIdentifierToken();
+ VarDeclParserOptions options;
+ options.allowLocationSpecifier = true;
+ ASTPointer<ParameterList> parameters(parseParameterList(options));
+ bool isDeclaredConst = false;
+ Declaration::Visibility visibility(Declaration::Visibility::Default);
+ vector<ASTPointer<ModifierInvocation>> modifiers;
+ while (true)
+ {
+ Token::Value token = m_scanner->getCurrentToken();
+ if (token == Token::Const)
+ {
+ isDeclaredConst = true;
+ m_scanner->next();
+ }
+ else if (token == Token::Identifier)
+ modifiers.push_back(parseModifierInvocation());
+ else if (Token::isVisibilitySpecifier(token))
+ {
+ if (visibility != Declaration::Visibility::Default)
+ BOOST_THROW_EXCEPTION(createParserError("Multiple visibility specifiers."));
+ visibility = parseVisibilitySpecifier(token);
+ }
+ else
+ break;
+ }
+ ASTPointer<ParameterList> returnParameters;
+ if (m_scanner->getCurrentToken() == Token::Returns)
+ {
+ bool const permitEmptyParameterList = false;
+ m_scanner->next();
+ returnParameters = parseParameterList(options, permitEmptyParameterList);
+ }
+ else
+ returnParameters = createEmptyParameterList();
+ ASTPointer<Block> block = ASTPointer<Block>();
+ nodeFactory.markEndPosition();
+ if (m_scanner->getCurrentToken() != Token::Semicolon)
+ {
+ block = parseBlock();
+ nodeFactory.setEndPositionFromNode(block);
+ }
+ else
+ m_scanner->next(); // just consume the ';'
+ bool const c_isConstructor = (_contractName && *name == *_contractName);
+ return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring,
+ parameters, isDeclaredConst, modifiers,
+ returnParameters, block);
+}
+
+ASTPointer<StructDefinition> Parser::parseStructDefinition()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::Struct);
+ ASTPointer<ASTString> name = expectIdentifierToken();
+ vector<ASTPointer<VariableDeclaration>> members;
+ expectToken(Token::LBrace);
+ while (m_scanner->getCurrentToken() != Token::RBrace)
+ {
+ members.push_back(parseVariableDeclaration());
+ expectToken(Token::Semicolon);
+ }
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrace);
+ return nodeFactory.createNode<StructDefinition>(name, members);
+}
+
+ASTPointer<EnumValue> Parser::parseEnumValue()
+{
+ ASTNodeFactory nodeFactory(*this);
+ nodeFactory.markEndPosition();
+ return nodeFactory.createNode<EnumValue>(expectIdentifierToken());
+}
+
+ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::Enum);
+ ASTPointer<ASTString> name = expectIdentifierToken();
+ vector<ASTPointer<EnumValue>> members;
+ expectToken(Token::LBrace);
+
+ while (m_scanner->getCurrentToken() != Token::RBrace)
+ {
+ members.push_back(parseEnumValue());
+ if (m_scanner->getCurrentToken() == Token::RBrace)
+ break;
+ expectToken(Token::Comma);
+ if (m_scanner->getCurrentToken() != Token::Identifier)
+ BOOST_THROW_EXCEPTION(createParserError("Expected Identifier after ','"));
+ }
+
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrace);
+ return nodeFactory.createNode<EnumDefinition>(name, members);
+}
+
+ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
+ VarDeclParserOptions const& _options,
+ ASTPointer<TypeName> const& _lookAheadArrayType
+)
+{
+ ASTNodeFactory nodeFactory = _lookAheadArrayType ?
+ ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
+ ASTPointer<TypeName> type;
+ if (_lookAheadArrayType)
+ type = _lookAheadArrayType;
+ else
+ {
+ type = parseTypeName(_options.allowVar);
+ if (type != nullptr)
+ nodeFactory.setEndPositionFromNode(type);
+ }
+ bool isIndexed = false;
+ bool isDeclaredConst = false;
+ Declaration::Visibility visibility(Declaration::Visibility::Default);
+ VariableDeclaration::Location location = VariableDeclaration::Location::Default;
+ ASTPointer<ASTString> identifier;
+
+ while (true)
+ {
+ Token::Value token = m_scanner->getCurrentToken();
+ if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token))
+ {
+ if (visibility != Declaration::Visibility::Default)
+ BOOST_THROW_EXCEPTION(createParserError("Visibility already specified."));
+ visibility = parseVisibilitySpecifier(token);
+ }
+ else
+ {
+ if (_options.allowIndexed && token == Token::Indexed)
+ isIndexed = true;
+ else if (token == Token::Const)
+ isDeclaredConst = true;
+ else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token))
+ {
+ if (location != VariableDeclaration::Location::Default)
+ BOOST_THROW_EXCEPTION(createParserError("Location already specified."));
+ if (!type)
+ BOOST_THROW_EXCEPTION(createParserError("Location specifier needs explicit type name."));
+ location = (
+ token == Token::Memory ?
+ VariableDeclaration::Location::Memory :
+ VariableDeclaration::Location::Storage
+ );
+ }
+ else
+ break;
+ m_scanner->next();
+ }
+ }
+ nodeFactory.markEndPosition();
+
+ if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier)
+ {
+ identifier = make_shared<ASTString>("");
+ solAssert(type != nullptr, "");
+ nodeFactory.setEndPositionFromNode(type);
+ }
+ else
+ identifier = expectIdentifierToken();
+ ASTPointer<Expression> value;
+ if (_options.allowInitialValue)
+ {
+ if (m_scanner->getCurrentToken() == Token::Assign)
+ {
+ m_scanner->next();
+ value = parseExpression();
+ nodeFactory.setEndPositionFromNode(value);
+ }
+ }
+ return nodeFactory.createNode<VariableDeclaration>(
+ type,
+ identifier,
+ value,
+ visibility,
+ _options.isStateVariable,
+ isIndexed,
+ isDeclaredConst,
+ location
+ );
+}
+
+ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
+{
+ ScopeGuard resetModifierFlag([this]() { m_insideModifier = false; });
+ m_insideModifier = true;
+
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<ASTString> docstring;
+ if (m_scanner->getCurrentCommentLiteral() != "")
+ docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral());
+
+ expectToken(Token::Modifier);
+ ASTPointer<ASTString> name(expectIdentifierToken());
+ ASTPointer<ParameterList> parameters;
+ if (m_scanner->getCurrentToken() == Token::LParen)
+ {
+ VarDeclParserOptions options;
+ options.allowIndexed = true;
+ options.allowLocationSpecifier = true;
+ parameters = parseParameterList(options);
+ }
+ else
+ parameters = createEmptyParameterList();
+ ASTPointer<Block> block = parseBlock();
+ nodeFactory.setEndPositionFromNode(block);
+ return nodeFactory.createNode<ModifierDefinition>(name, docstring, parameters, block);
+}
+
+ASTPointer<EventDefinition> Parser::parseEventDefinition()
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<ASTString> docstring;
+ if (m_scanner->getCurrentCommentLiteral() != "")
+ docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral());
+
+ expectToken(Token::Event);
+ ASTPointer<ASTString> name(expectIdentifierToken());
+ ASTPointer<ParameterList> parameters;
+ if (m_scanner->getCurrentToken() == Token::LParen)
+ {
+ VarDeclParserOptions options;
+ options.allowIndexed = true;
+ parameters = parseParameterList(options);
+ }
+ else
+ parameters = createEmptyParameterList();
+ bool anonymous = false;
+ if (m_scanner->getCurrentToken() == Token::Anonymous)
+ {
+ anonymous = true;
+ m_scanner->next();
+ }
+ nodeFactory.markEndPosition();
+ expectToken(Token::Semicolon);
+ return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous);
+}
+
+ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<Identifier> name(parseIdentifier());
+ vector<ASTPointer<Expression>> arguments;
+ if (m_scanner->getCurrentToken() == Token::LParen)
+ {
+ m_scanner->next();
+ arguments = parseFunctionCallListArguments();
+ nodeFactory.markEndPosition();
+ expectToken(Token::RParen);
+ }
+ else
+ nodeFactory.setEndPositionFromNode(name);
+ return nodeFactory.createNode<ModifierInvocation>(name, arguments);
+}
+
+ASTPointer<Identifier> Parser::parseIdentifier()
+{
+ ASTNodeFactory nodeFactory(*this);
+ nodeFactory.markEndPosition();
+ return nodeFactory.createNode<Identifier>(expectIdentifierToken());
+}
+
+ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<TypeName> type;
+ Token::Value token = m_scanner->getCurrentToken();
+ if (Token::isElementaryTypeName(token))
+ {
+ type = ASTNodeFactory(*this).createNode<ElementaryTypeName>(token);
+ m_scanner->next();
+ }
+ else if (token == Token::Var)
+ {
+ if (!_allowVar)
+ BOOST_THROW_EXCEPTION(createParserError("Expected explicit type name."));
+ m_scanner->next();
+ }
+ else if (token == Token::Mapping)
+ type = parseMapping();
+ else if (token == Token::Identifier)
+ {
+ ASTNodeFactory nodeFactory(*this);
+ nodeFactory.markEndPosition();
+ type = nodeFactory.createNode<UserDefinedTypeName>(expectIdentifierToken());
+ }
+ else
+ BOOST_THROW_EXCEPTION(createParserError("Expected type name"));
+
+ if (type)
+ // Parse "[...]" postfixes for arrays.
+ while (m_scanner->getCurrentToken() == Token::LBrack)
+ {
+ m_scanner->next();
+ ASTPointer<Expression> length;
+ if (m_scanner->getCurrentToken() != Token::RBrack)
+ length = parseExpression();
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrack);
+ type = nodeFactory.createNode<ArrayTypeName>(type, length);
+ }
+ return type;
+}
+
+ASTPointer<Mapping> Parser::parseMapping()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::Mapping);
+ expectToken(Token::LParen);
+ if (!Token::isElementaryTypeName(m_scanner->getCurrentToken()))
+ BOOST_THROW_EXCEPTION(createParserError("Expected elementary type name for mapping key type"));
+ ASTPointer<ElementaryTypeName> keyType;
+ keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(m_scanner->getCurrentToken());
+ m_scanner->next();
+ expectToken(Token::Arrow);
+ bool const allowVar = false;
+ ASTPointer<TypeName> valueType = parseTypeName(allowVar);
+ nodeFactory.markEndPosition();
+ expectToken(Token::RParen);
+ return nodeFactory.createNode<Mapping>(keyType, valueType);
+}
+
+ASTPointer<ParameterList> Parser::parseParameterList(
+ VarDeclParserOptions const& _options,
+ bool _allowEmpty
+)
+{
+ ASTNodeFactory nodeFactory(*this);
+ vector<ASTPointer<VariableDeclaration>> parameters;
+ VarDeclParserOptions options(_options);
+ options.allowEmptyName = true;
+ expectToken(Token::LParen);
+ if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RParen)
+ {
+ parameters.push_back(parseVariableDeclaration(options));
+ while (m_scanner->getCurrentToken() != Token::RParen)
+ {
+ expectToken(Token::Comma);
+ parameters.push_back(parseVariableDeclaration(options));
+ }
+ }
+ nodeFactory.markEndPosition();
+ m_scanner->next();
+ return nodeFactory.createNode<ParameterList>(parameters);
+}
+
+ASTPointer<Block> Parser::parseBlock()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::LBrace);
+ vector<ASTPointer<Statement>> statements;
+ while (m_scanner->getCurrentToken() != Token::RBrace)
+ statements.push_back(parseStatement());
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrace);
+ return nodeFactory.createNode<Block>(statements);
+}
+
+ASTPointer<Statement> Parser::parseStatement()
+{
+ ASTPointer<Statement> statement;
+ switch (m_scanner->getCurrentToken())
+ {
+ case Token::If:
+ return parseIfStatement();
+ case Token::While:
+ return parseWhileStatement();
+ case Token::For:
+ return parseForStatement();
+ case Token::LBrace:
+ return parseBlock();
+ // starting from here, all statements must be terminated by a semicolon
+ case Token::Continue:
+ statement = ASTNodeFactory(*this).createNode<Continue>();
+ m_scanner->next();
+ break;
+ case Token::Break:
+ statement = ASTNodeFactory(*this).createNode<Break>();
+ m_scanner->next();
+ break;
+ case Token::Return:
+ {
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<Expression> expression;
+ if (m_scanner->next() != Token::Semicolon)
+ {
+ expression = parseExpression();
+ nodeFactory.setEndPositionFromNode(expression);
+ }
+ statement = nodeFactory.createNode<Return>(expression);
+ break;
+ }
+ case Token::Identifier:
+ if (m_insideModifier && m_scanner->getCurrentLiteral() == "_")
+ {
+ statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>();
+ m_scanner->next();
+ return statement;
+ }
+ // fall-through
+ default:
+ statement = parseSimpleStatement();
+ }
+ expectToken(Token::Semicolon);
+ return statement;
+}
+
+ASTPointer<IfStatement> Parser::parseIfStatement()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::If);
+ expectToken(Token::LParen);
+ ASTPointer<Expression> condition = parseExpression();
+ expectToken(Token::RParen);
+ ASTPointer<Statement> trueBody = parseStatement();
+ ASTPointer<Statement> falseBody;
+ if (m_scanner->getCurrentToken() == Token::Else)
+ {
+ m_scanner->next();
+ falseBody = parseStatement();
+ nodeFactory.setEndPositionFromNode(falseBody);
+ }
+ else
+ nodeFactory.setEndPositionFromNode(trueBody);
+ return nodeFactory.createNode<IfStatement>(condition, trueBody, falseBody);
+}
+
+ASTPointer<WhileStatement> Parser::parseWhileStatement()
+{
+ ASTNodeFactory nodeFactory(*this);
+ expectToken(Token::While);
+ expectToken(Token::LParen);
+ ASTPointer<Expression> condition = parseExpression();
+ expectToken(Token::RParen);
+ ASTPointer<Statement> body = parseStatement();
+ nodeFactory.setEndPositionFromNode(body);
+ return nodeFactory.createNode<WhileStatement>(condition, body);
+}
+
+ASTPointer<ForStatement> Parser::parseForStatement()
+{
+ ASTNodeFactory nodeFactory(*this);
+ ASTPointer<Statement> initExpression;
+ ASTPointer<Expression> conditionExpression;
+ ASTPointer<ExpressionStatement> loopExpression;
+ expectToken(Token::For);
+ expectToken(Token::LParen);
+
+ // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RParen?
+ if (m_scanner->getCurrentToken() != Token::Semicolon)
+ initExpression = parseSimpleStatement();
+ expectToken(Token::Semicolon);
+
+ if (m_scanner->getCurrentToken() != Token::Semicolon)
+ conditionExpression = parseExpression();
+ expectToken(Token::Semicolon);
+
+ if (m_scanner->getCurrentToken() != Token::RParen)
+ loopExpression = parseExpressionStatement();
+ expectToken(Token::RParen);
+
+ ASTPointer<Statement> body = parseStatement();
+ nodeFactory.setEndPositionFromNode(body);
+ return nodeFactory.createNode<ForStatement>(initExpression,
+ conditionExpression,
+ loopExpression,
+ body);
+}
+
+ASTPointer<Statement> Parser::parseSimpleStatement()
+{
+ // These two cases are very hard to distinguish:
+ // x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9;
+ // In the first case, x is a type name, in the second it is the name of a variable.
+ switch (peekStatementType())
+ {
+ case LookAheadInfo::VariableDeclarationStatement:
+ return parseVariableDeclarationStatement();
+ case LookAheadInfo::ExpressionStatement:
+ return parseExpressionStatement();
+ default:
+ break;
+ }
+
+ // At this point, we have '(Identifier|ElementaryTypeName) "["'.
+ // We parse '(Identifier|ElementaryTypeName) ( "[" Expression "]" )+' and then decide whether to hand this over
+ // to ExpressionStatement or create a VariableDeclarationStatement out of it.
+ ASTPointer<PrimaryExpression> primary;
+ if (m_scanner->getCurrentToken() == Token::Identifier)
+ primary = parseIdentifier();
+ else
+ {
+ primary = ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(m_scanner->getCurrentToken());
+ m_scanner->next();
+ }
+ vector<pair<ASTPointer<Expression>, SourceLocation>> indices;
+ solAssert(m_scanner->getCurrentToken() == Token::LBrack, "");
+ SourceLocation indexLocation = primary->getLocation();
+ do
+ {
+ expectToken(Token::LBrack);
+ ASTPointer<Expression> index;
+ if (m_scanner->getCurrentToken() != Token::RBrack)
+ index = parseExpression();
+ indexLocation.end = getEndPosition();
+ indices.push_back(make_pair(index, indexLocation));
+ expectToken(Token::RBrack);
+ }
+ while (m_scanner->getCurrentToken() == Token::LBrack);
+
+ if (m_scanner->getCurrentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->getCurrentToken()))
+ return parseVariableDeclarationStatement(typeNameIndexAccessStructure(primary, indices));
+ else
+ return parseExpressionStatement(expressionFromIndexAccessStructure(primary, indices));
+}
+
+ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStatement(
+ ASTPointer<TypeName> const& _lookAheadArrayType)
+{
+ VarDeclParserOptions options;
+ options.allowVar = true;
+ options.allowInitialValue = true;
+ options.allowLocationSpecifier = true;
+ ASTPointer<VariableDeclaration> variable = parseVariableDeclaration(options, _lookAheadArrayType);
+ ASTNodeFactory nodeFactory(*this, variable);
+ return nodeFactory.createNode<VariableDeclarationStatement>(variable);
+}
+
+ASTPointer<ExpressionStatement> Parser::parseExpressionStatement(
+ ASTPointer<Expression> const& _lookAheadIndexAccessStructure)
+{
+ ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure);
+ return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(expression);
+}
+
+ASTPointer<Expression> Parser::parseExpression(
+ ASTPointer<Expression> const& _lookAheadIndexAccessStructure)
+{
+ ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure);
+ if (!Token::isAssignmentOp(m_scanner->getCurrentToken()))
+ return expression;
+ Token::Value assignmentOperator = expectAssignmentOperator();
+ ASTPointer<Expression> rightHandSide = parseExpression();
+ ASTNodeFactory nodeFactory(*this, expression);
+ nodeFactory.setEndPositionFromNode(rightHandSide);
+ return nodeFactory.createNode<Assignment>(expression, assignmentOperator, rightHandSide);
+}
+
+ASTPointer<Expression> Parser::parseBinaryExpression(int _minPrecedence,
+ ASTPointer<Expression> const& _lookAheadIndexAccessStructure)
+{
+ ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure);
+ ASTNodeFactory nodeFactory(*this, expression);
+ int precedence = Token::precedence(m_scanner->getCurrentToken());
+ for (; precedence >= _minPrecedence; --precedence)
+ while (Token::precedence(m_scanner->getCurrentToken()) == precedence)
+ {
+ Token::Value op = m_scanner->getCurrentToken();
+ m_scanner->next();
+ ASTPointer<Expression> right = parseBinaryExpression(precedence + 1);
+ nodeFactory.setEndPositionFromNode(right);
+ expression = nodeFactory.createNode<BinaryOperation>(expression, op, right);
+ }
+ return expression;
+}
+
+ASTPointer<Expression> Parser::parseUnaryExpression(
+ ASTPointer<Expression> const& _lookAheadIndexAccessStructure)
+{
+ ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
+ ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
+ Token::Value token = m_scanner->getCurrentToken();
+ if (!_lookAheadIndexAccessStructure && (Token::isUnaryOp(token) || Token::isCountOp(token)))
+ {
+ // prefix expression
+ m_scanner->next();
+ ASTPointer<Expression> subExpression = parseUnaryExpression();
+ nodeFactory.setEndPositionFromNode(subExpression);
+ return nodeFactory.createNode<UnaryOperation>(token, subExpression, true);
+ }
+ else
+ {
+ // potential postfix expression
+ ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_lookAheadIndexAccessStructure);
+ token = m_scanner->getCurrentToken();
+ if (!Token::isCountOp(token))
+ return subExpression;
+ nodeFactory.markEndPosition();
+ m_scanner->next();
+ return nodeFactory.createNode<UnaryOperation>(token, subExpression, false);
+ }
+}
+
+ASTPointer<Expression> Parser::parseLeftHandSideExpression(
+ ASTPointer<Expression> const& _lookAheadIndexAccessStructure)
+{
+ ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
+ ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
+
+ ASTPointer<Expression> expression;
+ if (_lookAheadIndexAccessStructure)
+ expression = _lookAheadIndexAccessStructure;
+ else if (m_scanner->getCurrentToken() == Token::New)
+ {
+ expectToken(Token::New);
+ ASTPointer<Identifier> contractName(parseIdentifier());
+ nodeFactory.setEndPositionFromNode(contractName);
+ expression = nodeFactory.createNode<NewExpression>(contractName);
+ }
+ else
+ expression = parsePrimaryExpression();
+
+ while (true)
+ {
+ switch (m_scanner->getCurrentToken())
+ {
+ case Token::LBrack:
+ {
+ m_scanner->next();
+ ASTPointer<Expression> index;
+ if (m_scanner->getCurrentToken() != Token::RBrack)
+ index = parseExpression();
+ nodeFactory.markEndPosition();
+ expectToken(Token::RBrack);
+ expression = nodeFactory.createNode<IndexAccess>(expression, index);
+ }
+ break;
+ case Token::Period:
+ {
+ m_scanner->next();
+ nodeFactory.markEndPosition();
+ expression = nodeFactory.createNode<MemberAccess>(expression, expectIdentifierToken());
+ }
+ break;
+ case Token::LParen:
+ {
+ m_scanner->next();
+ vector<ASTPointer<Expression>> arguments;
+ vector<ASTPointer<ASTString>> names;
+ std::tie(arguments, names) = parseFunctionCallArguments();
+ nodeFactory.markEndPosition();
+ expectToken(Token::RParen);
+ expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names);
+ }
+ break;
+ default:
+ return expression;
+ }
+ }
+}
+
+ASTPointer<Expression> Parser::parsePrimaryExpression()
+{
+ ASTNodeFactory nodeFactory(*this);
+ Token::Value token = m_scanner->getCurrentToken();
+ ASTPointer<Expression> expression;
+ switch (token)
+ {
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
+ expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
+ break;
+ case Token::Number:
+ if (Token::isEtherSubdenomination(m_scanner->peekNextToken()))
+ {
+ ASTPointer<ASTString> literal = getLiteralAndAdvance();
+ nodeFactory.markEndPosition();
+ Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->getCurrentToken());
+ m_scanner->next();
+ expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
+ break;
+ }
+ if (Token::isTimeSubdenomination(m_scanner->peekNextToken()))
+ {
+ ASTPointer<ASTString> literal = getLiteralAndAdvance();
+ nodeFactory.markEndPosition();
+ Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->getCurrentToken());
+ m_scanner->next();
+ expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
+ break;
+ }
+ // fall-through
+ case Token::StringLiteral:
+ nodeFactory.markEndPosition();
+ expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
+ break;
+ case Token::Identifier:
+ nodeFactory.markEndPosition();
+ expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());
+ break;
+ case Token::LParen:
+ {
+ m_scanner->next();
+ ASTPointer<Expression> expression = parseExpression();
+ expectToken(Token::RParen);
+ return expression;
+ }
+ default:
+ if (Token::isElementaryTypeName(token))
+ {
+ // used for casts
+ expression = nodeFactory.createNode<ElementaryTypeNameExpression>(token);
+ m_scanner->next();
+ }
+ else
+ BOOST_THROW_EXCEPTION(createParserError("Expected primary expression."));
+ break;
+ }
+ return expression;
+}
+
+vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments()
+{
+ vector<ASTPointer<Expression>> arguments;
+ if (m_scanner->getCurrentToken() != Token::RParen)
+ {
+ arguments.push_back(parseExpression());
+ while (m_scanner->getCurrentToken() != Token::RParen)
+ {
+ expectToken(Token::Comma);
+ arguments.push_back(parseExpression());
+ }
+ }
+ return arguments;
+}
+
+pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::parseFunctionCallArguments()
+{
+ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> ret;
+ Token::Value token = m_scanner->getCurrentToken();
+ if (token == Token::LBrace)
+ {
+ // call({arg1 : 1, arg2 : 2 })
+ expectToken(Token::LBrace);
+ while (m_scanner->getCurrentToken() != Token::RBrace)
+ {
+ ret.second.push_back(expectIdentifierToken());
+ expectToken(Token::Colon);
+ ret.first.push_back(parseExpression());
+
+ if (m_scanner->getCurrentToken() == Token::Comma)
+ expectToken(Token::Comma);
+ else
+ break;
+ }
+ expectToken(Token::RBrace);
+ }
+ else
+ ret.first = parseFunctionCallListArguments();
+ return ret;
+}
+
+Parser::LookAheadInfo Parser::peekStatementType() const
+{
+ // Distinguish between variable declaration (and potentially assignment) and expression statement
+ // (which include assignments to other expressions and pre-declared variables).
+ // We have a variable declaration if we get a keyword that specifies a type name.
+ // If it is an identifier or an elementary type name followed by an identifier, we also have
+ // a variable declaration.
+ // If we get an identifier followed by a "[", it can be both ("type[9] a;" or "arr[9] = 7;").
+ // In all other cases, we have an expression statement.
+ Token::Value token(m_scanner->getCurrentToken());
+ bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
+
+ if (token == Token::Mapping || token == Token::Var)
+ return LookAheadInfo::VariableDeclarationStatement;
+ if (mightBeTypeName)
+ {
+ Token::Value next = m_scanner->peekNextToken();
+ if (next == Token::Identifier || Token::isLocationSpecifier(next))
+ return LookAheadInfo::VariableDeclarationStatement;
+ if (m_scanner->peekNextToken() == Token::LBrack)
+ return LookAheadInfo::IndexAccessStructure;
+ }
+ return LookAheadInfo::ExpressionStatement;
+}
+
+ASTPointer<TypeName> Parser::typeNameIndexAccessStructure(
+ ASTPointer<PrimaryExpression> const& _primary, vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices)
+{
+ ASTNodeFactory nodeFactory(*this, _primary);
+ ASTPointer<TypeName> type;
+ if (auto identifier = dynamic_cast<Identifier const*>(_primary.get()))
+ type = nodeFactory.createNode<UserDefinedTypeName>(make_shared<ASTString>(identifier->getName()));
+ else if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_primary.get()))
+ type = nodeFactory.createNode<ElementaryTypeName>(typeName->getTypeToken());
+ else
+ solAssert(false, "Invalid type name for array look-ahead.");
+ for (auto const& lengthExpression: _indices)
+ {
+ nodeFactory.setLocation(lengthExpression.second);
+ type = nodeFactory.createNode<ArrayTypeName>(type, lengthExpression.first);
+ }
+ return type;
+}
+
+ASTPointer<Expression> Parser::expressionFromIndexAccessStructure(
+ ASTPointer<PrimaryExpression> const& _primary, vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices)
+{
+ ASTNodeFactory nodeFactory(*this, _primary);
+ ASTPointer<Expression> expression(_primary);
+ for (auto const& index: _indices)
+ {
+ nodeFactory.setLocation(index.second);
+ expression = nodeFactory.createNode<IndexAccess>(expression, index.first);
+ }
+ return expression;
+}
+
+void Parser::expectToken(Token::Value _value)
+{
+ if (m_scanner->getCurrentToken() != _value)
+ BOOST_THROW_EXCEPTION(createParserError(string("Expected token ") + string(Token::getName(_value))));
+ m_scanner->next();
+}
+
+Token::Value Parser::expectAssignmentOperator()
+{
+ Token::Value op = m_scanner->getCurrentToken();
+ if (!Token::isAssignmentOp(op))
+ BOOST_THROW_EXCEPTION(createParserError("Expected assignment operator"));
+ m_scanner->next();
+ return op;
+}
+
+ASTPointer<ASTString> Parser::expectIdentifierToken()
+{
+ if (m_scanner->getCurrentToken() != Token::Identifier)
+ BOOST_THROW_EXCEPTION(createParserError("Expected identifier"));
+ return getLiteralAndAdvance();
+}
+
+ASTPointer<ASTString> Parser::getLiteralAndAdvance()
+{
+ ASTPointer<ASTString> identifier = make_shared<ASTString>(m_scanner->getCurrentLiteral());
+ m_scanner->next();
+ return identifier;
+}
+
+ASTPointer<ParameterList> Parser::createEmptyParameterList()
+{
+ ASTNodeFactory nodeFactory(*this);
+ nodeFactory.setLocationEmpty();
+ return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>());
+}
+
+ParserError Parser::createParserError(string const& _description) const
+{
+ return ParserError() << errinfo_sourceLocation(SourceLocation(getPosition(), getPosition(), getSourceName()))
+ << errinfo_comment(_description);
+}
+
+
+}
+}
diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h
new file mode 100644
index 00000000..d667aa3e
--- /dev/null
+++ b/libsolidity/Parser.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp
new file mode 100644
index 00000000..fbe3ea97
--- /dev/null
+++ b/libsolidity/Scanner.cpp
@@ -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);
+}
+
+
+}
+}
diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h
new file mode 100644
index 00000000..43fcd133
--- /dev/null
+++ b/libsolidity/Scanner.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/SourceReferenceFormatter.cpp b/libsolidity/SourceReferenceFormatter.cpp
new file mode 100644
index 00000000..77805efc
--- /dev/null
+++ b/libsolidity/SourceReferenceFormatter.cpp
@@ -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;
+ }
+}
+
+}
+}
diff --git a/libsolidity/SourceReferenceFormatter.h b/libsolidity/SourceReferenceFormatter.h
new file mode 100644
index 00000000..dd258c27
--- /dev/null
+++ b/libsolidity/SourceReferenceFormatter.h
@@ -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);
+};
+
+}
+}
diff --git a/libsolidity/Token.cpp b/libsolidity/Token.cpp
new file mode 100644
index 00000000..d07d7502
--- /dev/null
+++ b/libsolidity/Token.cpp
@@ -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
+
+}
+}
diff --git a/libsolidity/Token.h b/libsolidity/Token.h
new file mode 100644
index 00000000..7c7b1c7f
--- /dev/null
+++ b/libsolidity/Token.h
@@ -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];
+};
+
+}
+}
diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp
new file mode 100644
index 00000000..bda68356
--- /dev/null
+++ b/libsolidity/Types.cpp
@@ -0,0 +1,1758 @@
+/*
+ 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
+ */
+
+#include <libsolidity/Types.h>
+#include <limits>
+#include <boost/range/adaptor/reversed.hpp>
+#include <libdevcore/CommonIO.h>
+#include <libdevcore/CommonData.h>
+#include <libdevcore/SHA3.h>
+#include <libsolidity/Utils.h>
+#include <libsolidity/AST.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+void StorageOffsets::computeOffsets(TypePointers const& _types)
+{
+ bigint slotOffset = 0;
+ unsigned byteOffset = 0;
+ map<size_t, pair<u256, unsigned>> offsets;
+ for (size_t i = 0; i < _types.size(); ++i)
+ {
+ TypePointer const& type = _types[i];
+ if (!type->canBeStored())
+ continue;
+ if (byteOffset + type->getStorageBytes() > 32)
+ {
+ // would overflow, go to next slot
+ ++slotOffset;
+ byteOffset = 0;
+ }
+ if (slotOffset >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
+ offsets[i] = make_pair(u256(slotOffset), byteOffset);
+ solAssert(type->getStorageSize() >= 1, "Invalid storage size.");
+ if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32)
+ byteOffset += type->getStorageBytes();
+ else
+ {
+ slotOffset += type->getStorageSize();
+ byteOffset = 0;
+ }
+ }
+ if (byteOffset > 0)
+ ++slotOffset;
+ if (slotOffset >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
+ m_storageSize = u256(slotOffset);
+ swap(m_offsets, offsets);
+}
+
+pair<u256, unsigned> const* StorageOffsets::getOffset(size_t _index) const
+{
+ if (m_offsets.count(_index))
+ return &m_offsets.at(_index);
+ else
+ return nullptr;
+}
+
+MemberList& MemberList::operator=(MemberList&& _other)
+{
+ assert(&_other != this);
+
+ m_memberTypes = std::move(_other.m_memberTypes);
+ m_storageOffsets = std::move(_other.m_storageOffsets);
+ return *this;
+}
+
+std::pair<u256, unsigned> const* MemberList::getMemberStorageOffset(string const& _name) const
+{
+ if (!m_storageOffsets)
+ {
+ TypePointers memberTypes;
+ memberTypes.reserve(m_memberTypes.size());
+ for (auto const& member: m_memberTypes)
+ memberTypes.push_back(member.type);
+ m_storageOffsets.reset(new StorageOffsets());
+ m_storageOffsets->computeOffsets(memberTypes);
+ }
+ for (size_t index = 0; index < m_memberTypes.size(); ++index)
+ if (m_memberTypes[index].name == _name)
+ return m_storageOffsets->getOffset(index);
+ return nullptr;
+}
+
+u256 const& MemberList::getStorageSize() const
+{
+ // trigger lazy computation
+ getMemberStorageOffset("");
+ return m_storageOffsets->getStorageSize();
+}
+
+TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
+{
+ char const* tokenCstr = Token::toString(_typeToken);
+ solAssert(Token::isElementaryTypeName(_typeToken),
+ "Expected an elementary type name but got " + ((tokenCstr) ? std::string(Token::toString(_typeToken)) : ""));
+
+ if (Token::Int <= _typeToken && _typeToken <= Token::Bytes32)
+ {
+ int offset = _typeToken - Token::Int;
+ int bytes = offset % 33;
+ if (bytes == 0 && _typeToken != Token::Bytes1)
+ bytes = 32;
+ int modifier = offset / 33;
+ switch(modifier)
+ {
+ case 0:
+ return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Signed);
+ case 1:
+ return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Unsigned);
+ case 2:
+ return make_shared<FixedBytesType>(bytes + 1);
+ default:
+ solAssert(false, "Unexpected modifier value. Should never happen");
+ return TypePointer();
+ }
+ }
+ else if (_typeToken == Token::Byte)
+ return make_shared<FixedBytesType>(1);
+ else if (_typeToken == Token::Address)
+ return make_shared<IntegerType>(0, IntegerType::Modifier::Address);
+ else if (_typeToken == Token::Bool)
+ return make_shared<BoolType>();
+ else if (_typeToken == Token::Bytes)
+ return make_shared<ArrayType>(DataLocation::Storage);
+ else if (_typeToken == Token::String)
+ return make_shared<ArrayType>(DataLocation::Storage, true);
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment(
+ "Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type."
+ ));
+}
+
+TypePointer Type::fromElementaryTypeName(string const& _name)
+{
+ return fromElementaryTypeName(Token::fromIdentifierOrKeyword(_name));
+}
+
+TypePointer Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName)
+{
+ Declaration const* declaration = _typeName.getReferencedDeclaration();
+ if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
+ return make_shared<StructType>(*structDef);
+ else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
+ return make_shared<EnumType>(*enumDef);
+ else if (FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(declaration))
+ return make_shared<FunctionType>(*function);
+ else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
+ return make_shared<ContractType>(*contract);
+ return TypePointer();
+}
+
+TypePointer Type::fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType)
+{
+ TypePointer keyType = _keyType.toType();
+ if (!keyType)
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name."));
+ TypePointer valueType = _valueType.toType();
+ if (!valueType)
+ BOOST_THROW_EXCEPTION(_valueType.createTypeError("Invalid type name."));
+ // Convert value type to storage reference.
+ valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType);
+ // Convert key type to memory.
+ keyType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, keyType);
+ return make_shared<MappingType>(keyType, valueType);
+}
+
+TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length)
+{
+ TypePointer baseType = _baseTypeName.toType();
+ if (!baseType)
+ BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name."));
+ if (baseType->getStorageBytes() == 0)
+ BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Illegal base type of storage size zero for array."));
+ if (_length)
+ {
+ if (!_length->getType())
+ _length->checkTypeRequirements(nullptr);
+ auto const* length = dynamic_cast<IntegerConstantType const*>(_length->getType().get());
+ if (!length)
+ BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length."));
+ return make_shared<ArrayType>(DataLocation::Storage, baseType, length->literalValue(nullptr));
+ }
+ else
+ return make_shared<ArrayType>(DataLocation::Storage, baseType);
+}
+
+TypePointer Type::forLiteral(Literal const& _literal)
+{
+ switch (_literal.getToken())
+ {
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
+ return make_shared<BoolType>();
+ case Token::Number:
+ if (!IntegerConstantType::isValidLiteral(_literal))
+ return TypePointer();
+ return make_shared<IntegerConstantType>(_literal);
+ case Token::StringLiteral:
+ return make_shared<StringLiteralType>(_literal);
+ default:
+ return shared_ptr<Type>();
+ }
+}
+
+TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b)
+{
+ if (_b->isImplicitlyConvertibleTo(*_a))
+ return _a;
+ else if (_a->isImplicitlyConvertibleTo(*_b))
+ return _b;
+ else
+ return TypePointer();
+}
+
+const MemberList Type::EmptyMemberList;
+
+IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
+ m_bits(_bits), m_modifier(_modifier)
+{
+ if (isAddress())
+ m_bits = 160;
+ solAssert(m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
+ "Invalid bit number for integer type: " + dev::toString(_bits));
+}
+
+bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (_convertTo.getCategory() != getCategory())
+ return false;
+ IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
+ if (convertTo.m_bits < m_bits)
+ return false;
+ if (isAddress())
+ return convertTo.isAddress();
+ else if (isSigned())
+ return convertTo.isSigned();
+ else
+ return !convertTo.isSigned() || convertTo.m_bits > m_bits;
+}
+
+bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ return _convertTo.getCategory() == getCategory() ||
+ _convertTo.getCategory() == Category::Contract ||
+ _convertTo.getCategory() == Category::Enum ||
+ _convertTo.getCategory() == Category::FixedBytes;
+}
+
+TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
+{
+ // "delete" is ok for all integer types
+ if (_operator == Token::Delete)
+ return make_shared<VoidType>();
+ // no further unary operators for addresses
+ else if (isAddress())
+ return TypePointer();
+ // for non-address integers, we allow +, -, ++ and --
+ else if (_operator == Token::Add || _operator == Token::Sub ||
+ _operator == Token::Inc || _operator == Token::Dec ||
+ _operator == Token::After || _operator == Token::BitNot)
+ return shared_from_this();
+ else
+ return TypePointer();
+}
+
+bool IntegerType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ IntegerType const& other = dynamic_cast<IntegerType const&>(_other);
+ return other.m_bits == m_bits && other.m_modifier == m_modifier;
+}
+
+string IntegerType::toString(bool) const
+{
+ if (isAddress())
+ return "address";
+ string prefix = isSigned() ? "int" : "uint";
+ return prefix + dev::toString(m_bits);
+}
+
+TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ if (_other->getCategory() != Category::IntegerConstant && _other->getCategory() != getCategory())
+ return TypePointer();
+ auto commonType = dynamic_pointer_cast<IntegerType const>(Type::commonType(shared_from_this(), _other));
+
+ if (!commonType)
+ return TypePointer();
+
+ // All integer types can be compared
+ if (Token::isCompareOp(_operator))
+ return commonType;
+ if (Token::isBooleanOp(_operator))
+ return TypePointer();
+ // Nothing else can be done with addresses
+ if (commonType->isAddress())
+ return TypePointer();
+
+ return commonType;
+}
+
+const MemberList IntegerType::AddressMemberList({
+ {"balance", make_shared<IntegerType >(256)},
+ {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true)},
+ {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true)},
+ {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}
+});
+
+bool IntegerConstantType::isValidLiteral(const Literal& _literal)
+{
+ try
+ {
+ bigint x(_literal.getValue());
+ }
+ catch (...)
+ {
+ return false;
+ }
+ return true;
+}
+
+IntegerConstantType::IntegerConstantType(Literal const& _literal)
+{
+ m_value = bigint(_literal.getValue());
+
+ switch (_literal.getSubDenomination())
+ {
+ case Literal::SubDenomination::Wei:
+ case Literal::SubDenomination::Second:
+ case Literal::SubDenomination::None:
+ break;
+ case Literal::SubDenomination::Szabo:
+ m_value *= bigint("1000000000000");
+ break;
+ case Literal::SubDenomination::Finney:
+ m_value *= bigint("1000000000000000");
+ break;
+ case Literal::SubDenomination::Ether:
+ m_value *= bigint("1000000000000000000");
+ break;
+ case Literal::SubDenomination::Minute:
+ m_value *= bigint("60");
+ break;
+ case Literal::SubDenomination::Hour:
+ m_value *= bigint("3600");
+ break;
+ case Literal::SubDenomination::Day:
+ m_value *= bigint("86400");
+ break;
+ case Literal::SubDenomination::Week:
+ m_value *= bigint("604800");
+ break;
+ case Literal::SubDenomination::Year:
+ m_value *= bigint("31536000");
+ break;
+ }
+}
+
+bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (auto targetType = dynamic_cast<IntegerType const*>(&_convertTo))
+ {
+ if (m_value == 0)
+ return true;
+ int forSignBit = (targetType->isSigned() ? 1 : 0);
+ if (m_value > 0)
+ {
+ if (m_value <= (u256(-1) >> (256 - targetType->getNumBits() + forSignBit)))
+ return true;
+ }
+ else if (targetType->isSigned() && -m_value <= (u256(1) << (targetType->getNumBits() - forSignBit)))
+ return true;
+ return false;
+ }
+ else if (_convertTo.getCategory() == Category::FixedBytes)
+ {
+ FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo);
+ return fixedBytes.numBytes() * 8 >= getIntegerType()->getNumBits();
+ }
+ else
+ return false;
+}
+
+bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ TypePointer integerType = getIntegerType();
+ return integerType && integerType->isExplicitlyConvertibleTo(_convertTo);
+}
+
+TypePointer IntegerConstantType::unaryOperatorResult(Token::Value _operator) const
+{
+ bigint value;
+ switch (_operator)
+ {
+ case Token::BitNot:
+ value = ~m_value;
+ break;
+ case Token::Add:
+ value = m_value;
+ break;
+ case Token::Sub:
+ value = -m_value;
+ break;
+ case Token::After:
+ return shared_from_this();
+ default:
+ return TypePointer();
+ }
+ return make_shared<IntegerConstantType>(value);
+}
+
+TypePointer IntegerConstantType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ if (_other->getCategory() == Category::Integer)
+ {
+ shared_ptr<IntegerType const> integerType = getIntegerType();
+ if (!integerType)
+ return TypePointer();
+ return integerType->binaryOperatorResult(_operator, _other);
+ }
+ else if (_other->getCategory() != getCategory())
+ return TypePointer();
+
+ IntegerConstantType const& other = dynamic_cast<IntegerConstantType const&>(*_other);
+ if (Token::isCompareOp(_operator))
+ {
+ shared_ptr<IntegerType const> thisIntegerType = getIntegerType();
+ shared_ptr<IntegerType const> otherIntegerType = other.getIntegerType();
+ if (!thisIntegerType || !otherIntegerType)
+ return TypePointer();
+ return thisIntegerType->binaryOperatorResult(_operator, otherIntegerType);
+ }
+ else
+ {
+ bigint value;
+ switch (_operator)
+ {
+ case Token::BitOr:
+ value = m_value | other.m_value;
+ break;
+ case Token::BitXor:
+ value = m_value ^ other.m_value;
+ break;
+ case Token::BitAnd:
+ value = m_value & other.m_value;
+ break;
+ case Token::Add:
+ value = m_value + other.m_value;
+ break;
+ case Token::Sub:
+ value = m_value - other.m_value;
+ break;
+ case Token::Mul:
+ value = m_value * other.m_value;
+ break;
+ case Token::Div:
+ if (other.m_value == 0)
+ return TypePointer();
+ value = m_value / other.m_value;
+ break;
+ case Token::Mod:
+ if (other.m_value == 0)
+ return TypePointer();
+ value = m_value % other.m_value;
+ break;
+ case Token::Exp:
+ if (other.m_value < 0)
+ return TypePointer();
+ else if (other.m_value > std::numeric_limits<unsigned int>::max())
+ return TypePointer();
+ else
+ value = boost::multiprecision::pow(m_value, other.m_value.convert_to<unsigned int>());
+ break;
+ default:
+ return TypePointer();
+ }
+ return make_shared<IntegerConstantType>(value);
+ }
+}
+
+bool IntegerConstantType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ return m_value == dynamic_cast<IntegerConstantType const&>(_other).m_value;
+}
+
+string IntegerConstantType::toString(bool) const
+{
+ return "int_const " + m_value.str();
+}
+
+u256 IntegerConstantType::literalValue(Literal const*) const
+{
+ u256 value;
+ // we ignore the literal and hope that the type was correctly determined
+ solAssert(m_value <= u256(-1), "Integer constant too large.");
+ solAssert(m_value >= -(bigint(1) << 255), "Integer constant too small.");
+
+ if (m_value >= 0)
+ value = u256(m_value);
+ else
+ value = s2u(s256(m_value));
+
+ return value;
+}
+
+TypePointer IntegerConstantType::mobileType() const
+{
+ auto intType = getIntegerType();
+ solAssert(!!intType, "mobileType called with invalid integer constant " + toString(false));
+ return intType;
+}
+
+shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const
+{
+ bigint value = m_value;
+ bool negative = (value < 0);
+ if (negative) // convert to positive number of same bit requirements
+ value = ((-value) - 1) << 1;
+ if (value > u256(-1))
+ return shared_ptr<IntegerType const>();
+ else
+ return make_shared<IntegerType>(
+ max(bytesRequired(value), 1u) * 8,
+ negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned
+ );
+}
+
+StringLiteralType::StringLiteralType(Literal const& _literal):
+ m_value(_literal.getValue())
+{
+}
+
+bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (auto fixedBytes = dynamic_cast<FixedBytesType const*>(&_convertTo))
+ return size_t(fixedBytes->numBytes()) >= m_value.size();
+ else if (auto arrayType = dynamic_cast<ArrayType const*>(&_convertTo))
+ return arrayType->isByteArray();
+ else
+ return false;
+}
+
+bool StringLiteralType::operator==(const Type& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ return m_value == dynamic_cast<StringLiteralType const&>(_other).m_value;
+}
+
+TypePointer StringLiteralType::mobileType() const
+{
+ return make_shared<ArrayType>(DataLocation::Memory, true);
+}
+
+shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
+{
+ if (_literal.length() <= 32)
+ return make_shared<FixedBytesType>(_literal.length());
+ return shared_ptr<FixedBytesType>();
+}
+
+FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes)
+{
+ solAssert(m_bytes >= 0 && m_bytes <= 32,
+ "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes));
+}
+
+bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (_convertTo.getCategory() != getCategory())
+ return false;
+ FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
+ return convertTo.m_bytes >= m_bytes;
+}
+
+bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ return _convertTo.getCategory() == Category::Integer ||
+ _convertTo.getCategory() == Category::Contract ||
+ _convertTo.getCategory() == getCategory();
+}
+
+TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const
+{
+ // "delete" and "~" is okay for FixedBytesType
+ if (_operator == Token::Delete)
+ return make_shared<VoidType>();
+ else if (_operator == Token::BitNot)
+ return shared_from_this();
+
+ return TypePointer();
+}
+
+TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other));
+ if (!commonType)
+ return TypePointer();
+
+ // FixedBytes can be compared and have bitwise operators applied to them
+ if (Token::isCompareOp(_operator) || Token::isBitOp(_operator))
+ return commonType;
+
+ return TypePointer();
+}
+
+bool FixedBytesType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ FixedBytesType const& other = dynamic_cast<FixedBytesType const&>(_other);
+ return other.m_bytes == m_bytes;
+}
+
+bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ // conversion to integer is fine, but not to address
+ // this is an example of explicit conversions being not transitive (though implicit should be)
+ if (_convertTo.getCategory() == getCategory())
+ {
+ IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
+ if (!convertTo.isAddress())
+ return true;
+ }
+ return isImplicitlyConvertibleTo(_convertTo);
+}
+
+u256 BoolType::literalValue(Literal const* _literal) const
+{
+ solAssert(_literal, "");
+ if (_literal->getToken() == Token::TrueLiteral)
+ return u256(1);
+ else if (_literal->getToken() == Token::FalseLiteral)
+ return u256(0);
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal."));
+}
+
+TypePointer BoolType::unaryOperatorResult(Token::Value _operator) const
+{
+ if (_operator == Token::Delete)
+ return make_shared<VoidType>();
+ return (_operator == Token::Not) ? shared_from_this() : TypePointer();
+}
+
+TypePointer BoolType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ if (getCategory() != _other->getCategory())
+ return TypePointer();
+ if (Token::isCompareOp(_operator) || _operator == Token::And || _operator == Token::Or)
+ return _other;
+ else
+ return TypePointer();
+}
+
+bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (*this == _convertTo)
+ return true;
+ if (_convertTo.getCategory() == Category::Integer)
+ return dynamic_cast<IntegerType const&>(_convertTo).isAddress();
+ if (_convertTo.getCategory() == Category::Contract)
+ {
+ auto const& bases = getContractDefinition().getLinearizedBaseContracts();
+ if (m_super && bases.size() <= 1)
+ return false;
+ return find(m_super ? ++bases.begin() : bases.begin(), bases.end(),
+ &dynamic_cast<ContractType const&>(_convertTo).getContractDefinition()) != bases.end();
+ }
+ return false;
+}
+
+bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ return isImplicitlyConvertibleTo(_convertTo) || _convertTo.getCategory() == Category::Integer ||
+ _convertTo.getCategory() == Category::Contract;
+}
+
+TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
+{
+ return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer();
+}
+
+TypePointer ReferenceType::unaryOperatorResult(Token::Value _operator) const
+{
+ if (_operator != Token::Delete)
+ return TypePointer();
+ // delete can be used on everything except calldata references or storage pointers
+ // (storage references are ok)
+ switch (location())
+ {
+ case DataLocation::CallData:
+ return TypePointer();
+ case DataLocation::Memory:
+ return make_shared<VoidType>();
+ case DataLocation::Storage:
+ return m_isPointer ? TypePointer() : make_shared<VoidType>();
+ default:
+ solAssert(false, "");
+ }
+ return TypePointer();
+}
+
+TypePointer ReferenceType::copyForLocationIfReference(DataLocation _location, TypePointer const& _type)
+{
+ if (auto type = dynamic_cast<ReferenceType const*>(_type.get()))
+ return type->copyForLocation(_location, false);
+ return _type;
+}
+
+TypePointer ReferenceType::copyForLocationIfReference(TypePointer const& _type) const
+{
+ return copyForLocationIfReference(m_location, _type);
+}
+
+string ReferenceType::stringForReferencePart() const
+{
+ switch (m_location)
+ {
+ case DataLocation::Storage:
+ return string("storage ") + (m_isPointer ? "pointer" : "ref");
+ case DataLocation::CallData:
+ return "calldata";
+ case DataLocation::Memory:
+ return "memory";
+ }
+ solAssert(false, "");
+ return "";
+}
+
+bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
+{
+ if (_convertTo.getCategory() != getCategory())
+ return false;
+ auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
+ if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString())
+ return false;
+ // memory/calldata to storage can be converted, but only to a direct storage reference
+ if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer())
+ return false;
+ if (convertTo.location() == DataLocation::CallData && location() != convertTo.location())
+ return false;
+ if (convertTo.location() == DataLocation::Storage && !convertTo.isPointer())
+ {
+ // Less restrictive conversion, since we need to copy anyway.
+ if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType()))
+ return false;
+ if (convertTo.isDynamicallySized())
+ return true;
+ return !isDynamicallySized() && convertTo.getLength() >= getLength();
+ }
+ else
+ {
+ // Conversion to storage pointer or to memory, we de not copy element-for-element here, so
+ // require that the base type is the same, not only convertible.
+ // This disallows assignment of nested dynamic arrays from storage to memory for now.
+ if (
+ *copyForLocationIfReference(location(), getBaseType()) !=
+ *copyForLocationIfReference(location(), convertTo.getBaseType())
+ )
+ return false;
+ if (isDynamicallySized() != convertTo.isDynamicallySized())
+ return false;
+ // We also require that the size is the same.
+ if (!isDynamicallySized() && getLength() != convertTo.getLength())
+ return false;
+ return true;
+ }
+}
+
+bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const
+{
+ if (isImplicitlyConvertibleTo(_convertTo))
+ return true;
+ // allow conversion bytes <-> string
+ if (_convertTo.getCategory() != getCategory())
+ return false;
+ auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
+ if (convertTo.location() != location())
+ return false;
+ if (!isByteArray() || !convertTo.isByteArray())
+ return false;
+ return true;
+}
+
+bool ArrayType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ ArrayType const& other = dynamic_cast<ArrayType const&>(_other);
+ if (
+ !ReferenceType::operator==(other) ||
+ other.isByteArray() != isByteArray() ||
+ other.isString() != isString() ||
+ other.isDynamicallySized() != isDynamicallySized()
+ )
+ return false;
+ return isDynamicallySized() || getLength() == other.getLength();
+}
+
+unsigned ArrayType::getCalldataEncodedSize(bool _padded) const
+{
+ if (isDynamicallySized())
+ return 0;
+ bigint size = bigint(getLength()) * (isByteArray() ? 1 : getBaseType()->getCalldataEncodedSize(_padded));
+ size = ((size + 31) / 32) * 32;
+ solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
+ return unsigned(size);
+}
+
+u256 ArrayType::getStorageSize() const
+{
+ if (isDynamicallySized())
+ return 1;
+
+ bigint size;
+ unsigned baseBytes = getBaseType()->getStorageBytes();
+ if (baseBytes == 0)
+ size = 1;
+ else if (baseBytes < 32)
+ {
+ unsigned itemsPerSlot = 32 / baseBytes;
+ size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot;
+ }
+ else
+ size = bigint(getLength()) * getBaseType()->getStorageSize();
+ if (size >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage."));
+ return max<u256>(1, u256(size));
+}
+
+unsigned ArrayType::getSizeOnStack() const
+{
+ if (m_location == DataLocation::CallData)
+ // offset [length] (stack top)
+ return 1 + (isDynamicallySized() ? 1 : 0);
+ else
+ // storage slot or memory offset
+ // byte offset inside storage value is omitted
+ return 1;
+}
+
+string ArrayType::toString(bool _short) const
+{
+ string ret;
+ if (isString())
+ ret = "string";
+ else if (isByteArray())
+ ret = "bytes";
+ else
+ {
+ ret = getBaseType()->toString(_short) + "[";
+ if (!isDynamicallySized())
+ ret += getLength().str();
+ ret += "]";
+ }
+ if (!_short)
+ ret += " " + stringForReferencePart();
+ return ret;
+}
+
+TypePointer ArrayType::externalType() const
+{
+ if (m_arrayKind != ArrayKind::Ordinary)
+ return this->copyForLocation(DataLocation::Memory, true);
+ TypePointer baseExt = m_baseType->externalType();
+ if (!baseExt)
+ return TypePointer();
+ if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized())
+ return TypePointer();
+
+ if (isDynamicallySized())
+ return std::make_shared<ArrayType>(DataLocation::Memory, baseExt);
+ else
+ return std::make_shared<ArrayType>(DataLocation::Memory, baseExt, m_length);
+}
+
+TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const
+{
+ auto copy = make_shared<ArrayType>(_location);
+ copy->m_isPointer = _isPointer;
+ copy->m_arrayKind = m_arrayKind;
+ copy->m_baseType = copy->copyForLocationIfReference(m_baseType);
+ copy->m_hasDynamicLength = m_hasDynamicLength;
+ copy->m_length = m_length;
+ return copy;
+}
+
+const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared<IntegerType>(256)}});
+
+bool ContractType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ ContractType const& other = dynamic_cast<ContractType const&>(_other);
+ return other.m_contract == m_contract && other.m_super == m_super;
+}
+
+string ContractType::toString(bool) const
+{
+ return "contract " + string(m_super ? "super " : "") + m_contract.getName();
+}
+
+MemberList const& ContractType::getMembers() const
+{
+ // We need to lazy-initialize it because of recursive references.
+ if (!m_members)
+ {
+ // All address members and all interface functions
+ MemberList::MemberMap members(
+ IntegerType::AddressMemberList.begin(),
+ IntegerType::AddressMemberList.end()
+ );
+ if (m_super)
+ {
+ // add the most derived of all functions which are visible in derived contracts
+ for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts())
+ for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions())
+ {
+ if (!function->isVisibleInDerivedContracts())
+ continue;
+ auto functionType = make_shared<FunctionType>(*function, true);
+ bool functionWithEqualArgumentsFound = false;
+ for (auto const& member: members)
+ {
+ if (member.name != function->getName())
+ continue;
+ auto memberType = dynamic_cast<FunctionType const*>(member.type.get());
+ solAssert(!!memberType, "Override changes type.");
+ if (!memberType->hasEqualArgumentTypes(*functionType))
+ continue;
+ functionWithEqualArgumentsFound = true;
+ break;
+ }
+ if (!functionWithEqualArgumentsFound)
+ members.push_back(MemberList::Member(
+ function->getName(),
+ functionType,
+ function.get()
+ ));
+ }
+ }
+ else
+ for (auto const& it: m_contract.getInterfaceFunctions())
+ members.push_back(MemberList::Member(
+ it.second->getDeclaration().getName(),
+ it.second->asMemberFunction(),
+ &it.second->getDeclaration()
+ ));
+ m_members.reset(new MemberList(members));
+ }
+ return *m_members;
+}
+
+shared_ptr<FunctionType const> const& ContractType::getConstructorType() const
+{
+ if (!m_constructorType)
+ {
+ FunctionDefinition const* constructor = m_contract.getConstructor();
+ if (constructor)
+ m_constructorType = make_shared<FunctionType>(*constructor);
+ else
+ m_constructorType = make_shared<FunctionType>(TypePointers(), TypePointers());
+ }
+ return m_constructorType;
+}
+
+vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::getStateVariables() const
+{
+ vector<VariableDeclaration const*> variables;
+ for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.getLinearizedBaseContracts()))
+ for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables())
+ if (!variable->isConstant())
+ variables.push_back(variable.get());
+ TypePointers types;
+ for (auto variable: variables)
+ types.push_back(variable->getType());
+ StorageOffsets offsets;
+ offsets.computeOffsets(types);
+
+ vector<tuple<VariableDeclaration const*, u256, unsigned>> variablesAndOffsets;
+ for (size_t index = 0; index < variables.size(); ++index)
+ if (auto const* offset = offsets.getOffset(index))
+ variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second));
+ return variablesAndOffsets;
+}
+
+bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
+{
+ if (_convertTo.getCategory() != getCategory())
+ return false;
+ auto& convertTo = dynamic_cast<StructType const&>(_convertTo);
+ // memory/calldata to storage can be converted, but only to a direct storage reference
+ if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer())
+ return false;
+ if (convertTo.location() == DataLocation::CallData && location() != convertTo.location())
+ return false;
+ return this->m_struct == convertTo.m_struct;
+}
+
+bool StructType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ StructType const& other = dynamic_cast<StructType const&>(_other);
+ return ReferenceType::operator==(other) && other.m_struct == m_struct;
+}
+
+unsigned StructType::getCalldataEncodedSize(bool _padded) const
+{
+ unsigned size = 0;
+ for (auto const& member: getMembers())
+ if (!member.type->canLiveOutsideStorage())
+ return 0;
+ else
+ {
+ unsigned memberSize = member.type->getCalldataEncodedSize(_padded);
+ if (memberSize == 0)
+ return 0;
+ size += memberSize;
+ }
+ return size;
+}
+
+u256 StructType::memorySize() const
+{
+ u256 size;
+ for (auto const& member: getMembers())
+ if (member.type->canLiveOutsideStorage())
+ size += member.type->memoryHeadSize();
+ return size;
+}
+
+u256 StructType::getStorageSize() const
+{
+ return max<u256>(1, getMembers().getStorageSize());
+}
+
+string StructType::toString(bool _short) const
+{
+ string ret = "struct " + m_struct.getName();
+ if (!_short)
+ ret += " " + stringForReferencePart();
+ return ret;
+}
+
+MemberList const& StructType::getMembers() const
+{
+ // We need to lazy-initialize it because of recursive references.
+ if (!m_members)
+ {
+ MemberList::MemberMap members;
+ for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers())
+ {
+ TypePointer type = variable->getType();
+ // Skip all mapping members if we are not in storage.
+ if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
+ continue;
+ members.push_back(MemberList::Member(
+ variable->getName(),
+ copyForLocationIfReference(type),
+ variable.get())
+ );
+ }
+ m_members.reset(new MemberList(members));
+ }
+ return *m_members;
+}
+
+TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const
+{
+ auto copy = make_shared<StructType>(m_struct, _location);
+ copy->m_isPointer = _isPointer;
+ return copy;
+}
+
+FunctionTypePointer StructType::constructorType() const
+{
+ TypePointers paramTypes;
+ strings paramNames;
+ for (auto const& member: getMembers())
+ {
+ if (!member.type->canLiveOutsideStorage())
+ continue;
+ paramNames.push_back(member.name);
+ paramTypes.push_back(copyForLocationIfReference(DataLocation::Memory, member.type));
+ }
+ return make_shared<FunctionType>(
+ paramTypes,
+ TypePointers{copyForLocation(DataLocation::Memory, false)},
+ paramNames,
+ strings(),
+ FunctionType::Location::Internal
+ );
+}
+
+pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const
+{
+ auto const* offsets = getMembers().getMemberStorageOffset(_name);
+ solAssert(offsets, "Storage offset of non-existing member requested.");
+ return *offsets;
+}
+
+u256 StructType::memoryOffsetOfMember(string const& _name) const
+{
+ u256 offset;
+ for (auto const& member: getMembers())
+ if (member.name == _name)
+ return offset;
+ else
+ offset += member.type->memoryHeadSize();
+ solAssert(false, "Member not found in struct.");
+ return 0;
+}
+
+set<string> StructType::membersMissingInMemory() const
+{
+ set<string> missing;
+ for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers())
+ if (!variable->getType()->canLiveOutsideStorage())
+ missing.insert(variable->getName());
+ return missing;
+}
+
+TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
+{
+ return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer();
+}
+
+bool EnumType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ EnumType const& other = dynamic_cast<EnumType const&>(_other);
+ return other.m_enum == m_enum;
+}
+
+unsigned EnumType::getStorageBytes() const
+{
+ size_t elements = m_enum.getMembers().size();
+ if (elements <= 1)
+ return 1;
+ else
+ return dev::bytesRequired(elements - 1);
+}
+
+string EnumType::toString(bool) const
+{
+ return string("enum ") + m_enum.getName();
+}
+
+bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::Integer;
+}
+
+unsigned int EnumType::getMemberValue(ASTString const& _member) const
+{
+ unsigned int index = 0;
+ for (ASTPointer<EnumValue> const& decl: m_enum.getMembers())
+ {
+ if (decl->getName() == _member)
+ return index;
+ ++index;
+ }
+ BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member));
+}
+
+FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
+ m_location(_isInternal ? Location::Internal : Location::External),
+ m_isConstant(_function.isDeclaredConst()),
+ m_declaration(&_function)
+{
+ TypePointers params;
+ vector<string> paramNames;
+ TypePointers retParams;
+ vector<string> retParamNames;
+
+ params.reserve(_function.getParameters().size());
+ paramNames.reserve(_function.getParameters().size());
+ for (ASTPointer<VariableDeclaration> const& var: _function.getParameters())
+ {
+ paramNames.push_back(var->getName());
+ params.push_back(var->getType());
+ }
+ retParams.reserve(_function.getReturnParameters().size());
+ retParamNames.reserve(_function.getReturnParameters().size());
+ for (ASTPointer<VariableDeclaration> const& var: _function.getReturnParameters())
+ {
+ retParamNames.push_back(var->getName());
+ retParams.push_back(var->getType());
+ }
+ swap(params, m_parameterTypes);
+ swap(paramNames, m_parameterNames);
+ swap(retParams, m_returnParameterTypes);
+ swap(retParamNames, m_returnParameterNames);
+}
+
+FunctionType::FunctionType(VariableDeclaration const& _varDecl):
+ m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl)
+{
+ TypePointers paramTypes;
+ vector<string> paramNames;
+ auto returnType = _varDecl.getType();
+
+ while (true)
+ {
+ if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get()))
+ {
+ paramTypes.push_back(mappingType->getKeyType());
+ paramNames.push_back("");
+ returnType = mappingType->getValueType();
+ }
+ else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get()))
+ {
+ if (arrayType->isByteArray())
+ // Return byte arrays as as whole.
+ break;
+ returnType = arrayType->getBaseType();
+ paramNames.push_back("");
+ paramTypes.push_back(make_shared<IntegerType>(256));
+ }
+ else
+ break;
+ }
+
+ TypePointers retParams;
+ vector<string> retParamNames;
+ if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
+ {
+ for (auto const& member: structType->getMembers())
+ if (member.type->getCategory() != Category::Mapping)
+ {
+ if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get()))
+ if (!arrayType->isByteArray())
+ continue;
+ retParams.push_back(member.type);
+ retParamNames.push_back(member.name);
+ }
+ }
+ else
+ {
+ retParams.push_back(ReferenceType::copyForLocationIfReference(
+ DataLocation::Memory,
+ returnType
+ ));
+ retParamNames.push_back("");
+ }
+
+ swap(paramTypes, m_parameterTypes);
+ swap(paramNames, m_parameterNames);
+ swap(retParams, m_returnParameterTypes);
+ swap(retParamNames, m_returnParameterNames);
+}
+
+FunctionType::FunctionType(const EventDefinition& _event):
+ m_location(Location::Event), m_isConstant(true), m_declaration(&_event)
+{
+ TypePointers params;
+ vector<string> paramNames;
+ params.reserve(_event.getParameters().size());
+ paramNames.reserve(_event.getParameters().size());
+ for (ASTPointer<VariableDeclaration> const& var: _event.getParameters())
+ {
+ paramNames.push_back(var->getName());
+ params.push_back(var->getType());
+ }
+ swap(params, m_parameterTypes);
+ swap(paramNames, m_parameterNames);
+}
+
+bool FunctionType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
+
+ if (m_location != other.m_location)
+ return false;
+ if (m_isConstant != other.isConstant())
+ return false;
+
+ if (m_parameterTypes.size() != other.m_parameterTypes.size() ||
+ m_returnParameterTypes.size() != other.m_returnParameterTypes.size())
+ return false;
+ auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
+
+ if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(),
+ other.m_parameterTypes.cbegin(), typeCompare))
+ return false;
+ if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(),
+ other.m_returnParameterTypes.cbegin(), typeCompare))
+ return false;
+ //@todo this is ugly, but cannot be prevented right now
+ if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
+ return false;
+ return true;
+}
+
+string FunctionType::toString(bool _short) const
+{
+ string name = "function (";
+ for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it)
+ name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ",");
+ name += ") returns (";
+ for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it)
+ name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ",");
+ return name + ")";
+}
+
+u256 FunctionType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable function type requested."));
+}
+
+unsigned FunctionType::getSizeOnStack() const
+{
+ Location location = m_location;
+ if (m_location == Location::SetGas || m_location == Location::SetValue)
+ {
+ solAssert(m_returnParameterTypes.size() == 1, "");
+ location = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_location;
+ }
+
+ unsigned size = 0;
+ if (location == Location::External || location == Location::CallCode)
+ size = 2;
+ else if (location == Location::Bare || location == Location::BareCallCode)
+ size = 1;
+ else if (location == Location::Internal)
+ size = 1;
+ if (m_gasSet)
+ size++;
+ if (m_valueSet)
+ size++;
+ return size;
+}
+
+FunctionTypePointer FunctionType::externalFunctionType() const
+{
+ TypePointers paramTypes;
+ TypePointers retParamTypes;
+
+ for (auto type: m_parameterTypes)
+ {
+ if (auto ext = type->externalType())
+ paramTypes.push_back(ext);
+ else
+ return FunctionTypePointer();
+ }
+ for (auto type: m_returnParameterTypes)
+ {
+ if (auto ext = type->externalType())
+ retParamTypes.push_back(ext);
+ else
+ return FunctionTypePointer();
+ }
+ return make_shared<FunctionType>(paramTypes, retParamTypes, m_parameterNames, m_returnParameterNames, m_location, m_arbitraryParameters);
+}
+
+MemberList const& FunctionType::getMembers() const
+{
+ switch (m_location)
+ {
+ case Location::External:
+ case Location::Creation:
+ case Location::ECRecover:
+ case Location::SHA256:
+ case Location::RIPEMD160:
+ case Location::Bare:
+ case Location::BareCallCode:
+ if (!m_members)
+ {
+ MemberList::MemberMap members{
+ {
+ "value",
+ make_shared<FunctionType>(
+ parseElementaryTypeVector({"uint"}),
+ TypePointers{copyAndSetGasOrValue(false, true)},
+ strings(),
+ strings(),
+ Location::SetValue,
+ false,
+ nullptr,
+ m_gasSet,
+ m_valueSet
+ )
+ }
+ };
+ if (m_location != Location::Creation)
+ members.push_back(
+ MemberList::Member(
+ "gas",
+ make_shared<FunctionType>(
+ parseElementaryTypeVector({"uint"}),
+ TypePointers{copyAndSetGasOrValue(true, false)},
+ strings(),
+ strings(),
+ Location::SetGas,
+ false,
+ nullptr,
+ m_gasSet,
+ m_valueSet
+ )
+ )
+ );
+ m_members.reset(new MemberList(members));
+ }
+ return *m_members;
+ default:
+ return EmptyMemberList;
+ }
+}
+
+bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes) const
+{
+ TypePointers const& parameterTypes = getParameterTypes();
+ if (takesArbitraryParameters())
+ return true;
+ else if (_argumentTypes.size() != parameterTypes.size())
+ return false;
+ else
+ return std::equal(
+ _argumentTypes.cbegin(),
+ _argumentTypes.cend(),
+ parameterTypes.cbegin(),
+ [](TypePointer const& argumentType, TypePointer const& parameterType)
+ {
+ return argumentType->isImplicitlyConvertibleTo(*parameterType);
+ }
+ );
+}
+
+bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
+{
+ if (m_parameterTypes.size() != _other.m_parameterTypes.size())
+ return false;
+ return equal(
+ m_parameterTypes.cbegin(),
+ m_parameterTypes.cend(),
+ _other.m_parameterTypes.cbegin(),
+ [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }
+ );
+}
+
+bool FunctionType::isBareCall() const
+{
+ switch (m_location)
+ {
+ case Location::Bare:
+ case Location::BareCallCode:
+ case Location::ECRecover:
+ case Location::SHA256:
+ case Location::RIPEMD160:
+ return true;
+ default:
+ return false;
+ }
+}
+
+string FunctionType::externalSignature(std::string const& _name) const
+{
+ std::string funcName = _name;
+ if (_name == "")
+ {
+ solAssert(m_declaration != nullptr, "Function type without name needs a declaration");
+ funcName = m_declaration->getName();
+ }
+ string ret = funcName + "(";
+
+ FunctionTypePointer external = externalFunctionType();
+ solAssert(!!external, "External function type requested.");
+ TypePointers externalParameterTypes = external->getParameterTypes();
+ for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it)
+ {
+ solAssert(!!(*it), "Parameter should have external type");
+ ret += (*it)->toString(true) + (it + 1 == externalParameterTypes.cend() ? "" : ",");
+ }
+
+ return ret + ")";
+}
+
+u256 FunctionType::externalIdentifier() const
+{
+ return FixedHash<4>::Arith(FixedHash<4>(dev::sha3(externalSignature())));
+}
+
+TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
+{
+ TypePointers pointers;
+ pointers.reserve(_types.size());
+ for (string const& type: _types)
+ pointers.push_back(Type::fromElementaryTypeName(type));
+ return pointers;
+}
+
+TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const
+{
+ return make_shared<FunctionType>(
+ m_parameterTypes,
+ m_returnParameterTypes,
+ m_parameterNames,
+ m_returnParameterNames,
+ m_location,
+ m_arbitraryParameters,
+ m_declaration,
+ m_gasSet || _setGas,
+ m_valueSet || _setValue
+ );
+}
+
+FunctionTypePointer FunctionType::asMemberFunction() const
+{
+ TypePointers parameterTypes;
+ for (auto const& t: m_parameterTypes)
+ {
+ auto refType = dynamic_cast<ReferenceType const*>(t.get());
+ if (refType && refType->location() == DataLocation::CallData)
+ parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, false));
+ else
+ parameterTypes.push_back(t);
+ }
+
+ //@todo make this more intelligent once we support destructuring assignments
+ TypePointers returnParameterTypes;
+ vector<string> returnParameterNames;
+ if (!m_returnParameterTypes.empty() && m_returnParameterTypes.front()->getCalldataEncodedSize() > 0)
+ {
+ returnParameterTypes.push_back(m_returnParameterTypes.front());
+ returnParameterNames.push_back(m_returnParameterNames.front());
+ }
+ return make_shared<FunctionType>(
+ parameterTypes,
+ returnParameterTypes,
+ m_parameterNames,
+ returnParameterNames,
+ m_location,
+ m_arbitraryParameters,
+ m_declaration,
+ m_gasSet,
+ m_valueSet
+ );
+}
+
+vector<string> const FunctionType::getParameterTypeNames() const
+{
+ vector<string> names;
+ for (TypePointer const& t: m_parameterTypes)
+ names.push_back(t->toString(true));
+
+ return names;
+}
+
+vector<string> const FunctionType::getReturnParameterTypeNames() const
+{
+ vector<string> names;
+ for (TypePointer const& t: m_returnParameterTypes)
+ names.push_back(t->toString(true));
+
+ return names;
+}
+
+ASTPointer<ASTString> FunctionType::getDocumentation() const
+{
+ auto function = dynamic_cast<Documented const*>(m_declaration);
+ if (function)
+ return function->getDocumentation();
+
+ return ASTPointer<ASTString>();
+}
+
+bool MappingType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ MappingType const& other = dynamic_cast<MappingType const&>(_other);
+ return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType;
+}
+
+string MappingType::toString(bool _short) const
+{
+ return "mapping(" + getKeyType()->toString(_short) + " => " + getValueType()->toString(_short) + ")";
+}
+
+u256 VoidType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable void type requested."));
+}
+
+bool TypeType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ TypeType const& other = dynamic_cast<TypeType const&>(_other);
+ return *getActualType() == *other.getActualType();
+}
+
+u256 TypeType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable type type requested."));
+}
+
+MemberList const& TypeType::getMembers() const
+{
+ // We need to lazy-initialize it because of recursive references.
+ if (!m_members)
+ {
+ MemberList::MemberMap members;
+ if (m_actualType->getCategory() == Category::Contract && m_currentContract != nullptr)
+ {
+ ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).getContractDefinition();
+ vector<ContractDefinition const*> currentBases = m_currentContract->getLinearizedBaseContracts();
+ if (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end())
+ // We are accessing the type of a base contract, so add all public and protected
+ // members. Note that this does not add inherited functions on purpose.
+ for (Declaration const* decl: contract.getInheritableMembers())
+ members.push_back(MemberList::Member(decl->getName(), decl->getType(), decl));
+ }
+ else if (m_actualType->getCategory() == Category::Enum)
+ {
+ EnumDefinition const& enumDef = dynamic_cast<EnumType const&>(*m_actualType).getEnumDefinition();
+ auto enumType = make_shared<EnumType>(enumDef);
+ for (ASTPointer<EnumValue> const& enumValue: enumDef.getMembers())
+ members.push_back(MemberList::Member(enumValue->getName(), enumType));
+ }
+ m_members.reset(new MemberList(members));
+ }
+ return *m_members;
+}
+
+ModifierType::ModifierType(const ModifierDefinition& _modifier)
+{
+ TypePointers params;
+ params.reserve(_modifier.getParameters().size());
+ for (ASTPointer<VariableDeclaration> const& var: _modifier.getParameters())
+ params.push_back(var->getType());
+ swap(params, m_parameterTypes);
+}
+
+u256 ModifierType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable type type requested."));
+}
+
+bool ModifierType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ ModifierType const& other = dynamic_cast<ModifierType const&>(_other);
+
+ if (m_parameterTypes.size() != other.m_parameterTypes.size())
+ return false;
+ auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
+
+ if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(),
+ other.m_parameterTypes.cbegin(), typeCompare))
+ return false;
+ return true;
+}
+
+string ModifierType::toString(bool _short) const
+{
+ string name = "modifier (";
+ for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it)
+ name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ",");
+ return name + ")";
+}
+
+MagicType::MagicType(MagicType::Kind _kind):
+ m_kind(_kind)
+{
+ switch (m_kind)
+ {
+ case Kind::Block:
+ m_members = MemberList({
+ {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"timestamp", make_shared<IntegerType>(256)},
+ {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)},
+ {"difficulty", make_shared<IntegerType>(256)},
+ {"number", make_shared<IntegerType>(256)},
+ {"gaslimit", make_shared<IntegerType>(256)}
+ });
+ break;
+ case Kind::Message:
+ m_members = MemberList({
+ {"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"gas", make_shared<IntegerType>(256)},
+ {"value", make_shared<IntegerType>(256)},
+ {"data", make_shared<ArrayType>(DataLocation::CallData)},
+ {"sig", make_shared<FixedBytesType>(4)}
+ });
+ break;
+ case Kind::Transaction:
+ m_members = MemberList({
+ {"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"gasprice", make_shared<IntegerType>(256)}
+ });
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic."));
+ }
+}
+
+bool MagicType::operator==(Type const& _other) const
+{
+ if (_other.getCategory() != getCategory())
+ return false;
+ MagicType const& other = dynamic_cast<MagicType const&>(_other);
+ return other.m_kind == m_kind;
+}
+
+string MagicType::toString(bool) const
+{
+ switch (m_kind)
+ {
+ case Kind::Block:
+ return "block";
+ case Kind::Message:
+ return "msg";
+ case Kind::Transaction:
+ return "tx";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic."));
+ }
+}
diff --git a/libsolidity/Types.h b/libsolidity/Types.h
new file mode 100644
index 00000000..c4992cfd
--- /dev/null
+++ b/libsolidity/Types.h
@@ -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;
+};
+
+}
+}
diff --git a/libsolidity/Utils.h b/libsolidity/Utils.h
new file mode 100644
index 00000000..05c5fa6f
--- /dev/null
+++ b/libsolidity/Utils.h
@@ -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)
+
diff --git a/libsolidity/Version.cpp b/libsolidity/Version.cpp
new file mode 100644
index 00000000..75a0dd25
--- /dev/null
+++ b/libsolidity/Version.cpp
@@ -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);
+
diff --git a/libsolidity/Version.h b/libsolidity/Version.h
new file mode 100644
index 00000000..6e00f07b
--- /dev/null
+++ b/libsolidity/Version.h
@@ -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;
+
+}
+}
diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt
new file mode 100644
index 00000000..6503516c
--- /dev/null
+++ b/libsolidity/grammar.txt
@@ -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 ')'
diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt
new file mode 100644
index 00000000..df72f52d
--- /dev/null
+++ b/solc/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_policy(SET CMP0015 NEW)
+set(CMAKE_AUTOMOC OFF)
+
+aux_source_directory(. SRC_LIST)
+list(REMOVE_ITEM SRC_LIST "./jsonCompiler.cpp")
+
+include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS})
+include_directories(BEFORE ..)
+include_directories(${Boost_INCLUDE_DIRS})
+
+set(EXECUTABLE solc)
+
+file(GLOB HEADERS "*.h")
+add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
+
+add_dependencies(${EXECUTABLE} BuildInfo.h)
+
+target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES})
+target_link_libraries(${EXECUTABLE} ${Boost_PROGRAM_OPTIONS_LIBRARIES})
+target_link_libraries(${EXECUTABLE} solidity)
+
+if (APPLE)
+ install(TARGETS ${EXECUTABLE} DESTINATION bin)
+else()
+ eth_install_executable(${EXECUTABLE})
+endif()
+
+add_library(soljson jsonCompiler.cpp ${HEADERS})
+target_link_libraries(soljson solidity)
diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp
new file mode 100644
index 00000000..a5ad07d7
--- /dev/null
+++ b/solc/CommandLineInterface.cpp
@@ -0,0 +1,635 @@
+/*
+ This file is part of cpp-ethereum.
+
+ cpp-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ cpp-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Lefteris <lefteris@ethdev.com>
+ * @author Gav Wood <g@ethdev.com>
+ * @date 2014
+ * Solidity command line interface.
+ */
+#include "CommandLineInterface.h"
+
+#include <string>
+#include <iostream>
+#include <fstream>
+
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include "BuildInfo.h"
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonData.h>
+#include <libdevcore/CommonIO.h>
+#include <libevmcore/Instruction.h>
+#include <libevmcore/Params.h>
+#include <libsolidity/Version.h>
+#include <libsolidity/Scanner.h>
+#include <libsolidity/Parser.h>
+#include <libsolidity/ASTPrinter.h>
+#include <libsolidity/ASTJsonConverter.h>
+#include <libsolidity/NameAndTypeResolver.h>
+#include <libsolidity/Exceptions.h>
+#include <libsolidity/CompilerStack.h>
+#include <libsolidity/SourceReferenceFormatter.h>
+#include <libsolidity/GasEstimator.h>
+
+using namespace std;
+namespace po = boost::program_options;
+
+namespace dev
+{
+namespace solidity
+{
+
+static string const g_argAbiStr = "abi";
+static string const g_argSolInterfaceStr = "interface";
+static string const g_argSignatureHashes = "hashes";
+static string const g_argGas = "gas";
+static string const g_argAsmStr = "asm";
+static string const g_argAsmJsonStr = "asm-json";
+static string const g_argAstStr = "ast";
+static string const g_argAstJson = "ast-json";
+static string const g_argBinaryStr = "bin";
+static string const g_argCloneBinaryStr = "clone-bin";
+static string const g_argOpcodesStr = "opcodes";
+static string const g_argNatspecDevStr = "devdoc";
+static string const g_argNatspecUserStr = "userdoc";
+static string const g_argAddStandard = "add-std";
+
+/// Possible arguments to for --combined-json
+static set<string> const g_combinedJsonArgs{
+ "bin",
+ "clone-bin",
+ "opcodes",
+ "abi",
+ "interface",
+ "asm",
+ "ast",
+ "userdoc",
+ "devdoc"
+};
+
+static void version()
+{
+ cout <<
+ "solc, the solidity compiler commandline interface" <<
+ endl <<
+ "Version: " <<
+ dev::solidity::VersionString <<
+ endl;
+ exit(0);
+}
+
+static inline bool humanTargetedStdout(po::variables_map const& _args, string const& _name)
+{
+ return _args.count(_name) && !(_args.count("output-dir"));
+}
+
+static bool needsHumanTargetedStdout(po::variables_map const& _args)
+{
+
+ return
+ _args.count(g_argGas) ||
+ humanTargetedStdout(_args, g_argAbiStr) ||
+ humanTargetedStdout(_args, g_argSolInterfaceStr) ||
+ humanTargetedStdout(_args, g_argSignatureHashes) ||
+ humanTargetedStdout(_args, g_argNatspecUserStr) ||
+ humanTargetedStdout(_args, g_argAstJson) ||
+ humanTargetedStdout(_args, g_argNatspecDevStr) ||
+ humanTargetedStdout(_args, g_argAsmStr) ||
+ humanTargetedStdout(_args, g_argAsmJsonStr) ||
+ humanTargetedStdout(_args, g_argOpcodesStr) ||
+ humanTargetedStdout(_args, g_argBinaryStr) ||
+ humanTargetedStdout(_args, g_argCloneBinaryStr);
+}
+
+void CommandLineInterface::handleBinary(string const& _contract)
+{
+ if (m_args.count(g_argBinaryStr))
+ {
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ data << toHex(m_compiler->getBytecode(_contract));
+ createFile(_contract + ".bin", data.str());
+ }
+ else
+ {
+ cout << "Binary: " << endl;
+ cout << toHex(m_compiler->getBytecode(_contract)) << endl;
+ }
+
+ }
+ if (m_args.count(g_argCloneBinaryStr))
+ {
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ data << toHex(m_compiler->getCloneBytecode(_contract));
+ createFile(_contract + ".clone_bin", data.str());
+ }
+ else
+ {
+ cout << "Clone Binary: " << endl;
+ cout << toHex(m_compiler->getCloneBytecode(_contract)) << endl;
+ }
+ }
+ else
+ {
+ cout << "Binary: " << endl;
+ cout << toHex(m_compiler->getBytecode(_contract)) << endl;
+ }
+
+}
+
+void CommandLineInterface::handleOpcode(string const& _contract)
+{
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ data << eth::disassemble(m_compiler->getBytecode(_contract));
+ createFile(_contract + ".opcode", data.str());
+ }
+ else
+ {
+ cout << "Opcodes: " << endl;
+ cout << eth::disassemble(m_compiler->getBytecode(_contract));
+ cout << endl;
+ }
+
+}
+
+void CommandLineInterface::handleBytecode(string const& _contract)
+{
+ if (m_args.count(g_argOpcodesStr))
+ handleOpcode(_contract);
+ if (m_args.count(g_argBinaryStr) || m_args.count(g_argCloneBinaryStr))
+ handleBinary(_contract);
+}
+
+void CommandLineInterface::handleSignatureHashes(string const& _contract)
+{
+ if (!m_args.count(g_argSignatureHashes))
+ return;
+
+ string out;
+ for (auto const& it: m_compiler->getContractDefinition(_contract).getInterfaceFunctions())
+ out += toHex(it.first.ref()) + ": " + it.second->externalSignature() + "\n";
+
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ data << out;
+ createFile(_contract + ".signatures", data.str());
+ }
+ else
+ cout << "Function signatures: " << endl << out;
+}
+
+void CommandLineInterface::handleMeta(DocumentationType _type, string const& _contract)
+{
+ std::string argName;
+ std::string suffix;
+ std::string title;
+ switch(_type)
+ {
+ case DocumentationType::ABIInterface:
+ argName = g_argAbiStr;
+ suffix = ".abi";
+ title = "Contract JSON ABI";
+ break;
+ case DocumentationType::ABISolidityInterface:
+ argName = g_argSolInterfaceStr;
+ suffix = "_interface.sol";
+ title = "Contract Solidity ABI";
+ break;
+ case DocumentationType::NatspecUser:
+ argName = g_argNatspecUserStr;
+ suffix = ".docuser";
+ title = "User Documentation";
+ break;
+ case DocumentationType::NatspecDev:
+ argName = g_argNatspecDevStr;
+ suffix = ".docdev";
+ title = "Developer Documentation";
+ break;
+ default:
+ // should never happen
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation _type"));
+ }
+
+ if (m_args.count(argName))
+ {
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ data << m_compiler->getMetadata(_contract, _type);
+ createFile(_contract + suffix, data.str());
+ }
+ else
+ {
+ cout << title << endl;
+ cout << m_compiler->getMetadata(_contract, _type) << endl;
+ }
+
+ }
+}
+
+void CommandLineInterface::handleGasEstimation(string const& _contract)
+{
+ using Gas = GasEstimator::GasConsumption;
+ if (!m_compiler->getAssemblyItems(_contract) && !m_compiler->getRuntimeAssemblyItems(_contract))
+ return;
+ cout << "Gas estimation:" << endl;
+ if (eth::AssemblyItems const* items = m_compiler->getAssemblyItems(_contract))
+ {
+ Gas gas = GasEstimator::functionalEstimation(*items);
+ u256 bytecodeSize(m_compiler->getRuntimeBytecode(_contract).size());
+ cout << "construction:" << endl;
+ cout << " " << gas << " + " << (bytecodeSize * eth::c_createDataGas) << " = ";
+ gas += bytecodeSize * eth::c_createDataGas;
+ cout << gas << endl;
+ }
+ if (eth::AssemblyItems const* items = m_compiler->getRuntimeAssemblyItems(_contract))
+ {
+ ContractDefinition const& contract = m_compiler->getContractDefinition(_contract);
+ cout << "external:" << endl;
+ for (auto it: contract.getInterfaceFunctions())
+ {
+ string sig = it.second->externalSignature();
+ GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig);
+ cout << " " << sig << ":\t" << gas << endl;
+ }
+ if (contract.getFallbackFunction())
+ {
+ GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID");
+ cout << " fallback:\t" << gas << endl;
+ }
+ cout << "internal:" << endl;
+ for (auto const& it: contract.getDefinedFunctions())
+ {
+ if (it->isPartOfExternalInterface() || it->isConstructor())
+ continue;
+ size_t entry = m_compiler->getFunctionEntryPoint(_contract, *it);
+ GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
+ if (entry > 0)
+ gas = GasEstimator::functionalEstimation(*items, entry, *it);
+ FunctionType type(*it);
+ cout << " " << it->getName() << "(";
+ auto end = type.getParameterTypes().end();
+ for (auto it = type.getParameterTypes().begin(); it != end; ++it)
+ cout << (*it)->toString() << (it + 1 == end ? "" : ",");
+ cout << "):\t" << gas << endl;
+ }
+ }
+}
+
+void CommandLineInterface::createFile(string const& _fileName, string const& _data)
+{
+ namespace fs = boost::filesystem;
+ // create directory if not existent
+ fs::path p(m_args["output-dir"].as<string>());
+ fs::create_directories(p);
+ ofstream outFile((p / _fileName).string());
+ outFile << _data;
+ if (!outFile)
+ BOOST_THROW_EXCEPTION(FileError() << errinfo_comment("Could not write to file: " + _fileName));
+}
+
+bool CommandLineInterface::parseArguments(int _argc, char** _argv)
+{
+ // Declare the supported options.
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help", "Show help message and exit")
+ ("version", "Show version and exit")
+ ("optimize", po::value<bool>()->default_value(false), "Optimize bytecode")
+ ("optimize-runs", po::value<unsigned>()->default_value(200), "Estimated number of contract runs for optimizer.")
+ ("add-std", po::value<bool>()->default_value(false), "Add standard contracts")
+ ("input-file", po::value<vector<string>>(), "input file")
+ ("output-dir,o", po::value<string>(), "Output directory path")
+ (
+ "combined-json",
+ po::value<string>()->value_name(boost::join(g_combinedJsonArgs, ",")),
+ "Output a single json document containing the specified information, can be combined."
+ )
+ (g_argAstStr.c_str(), "Outputs the AST of the contract.")
+ (g_argAstJson.c_str(), "Outputs the AST of the contract in JSON format.")
+ (g_argAsmStr.c_str(), "Outputs the EVM assembly of the contract.")
+ (g_argAsmJsonStr.c_str(), "Outputs the EVM assembly of the contract in JSON format.")
+ (g_argOpcodesStr.c_str(), "Outputs the Opcodes of the contract.")
+ (g_argBinaryStr.c_str(), "Outputs the contract in binary (hexadecimal).")
+ (g_argCloneBinaryStr.c_str(), "Output the clone contract in binary (hexadecimal).")
+ (g_argAbiStr.c_str(), "Outputs the contract's JSON ABI interface.")
+ (g_argSolInterfaceStr.c_str(), "Outputs the contract's Solidity interface.")
+ (g_argSignatureHashes.c_str(), "Outputs the contract's functions' signature hashes.")
+ (g_argGas.c_str(), "Outputs an estimate for each function's maximal gas usage.")
+ (g_argNatspecUserStr.c_str(), "Outputs the contract's Natspec user documentation.")
+ (g_argNatspecDevStr.c_str(), "Outputs the contract's Natspec developer documentation.");
+
+ // All positional options should be interpreted as input files
+ po::positional_options_description filesPositions;
+ filesPositions.add("output-dir", 1);
+ filesPositions.add("input-file", -1);
+
+ // parse the compiler arguments
+ try
+ {
+ po::store(po::command_line_parser(_argc, _argv).options(desc).positional(filesPositions).allow_unregistered().run(), m_args);
+
+ }
+ catch (po::error const& _exception)
+ {
+ cerr << _exception.what() << endl;
+ return false;
+ }
+
+ if (m_args.count("combined-json"))
+ {
+ vector<string> requests;
+ for (string const& item: boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(",")))
+ if (!g_combinedJsonArgs.count(item))
+ {
+ cerr << "Invalid option to --combined-json: " << item << endl;
+ return false;
+ }
+ }
+ po::notify(m_args);
+
+ if (m_args.count("help"))
+ {
+ cout << desc;
+ return false;
+ }
+
+ if (m_args.count("version"))
+ {
+ version();
+ return false;
+ }
+
+ return true;
+}
+
+bool CommandLineInterface::processInput()
+{
+ if (!m_args.count("input-file"))
+ {
+ string s;
+ while (!cin.eof())
+ {
+ getline(cin, s);
+ m_sourceCodes["<stdin>"].append(s + '\n');
+ }
+ }
+ else
+ for (string const& infile: m_args["input-file"].as<vector<string>>())
+ {
+ auto path = boost::filesystem::path(infile);
+ if (!boost::filesystem::exists(path))
+ {
+ cerr << "Skipping non existant input file \"" << infile << "\"" << endl;
+ continue;
+ }
+
+ if (!boost::filesystem::is_regular_file(path))
+ {
+ cerr << "\"" << infile << "\" is not a valid file. Skipping" << endl;
+ continue;
+ }
+
+ m_sourceCodes[infile] = dev::contentsString(infile);
+ }
+
+ m_compiler.reset(new CompilerStack(m_args["add-std"].as<bool>()));
+ try
+ {
+ for (auto const& sourceCode: m_sourceCodes)
+ m_compiler->addSource(sourceCode.first, sourceCode.second);
+ // TODO: Perhaps we should not compile unless requested
+ bool optimize = m_args["optimize"].as<bool>();
+ unsigned runs = m_args["optimize-runs"].as<unsigned>();
+ m_compiler->compile(optimize, runs);
+ }
+ catch (ParserError const& _exception)
+ {
+ SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", *m_compiler);
+ return false;
+ }
+ catch (DeclarationError const& _exception)
+ {
+ SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", *m_compiler);
+ return false;
+ }
+ catch (TypeError const& _exception)
+ {
+ SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", *m_compiler);
+ return false;
+ }
+ catch (CompilerError const& _exception)
+ {
+ SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", *m_compiler);
+ return false;
+ }
+ catch (InternalCompilerError const& _exception)
+ {
+ cerr << "Internal compiler error during compilation:" << endl
+ << boost::diagnostic_information(_exception);
+ return false;
+ }
+ catch (DocstringParsingError const& _exception)
+ {
+ cerr << "Documentation parsing error: " << *boost::get_error_info<errinfo_comment>(_exception) << endl;
+ return false;
+ }
+ catch (Exception const& _exception)
+ {
+ cerr << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl;
+ return false;
+ }
+ catch (...)
+ {
+ cerr << "Unknown exception during compilation." << endl;
+ return false;
+ }
+
+ return true;
+}
+
+void CommandLineInterface::handleCombinedJSON()
+{
+ if (!m_args.count("combined-json"))
+ return;
+
+ Json::Value output(Json::objectValue);
+
+ set<string> requests;
+ boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(","));
+ vector<string> contracts = m_compiler->getContractNames();
+
+ if (!contracts.empty())
+ output["contracts"] = Json::Value(Json::objectValue);
+ for (string const& contractName: contracts)
+ {
+ Json::Value contractData(Json::objectValue);
+ if (requests.count("interface"))
+ contractData["interface"] = m_compiler->getSolidityInterface(contractName);
+ if (requests.count("abi"))
+ contractData["abi"] = m_compiler->getInterface(contractName);
+ if (requests.count("bin"))
+ contractData["bin"] = toHex(m_compiler->getBytecode(contractName));
+ if (requests.count("clone-bin"))
+ contractData["clone-bin"] = toHex(m_compiler->getCloneBytecode(contractName));
+ if (requests.count("opcodes"))
+ contractData["opcodes"] = eth::disassemble(m_compiler->getBytecode(contractName));
+ if (requests.count("asm"))
+ {
+ ostringstream unused;
+ contractData["asm"] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true);
+ }
+ if (requests.count("devdoc"))
+ contractData["devdoc"] = m_compiler->getMetadata(contractName, DocumentationType::NatspecDev);
+ if (requests.count("userdoc"))
+ contractData["userdoc"] = m_compiler->getMetadata(contractName, DocumentationType::NatspecUser);
+ output["contracts"][contractName] = contractData;
+ }
+
+ if (requests.count("ast"))
+ {
+ output["sources"] = Json::Value(Json::objectValue);
+ for (auto const& sourceCode: m_sourceCodes)
+ {
+ ASTJsonConverter converter(m_compiler->getAST(sourceCode.first));
+ output["sources"][sourceCode.first] = Json::Value(Json::objectValue);
+ output["sources"][sourceCode.first]["AST"] = converter.json();
+ }
+ }
+ cout << Json::FastWriter().write(output) << endl;
+}
+
+void CommandLineInterface::handleAst(string const& _argStr)
+{
+ string title;
+
+ if (_argStr == g_argAstStr)
+ title = "Syntax trees:";
+ else if (_argStr == g_argAstJson)
+ title = "JSON AST:";
+ else
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal argStr for AST"));
+
+ // do we need AST output?
+ if (m_args.count(_argStr))
+ {
+ vector<ASTNode const*> asts;
+ for (auto const& sourceCode: m_sourceCodes)
+ asts.push_back(&m_compiler->getAST(sourceCode.first));
+ map<ASTNode const*, eth::GasMeter::GasConsumption> gasCosts;
+ if (m_compiler->getRuntimeAssemblyItems())
+ gasCosts = GasEstimator::breakToStatementLevel(
+ GasEstimator::structuralEstimation(*m_compiler->getRuntimeAssemblyItems(), asts),
+ asts
+ );
+
+ if (m_args.count("output-dir"))
+ {
+ for (auto const& sourceCode: m_sourceCodes)
+ {
+ stringstream data;
+ string postfix = "";
+ if (_argStr == g_argAstStr)
+ {
+ ASTPrinter printer(m_compiler->getAST(sourceCode.first), sourceCode.second);
+ printer.print(data);
+ }
+ else
+ {
+ ASTJsonConverter converter(m_compiler->getAST(sourceCode.first));
+ converter.print(data);
+ postfix += "_json";
+ }
+ createFile(sourceCode.first + postfix + ".ast", data.str());
+ }
+ }
+ else
+ {
+ cout << title << endl << endl;
+ for (auto const& sourceCode: m_sourceCodes)
+ {
+ cout << endl << "======= " << sourceCode.first << " =======" << endl;
+ if (_argStr == g_argAstStr)
+ {
+ ASTPrinter printer(
+ m_compiler->getAST(sourceCode.first),
+ sourceCode.second,
+ gasCosts
+ );
+ printer.print(cout);
+ }
+ else
+ {
+ ASTJsonConverter converter(m_compiler->getAST(sourceCode.first));
+ converter.print(cout);
+ }
+ }
+ }
+ }
+}
+
+void CommandLineInterface::actOnInput()
+{
+ handleCombinedJSON();
+
+ // do we need AST output?
+ handleAst(g_argAstStr);
+ handleAst(g_argAstJson);
+
+ vector<string> contracts = m_compiler->getContractNames();
+ for (string const& contract: contracts)
+ {
+ if (needsHumanTargetedStdout(m_args))
+ cout << endl << "======= " << contract << " =======" << endl;
+
+ // do we need EVM assembly?
+ if (m_args.count(g_argAsmStr) || m_args.count(g_argAsmJsonStr))
+ {
+ if (m_args.count("output-dir"))
+ {
+ stringstream data;
+ m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr));
+ createFile(contract + (m_args.count(g_argAsmJsonStr) ? "_evm.json" : ".evm"), data.str());
+ }
+ else
+ {
+ cout << "EVM assembly:" << endl;
+ m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr));
+ }
+ }
+
+ if (m_args.count(g_argGas))
+ handleGasEstimation(contract);
+
+ handleBytecode(contract);
+ handleSignatureHashes(contract);
+ handleMeta(DocumentationType::ABIInterface, contract);
+ handleMeta(DocumentationType::ABISolidityInterface, contract);
+ handleMeta(DocumentationType::NatspecDev, contract);
+ handleMeta(DocumentationType::NatspecUser, contract);
+ } // end of contracts iteration
+}
+
+}
+}
diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h
new file mode 100644
index 00000000..f79bc0be
--- /dev/null
+++ b/solc/CommandLineInterface.h
@@ -0,0 +1,74 @@
+/*
+ This file is part of cpp-ethereum.
+
+ cpp-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ cpp-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Lefteris <lefteris@ethdev.com>
+ * @date 2014
+ * Solidity command line interface.
+ */
+#pragma once
+
+#include <libsolidity/CompilerStack.h>
+#include <memory>
+#include <boost/program_options.hpp>
+
+namespace dev
+{
+namespace solidity
+{
+
+//forward declaration
+enum class DocumentationType: uint8_t;
+
+class CommandLineInterface
+{
+public:
+ CommandLineInterface() {}
+
+ /// Parse command line arguments and return false if we should not continue
+ bool parseArguments(int _argc, char** _argv);
+ /// Parse the files and create source code objects
+ bool processInput();
+ /// Perform actions on the input depending on provided compiler arguments
+ void actOnInput();
+
+private:
+ void handleCombinedJSON();
+ void handleAst(std::string const& _argStr);
+ void handleBinary(std::string const& _contract);
+ void handleOpcode(std::string const& _contract);
+ void handleBytecode(std::string const& _contract);
+ void handleSignatureHashes(std::string const& _contract);
+ void handleMeta(DocumentationType _type,
+ std::string const& _contract);
+ void handleGasEstimation(std::string const& _contract);
+
+
+ /// Create a file in the given directory
+ /// @arg _fileName the name of the file
+ /// @arg _data to be written
+ void createFile(std::string const& _fileName, std::string const& _data);
+
+ /// Compiler arguments variable map
+ boost::program_options::variables_map m_args;
+ /// map of input files to source code strings
+ std::map<std::string, std::string> m_sourceCodes;
+ /// Solidity compiler stack
+ std::unique_ptr<dev::solidity::CompilerStack> m_compiler;
+};
+
+}
+}
diff --git a/solc/docker_emscripten/Dockerfile b/solc/docker_emscripten/Dockerfile
new file mode 100644
index 00000000..881f602d
--- /dev/null
+++ b/solc/docker_emscripten/Dockerfile
@@ -0,0 +1,70 @@
+FROM ubuntu:14.04
+
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt-get update
+RUN apt-get upgrade -y
+
+# Ethereum dependencies
+RUN apt-get install -qy build-essential git cmake libcurl4-openssl-dev wget
+RUN apt-get install -qy automake libtool yasm scons
+
+RUN useradd -ms /bin/bash user
+USER user
+ENV HOME /home/user
+WORKDIR /home/user
+
+# Emscripten SDK
+RUN wget -c https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
+RUN tar xzf emsdk-portable.tar.gz
+WORKDIR /home/user/emsdk_portable
+RUN ./emsdk update && ./emsdk install latest && ./emsdk activate latest
+ENV PATH $PATH:/home/user/emsdk_portable:/home/user/emsdk_portable/clang/fastcomp/build_master_64/bin:/home/user/emsdk_portable/emscripten/master
+
+USER root
+RUN apt-get install -qy nodejs
+USER user
+RUN sed -i "s/NODE_JS = 'node'/NODE_JS = 'nodejs'/g" ~/.emscripten
+
+# CryptoPP
+WORKDIR /home/user
+RUN git clone https://github.com/mmoss/cryptopp.git
+WORKDIR /home/user/cryptopp
+RUN emcmake cmake -DCRYPTOPP_LIBRARY_TYPE=STATIC -DCRYPTOPP_RUNTIME_TYPE=STATIC && emmake make -j 4
+RUN ln -s . src/cryptopp
+
+# Boost
+WORKDIR /home/user
+RUN wget 'http://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2F1.57.0%2F&ts=1421887207&use_mirror=cznic' -O boost_1_57_0.tar.bz2
+RUN tar xjf boost_1_57_0.tar.bz2
+WORKDIR /home/user/boost_1_57_0
+RUN ./bootstrap.sh --with-libraries=thread,system,regex
+RUN sed -i 's/using gcc ;/using gcc : : \/home\/user\/emsdk_portable\/emscripten\/master\/em++ ;/g' ./project-config.jam
+RUN sed -i 's/$(archiver\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emar/g' ./tools/build/src/tools/gcc.jam
+RUN sed -i 's/$(ranlib\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emranlib/g' ./tools/build/src/tools/gcc.jam
+RUN ./b2 link=static variant=release threading=single runtime-link=static thread system regex
+
+# Json-CPP
+WORKDIR /home/user
+RUN git clone https://github.com/open-source-parsers/jsoncpp.git
+WORKDIR /home/user/jsoncpp
+RUN emcmake cmake -DJSONCPP_LIB_BUILD_STATIC=ON -DJSONCPP_LIB_BUILD_SHARED=OFF -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF -G "Unix Makefiles" .
+RUN emmake make
+
+## Build soljs
+WORKDIR /home/user
+ADD https://api.github.com/repos/ethereum/cpp-ethereum/git/refs/heads/develop unused.txt
+RUN git clone --depth=1 https://github.com/ethereum/cpp-ethereum
+WORKDIR /home/user/cpp-ethereum
+RUN git config --global user.email "me@example.com"
+RUN git config --global user.name "Jane Doe"
+ADD https://api.github.com/repos/chriseth/cpp-ethereum/git/refs/heads/solidity-js unused2.txt
+RUN git remote add -f solidityjs https://github.com/chriseth/cpp-ethereum
+# TODO this should be a proper merge but somehow causes problems
+# NOTE that we only get the latest commit of that branch
+RUN git cherry-pick solidityjs/solidity-js
+RUN emcmake cmake -DMINER=0 -DETHKEY=0 -DSERPENT=0 -DTESTS=0 -DETHASHCL=0 -DJSCONSOLE=0 -DEVMJIT=0 -DETH_STATIC=1 -DSOLIDITY=1 -DGUI=0 -DCMAKE_CXX_COMPILER=/home/user/emsdk_portable/emscripten/master/em++ -DCMAKE_C_COMPILER=/home/user/emsdk_portable/emscripten/master/emcc
+RUN emmake make -j 6 soljson
+
+WORKDIR /home/user/cpp-ethereum/solc
+ENTRYPOINT cat soljson.js
+
diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp
new file mode 100644
index 00000000..bde13762
--- /dev/null
+++ b/solc/jsonCompiler.cpp
@@ -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 2014
+ * JSON interface for the solidity compiler to be used from Javascript.
+ */
+
+#include <string>
+#include <iostream>
+#include <json/json.h>
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonData.h>
+#include <libdevcore/CommonIO.h>
+#include <libevmcore/Instruction.h>
+#include <libevmcore/Params.h>
+#include <libsolidity/Scanner.h>
+#include <libsolidity/Parser.h>
+#include <libsolidity/ASTPrinter.h>
+#include <libsolidity/NameAndTypeResolver.h>
+#include <libsolidity/Exceptions.h>
+#include <libsolidity/CompilerStack.h>
+#include <libsolidity/SourceReferenceFormatter.h>
+#include <libsolidity/ASTJsonConverter.h>
+
+using namespace std;
+using namespace dev;
+using namespace solidity;
+
+string formatError(Exception const& _exception, string const& _name, CompilerStack const& _compiler)
+{
+ ostringstream errorOutput;
+ SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _compiler);
+
+ Json::Value output(Json::objectValue);
+ output["error"] = errorOutput.str();
+ return Json::FastWriter().write(output);
+}
+
+Json::Value functionHashes(ContractDefinition const& _contract)
+{
+ Json::Value functionHashes(Json::objectValue);
+ for (auto const& it: _contract.getInterfaceFunctions())
+ functionHashes[it.second->externalSignature()] = toHex(it.first.ref());
+ return functionHashes;
+}
+
+Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
+{
+ if (_gas.isInfinite || _gas.value > std::numeric_limits<Json::LargestUInt>::max())
+ return Json::Value(Json::nullValue);
+ else
+ return Json::Value(Json::LargestUInt(_gas.value));
+}
+
+Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract)
+{
+ Json::Value gasEstimates(Json::objectValue);
+ using Gas = GasEstimator::GasConsumption;
+ if (!_compiler.getAssemblyItems(_contract) && !_compiler.getRuntimeAssemblyItems(_contract))
+ return gasEstimates;
+ if (eth::AssemblyItems const* items = _compiler.getAssemblyItems(_contract))
+ {
+ Gas gas = GasEstimator::functionalEstimation(*items);
+ u256 bytecodeSize(_compiler.getRuntimeBytecode(_contract).size());
+ Json::Value creationGas(Json::arrayValue);
+ creationGas[0] = gasToJson(gas);
+ creationGas[1] = gasToJson(bytecodeSize * eth::c_createDataGas);
+ gasEstimates["creation"] = creationGas;
+ }
+ if (eth::AssemblyItems const* items = _compiler.getRuntimeAssemblyItems(_contract))
+ {
+ ContractDefinition const& contract = _compiler.getContractDefinition(_contract);
+ Json::Value externalFunctions(Json::objectValue);
+ for (auto it: contract.getInterfaceFunctions())
+ {
+ string sig = it.second->externalSignature();
+ externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig));
+ }
+ if (contract.getFallbackFunction())
+ externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID"));
+ gasEstimates["external"] = externalFunctions;
+ Json::Value internalFunctions(Json::objectValue);
+ for (auto const& it: contract.getDefinedFunctions())
+ {
+ if (it->isPartOfExternalInterface() || it->isConstructor())
+ continue;
+ size_t entry = _compiler.getFunctionEntryPoint(_contract, *it);
+ GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
+ if (entry > 0)
+ gas = GasEstimator::functionalEstimation(*items, entry, *it);
+ FunctionType type(*it);
+ string sig = it->getName() + "(";
+ auto end = type.getParameterTypes().end();
+ for (auto it = type.getParameterTypes().begin(); it != end; ++it)
+ sig += (*it)->toString() + (it + 1 == end ? "" : ",");
+ sig += ")";
+ internalFunctions[sig] = gasToJson(gas);
+ }
+ gasEstimates["internal"] = internalFunctions;
+ }
+ return gasEstimates;
+}
+
+string compile(string _input, bool _optimize)
+{
+ StringMap sources;
+ sources[""] = _input;
+
+ Json::Value output(Json::objectValue);
+ CompilerStack compiler;
+ try
+ {
+ compiler.compile(_input, _optimize);
+ }
+ catch (ParserError const& exception)
+ {
+ return formatError(exception, "Parser error", compiler);
+ }
+ catch (DeclarationError const& exception)
+ {
+ return formatError(exception, "Declaration error", compiler);
+ }
+ catch (TypeError const& exception)
+ {
+ return formatError(exception, "Type error", compiler);
+ }
+ catch (CompilerError const& exception)
+ {
+ return formatError(exception, "Compiler error", compiler);
+ }
+ catch (InternalCompilerError const& exception)
+ {
+ return formatError(exception, "Internal compiler error", compiler);
+ }
+ catch (DocstringParsingError const& exception)
+ {
+ return formatError(exception, "Documentation parsing error", compiler);
+ }
+ catch (Exception const& exception)
+ {
+ output["error"] = "Exception during compilation: " + boost::diagnostic_information(exception);
+ return Json::FastWriter().write(output);
+ }
+ catch (...)
+ {
+ output["error"] = "Unknown exception during compilation.";
+ return Json::FastWriter().write(output);
+ }
+
+ output["contracts"] = Json::Value(Json::objectValue);
+ for (string const& contractName: compiler.getContractNames())
+ {
+ Json::Value contractData(Json::objectValue);
+ contractData["solidity_interface"] = compiler.getSolidityInterface(contractName);
+ contractData["interface"] = compiler.getInterface(contractName);
+ contractData["bytecode"] = toHex(compiler.getBytecode(contractName));
+ contractData["opcodes"] = eth::disassemble(compiler.getBytecode(contractName));
+ contractData["functionHashes"] = functionHashes(compiler.getContractDefinition(contractName));
+ contractData["gasEstimates"] = estimateGas(compiler, contractName);
+ ostringstream unused;
+ contractData["assembly"] = compiler.streamAssembly(unused, contractName, sources, true);
+ output["contracts"][contractName] = contractData;
+ }
+
+ output["sources"] = Json::Value(Json::objectValue);
+ output["sources"][""] = Json::Value(Json::objectValue);
+ output["sources"][""]["AST"] = ASTJsonConverter(compiler.getAST("")).json();
+
+ return Json::FastWriter().write(output);
+}
+
+static string outputBuffer;
+
+extern "C"
+{
+extern char const* compileJSON(char const* _input, bool _optimize)
+{
+ outputBuffer = compile(_input, _optimize);
+ return outputBuffer.c_str();
+}
+}
diff --git a/solc/main.cpp b/solc/main.cpp
new file mode 100644
index 00000000..c5f72980
--- /dev/null
+++ b/solc/main.cpp
@@ -0,0 +1,35 @@
+/*
+ This file is part of cpp-ethereum.
+
+ cpp-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ cpp-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2014
+ * Solidity commandline compiler.
+ */
+
+#include "CommandLineInterface.h"
+
+int main(int argc, char** argv)
+{
+ dev::solidity::CommandLineInterface cli;
+ if (!cli.parseArguments(argc, argv))
+ return 1;
+ if (!cli.processInput())
+ return 1;
+ cli.actOnInput();
+
+ return 0;
+}