[libevmasm] Add support to import evm assembly json.

This commit is contained in:
Alexander Arlt 2022-11-01 23:58:45 -05:00
parent 69c034b1c0
commit 108490e630
57 changed files with 1099 additions and 63 deletions

View File

@ -34,13 +34,15 @@
#include <liblangutil/CharStream.h> #include <liblangutil/CharStream.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <json/json.h> #include <libsolutil/JSON.h>
#include <libsolutil/StringUtils.h>
#include <range/v3/algorithm/any_of.hpp> #include <range/v3/algorithm/any_of.hpp>
#include <range/v3/view/enumerate.hpp> #include <range/v3/view/enumerate.hpp>
#include <fstream> #include <fstream>
#include <limits> #include <limits>
#include <iterator>
using namespace solidity; using namespace solidity;
using namespace solidity::evmasm; using namespace solidity::evmasm;
@ -73,6 +75,207 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
} }
} }
void Assembly::importAssemblyItemsFromJSON(Json::Value const& _code)
{
solAssert(m_items.empty());
solRequire(_code.isArray(), AssemblyImportException, "Supplied JSON is not an array.");
for (auto current = begin(_code); current != end(_code); ++current)
{
auto const& item = m_items.emplace_back(createAssemblyItemFromJSON(*current, m_sourceList));
if (item == Instruction::JUMPDEST)
solThrow(AssemblyImportException, "JUMPDEST instruction without a tag");
else if (item.type() == AssemblyItemType::Tag)
{
++current;
if (current != end(_code) && createAssemblyItemFromJSON(*current, m_sourceList) != Instruction::JUMPDEST)
solThrow(AssemblyImportException, "JUMPDEST expected after tag.");
}
}
}
AssemblyItem Assembly::createAssemblyItemFromJSON(Json::Value const& _json, std::vector<std::string> const& _sourceList)
{
solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object.");
static set<string> const validMembers{"name", "begin", "end", "source", "value", "modifierDepth", "jumpType"};
for (auto const& member: _json.getMemberNames())
solRequire(
validMembers.count(member),
AssemblyImportException,
"Unknown member '" + member + "'. Valid members are " +
solidity::util::joinHumanReadable(validMembers, ", ") + "."
);
solRequire(isOfType<string>(_json["name"]), AssemblyImportException, "Member 'name' missing or not of type string.");
solRequire(isOfTypeIfExists<int>(_json, "begin"), AssemblyImportException, "Optional member 'begin' not of type int.");
solRequire(isOfTypeIfExists<int>(_json, "end"), AssemblyImportException, "Optional member 'end' not of type int.");
solRequire(isOfTypeIfExists<int>(_json, "source"), AssemblyImportException, "Optional member 'source' not of type int.");
solRequire(isOfTypeIfExists<string>(_json, "value"), AssemblyImportException, "Optional member 'value' not of type string.");
solRequire(
isOfTypeIfExists<int>(_json, "modifierDepth"),
AssemblyImportException,
"Optional member 'modifierDepth' not of type int."
);
solRequire(
isOfTypeIfExists<string>(_json, "jumpType"),
AssemblyImportException,
"Optional member 'jumpType' not of type string."
);
string name = get<string>(_json["name"]);
solRequire(!name.empty(), AssemblyImportException, "Member 'name' was empty.");
SourceLocation location;
location.start = get<int>(_json["begin"]);
location.end = get<int>(_json["end"]);
int srcIndex = getOrDefault<int>(_json["source"], -1);
size_t modifierDepth = static_cast<size_t>(getOrDefault<int>(_json["modifierDepth"], 0));
string value = getOrDefault<string>(_json["value"], "");
string jumpType = getOrDefault<string>(_json["jumpType"], "");
auto updateUsedTags = [&](u256 const& data)
{
m_usedTags = max(m_usedTags, static_cast<unsigned>(data) + 1);
return data;
};
auto storeImmutableHash = [&](string const& _immutableName) -> h256
{
h256 hash(util::keccak256(_immutableName));
solAssert(m_immutables.count(hash) == 0 || m_immutables[hash] == _immutableName);
m_immutables[hash] = _immutableName;
return hash;
};
auto storeLibraryHash = [&](string const& _libraryName) -> h256
{
h256 hash(util::keccak256(_libraryName));
solAssert(m_libraries.count(hash) == 0 || m_libraries[hash] == _libraryName);
m_libraries[hash] = _libraryName;
return hash;
};
auto requireValueDefinedForInstruction = [&](string const& _name, string const& _value)
{
solRequire(
!_value.empty(),
AssemblyImportException,
"Member 'value' was not defined for instruction '" + _name + "', but the instruction needs a value."
);
};
auto requireValueUndefinedForInstruction = [&](string const& _name, string const& _value)
{
solRequire(
_value.empty(),
AssemblyImportException,
"Member 'value' defined for instruction '" + _name + "', but the instruction does not need a value."
);
};
solRequire(srcIndex >= -1 && srcIndex < static_cast<int>(_sourceList.size()), AssemblyImportException, "srcIndex out of bound.");
if (srcIndex != -1)
location.sourceName = std::make_shared<std::string>(_sourceList[static_cast<size_t>(srcIndex)]);
AssemblyItem result(0);
if (c_instructions.count(name))
{
AssemblyItem item{c_instructions.at(name), location};
if (!jumpType.empty())
{
if (item.instruction() == Instruction::JUMP || item.instruction() == Instruction::JUMPI)
item.setJumpType(jumpType);
else
solThrow(
AssemblyImportException,
"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')"
);
}
InstructionInfo info = instructionInfo(item.instruction(), m_evmVersion);
if (info.args == 0)
requireValueUndefinedForInstruction(name, value);
result = item;
}
else
{
solRequire(
jumpType.empty(),
AssemblyImportException,
"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')"
);
if (name == "PUSH")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::Push, u256("0x" + value)};
}
else if (name == "PUSH [ErrorTag]")
{
requireValueUndefinedForInstruction(name, value);
result = {AssemblyItemType::PushTag, 0};
}
else if (name == "PUSH [tag]")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushTag, updateUsedTags(u256(value))};
}
else if (name == "PUSH [$]")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushSub, u256("0x" + value)};
}
else if (name == "PUSH #[$]")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushSubSize, u256("0x" + value)};
}
else if (name == "PUSHSIZE")
{
requireValueUndefinedForInstruction(name, value);
result = {AssemblyItemType::PushProgramSize, 0};
}
else if (name == "PUSHLIB")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushLibraryAddress, storeLibraryHash(value)};
}
else if (name == "PUSHDEPLOYADDRESS")
{
requireValueUndefinedForInstruction(name, value);
result = {AssemblyItemType::PushDeployTimeAddress, 0};
}
else if (name == "PUSHIMMUTABLE")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushImmutable, storeImmutableHash(value)};
}
else if (name == "ASSIGNIMMUTABLE")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::AssignImmutable, storeImmutableHash(value)};
}
else if (name == "tag")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::Tag, updateUsedTags(u256(value))};
}
else if (name == "PUSH data")
{
requireValueDefinedForInstruction(name, value);
result = {AssemblyItemType::PushData, u256("0x" + value)};
}
else if (name == "VERBATIM")
{
requireValueUndefinedForInstruction(name, value);
AssemblyItem item(fromHex(value), 0, 0);
result = item;
}
else
solThrow(InvalidOpcode, "Invalid opcode: " + name);
}
result.setLocation(location);
result.m_modifierDepth = modifierDepth;
return result;
}
namespace namespace
{ {
@ -221,7 +424,7 @@ std::string Assembly::assemblyString(
return tmp.str(); return tmp.str();
} }
Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList) const Json::Value Assembly::assemblyJSON(bool _includeSourceList) const
{ {
Json::Value root; Json::Value root;
root[".code"] = Json::arrayValue; root[".code"] = Json::arrayValue;
@ -231,9 +434,9 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc
int sourceIndex = -1; int sourceIndex = -1;
if (item.location().sourceName) if (item.location().sourceName)
{ {
auto iter = _sourceIndices.find(*item.location().sourceName); for (size_t index = 0; index < m_sourceList.size(); ++index)
if (iter != _sourceIndices.end()) if (m_sourceList[index] == *item.location().sourceName)
sourceIndex = static_cast<int>(iter->second); sourceIndex = static_cast<int>(index);
} }
auto [name, data] = item.nameAndData(m_evmVersion); auto [name, data] = item.nameAndData(m_evmVersion);
@ -271,8 +474,8 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc
{ {
root["sourceList"] = Json::arrayValue; root["sourceList"] = Json::arrayValue;
Json::Value& jsonSourceList = root["sourceList"]; Json::Value& jsonSourceList = root["sourceList"];
for (auto const& [name, index]: _sourceIndices) for (int index = 0; index < static_cast<int>(m_sourceList.size()); ++index)
jsonSourceList[index] = name; jsonSourceList[index] = m_sourceList[static_cast<size_t>(index)];
} }
if (!m_data.empty() || !m_subs.empty()) if (!m_data.empty() || !m_subs.empty())
@ -285,9 +488,10 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc
for (size_t i = 0; i < m_subs.size(); ++i) for (size_t i = 0; i < m_subs.size(); ++i)
{ {
std::stringstream hexStr; stringstream hexStr;
hexStr << std::hex << i; hexStr << hex << i;
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices, /*_includeSourceList = */false); m_subs[i]->setSourceList(m_sourceList);
data[hexStr.str()] = m_subs[i]->assemblyJSON(false);
} }
} }
@ -297,7 +501,84 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc
return root; return root;
} }
AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID) shared_ptr<Assembly> Assembly::fromJSON(Json::Value const& _json, vector<string> const& _sourceList, int _level)
{
solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object.");
static set<string> const validMembers{".code", ".data", ".auxdata", "sourceList"};
for (auto const& attribute: _json.getMemberNames())
solRequire(validMembers.count(attribute), AssemblyImportException, "Unknown attribute '" + attribute + "'.");
solRequire(_json.isMember(".code"), AssemblyImportException, "Member '.code' does not exist.");
solRequire(_json[".code"].isArray(), AssemblyImportException, "Member '.code' is not an array.");
for (auto const& codeItem: _json[".code"])
solRequire(codeItem.isObject(), AssemblyImportException, "Item of '.code' array is not an object.");
if (_level == 0)
{
if (_json.isMember("sourceList"))
{
solRequire(_json["sourceList"].isArray(), AssemblyImportException, "Optional member 'sourceList' is not an array.");
for (auto const& sourceListItem: _json["sourceList"])
solRequire(sourceListItem.isString(), AssemblyImportException, "Item of 'sourceList' array is not of type string.");
}
} else
solRequire(
!_json.isMember("sourceList"),
AssemblyImportException,
"Member 'sourceList' is only allowed in root JSON object."
);
shared_ptr<Assembly> result = make_shared<Assembly>(langutil::EVMVersion(), _level == 0, "");
if (_json.isMember("sourceList"))
{
solAssert(_level == 0);
for (auto const& it: _json["sourceList"])
result->m_sourceList.emplace_back(it.asString());
} else
result->m_sourceList = _sourceList;
result->importAssemblyItemsFromJSON(_json[".code"]);
if (_json[".auxdata"])
{
solRequire(_json[".auxdata"].isString(), AssemblyImportException, "Optional member '.auxdata' is not of type string.");
bytes auxdata{fromHex(_json[".auxdata"].asString())};
solRequire(!auxdata.empty(), AssemblyImportException, "Optional member '.auxdata' is not a valid hexadecimal string.");
result->m_auxiliaryData = auxdata;
}
if (_json.isMember(".data"))
{
solRequire(_json[".data"].isObject(), AssemblyImportException, "Optional member '.data' is not an object.");
Json::Value const& data = _json[".data"];
for (Json::ValueConstIterator dataIter = data.begin(); dataIter != data.end(); dataIter++)
{
solRequire(dataIter.key().isString(), AssemblyImportException, "Key inside '.data' is not of type string.");
string dataItemID = dataIter.key().asString();
Json::Value const& code = data[dataItemID];
if (code.isString())
{
bytes data_value{fromHex(code.asString())};
solRequire(
!data_value.empty(),
AssemblyImportException,
"Member '.data' contains a value for '" + dataItemID + "' that is not a valid hexadecimal string.");
result->m_data[h256(fromHex(dataItemID))] = fromHex(code.asString());
}
else if (code.isObject())
{
shared_ptr<Assembly> subassembly(Assembly::fromJSON(code, result->m_sourceList, _level + 1));
solAssert(subassembly);
result->m_subs.emplace_back(make_shared<Assembly>(*subassembly));
// TODO: this shouldn't be enough for the general case.
result->encodeSubPath({0, 0});
}
else
solThrow(AssemblyImportException, "Key inside '.data' '" + dataItemID + "' can only be a valid hex-string or an object.");
}
}
return result;
}
AssemblyItem Assembly::namedTag(string const& _name, size_t _params, size_t _returns, optional<uint64_t> _sourceID)
{ {
assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); assertThrow(!_name.empty(), AssemblyException, "Empty named tag.");
if (m_namedTags.count(_name)) if (m_namedTags.count(_name))

View File

@ -151,10 +151,30 @@ public:
/// Create a JSON representation of the assembly. /// Create a JSON representation of the assembly.
Json::Value assemblyJSON( Json::Value assemblyJSON(
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>(),
bool _includeSourceList = true bool _includeSourceList = true
) const; ) const;
/// Loads the JSON representation of assembly.
/// @param _json JSON object containing assembly in the format produced by assemblyJSON().
/// @param _sourceList list of source names (used to supply the source-list from the root-assembly object to sub-assemblies).
/// @param _level this function might be called recursively, _level reflects the nesting level.
/// @returns Resulting Assembly object loaded from given json.
static std::shared_ptr<Assembly> fromJSON(
Json::Value const& _json,
std::vector<std::string> const& _sourceList = {},
int _level = 0
);
void setSourceList(std::vector<std::string> const& _sourceList)
{
m_sourceList = _sourceList;
}
std::vector<std::string> const& sourceList()
{
return m_sourceList;
}
/// Mark this assembly as invalid. Calling ``assemble`` on it will throw. /// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
void markAsInvalid() { m_invalid = true; } void markAsInvalid() { m_invalid = true; }
@ -171,6 +191,18 @@ protected:
unsigned codeSize(unsigned subTagSize) const; unsigned codeSize(unsigned subTagSize) const;
/// Add all assembly items from given JSON array. This function imports the items by iterating through
/// the code array. This method only works on clean Assembly objects that don't have any items defined yet.
/// @param _json JSON array that contains assembly items (e.g. json['.code'])
/// @param _sourceList list of source names.
void importAssemblyItemsFromJSON(Json::Value const& _code);
/// Creates an AssemblyItem from a given JSON representation.
/// @param _json JSON object that consists a single assembly item
/// @param _sourceList list of source names.
/// @returns AssemblyItem of _json argument.
AssemblyItem createAssemblyItemFromJSON(Json::Value const& _json, std::vector<std::string> const& _sourceList);
private: private:
bool m_invalid = false; bool m_invalid = false;
@ -217,7 +249,7 @@ protected:
/// Internal name of the assembly object, only used with the Yul backend /// Internal name of the assembly object, only used with the Yul backend
/// currently /// currently
std::string m_name; std::string m_name;
std::vector<std::string> m_sourceList;
langutil::SourceLocation m_currentSourceLocation; langutil::SourceLocation m_currentSourceLocation;
public: public:

View File

@ -247,6 +247,18 @@ std::string AssemblyItem::getJumpTypeAsString() const
} }
} }
void AssemblyItem::setJumpType(string const& _jumpType)
{
if (_jumpType == "[in]")
m_jumpType = JumpType::IntoFunction;
else if (_jumpType == "[out]")
m_jumpType = JumpType::OutOfFunction;
else if (_jumpType.empty())
m_jumpType = JumpType::Ordinary;
else
solThrow(AssemblyImportException, "Invalid jump type.");
}
std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
{ {
std::string text; std::string text;

View File

@ -174,6 +174,7 @@ public:
langutil::SourceLocation const& location() const { return m_location; } langutil::SourceLocation const& location() const { return m_location; }
void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; }
void setJumpType(std::string const& _jumpType);
JumpType getJumpType() const { return m_jumpType; } JumpType getJumpType() const { return m_jumpType; }
std::string getJumpTypeAsString() const; std::string getJumpTypeAsString() const;

View File

@ -3,6 +3,8 @@ set(sources
Assembly.h Assembly.h
AssemblyItem.cpp AssemblyItem.cpp
AssemblyItem.h AssemblyItem.h
EVMAssemblyStack.cpp
EVMAssemblyStack.h
BlockDeduplicator.cpp BlockDeduplicator.cpp
BlockDeduplicator.h BlockDeduplicator.h
CommonSubexpressionEliminator.cpp CommonSubexpressionEliminator.cpp

View File

@ -0,0 +1,58 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libevmasm/EVMAssemblyStack.h>
#include <libsolutil/JSON.h>
#include <liblangutil/Exceptions.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <utility>
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;
using namespace std;
namespace solidity::evmasm
{
bool EVMAssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
{
m_name = _sourceName;
if (jsonParseStrict(_source, m_json))
{
m_evmAssembly = evmasm::Assembly::fromJSON(m_json);
return m_evmAssembly != nullptr;
}
return false;
}
void EVMAssemblyStack::assemble()
{
solAssert(m_evmAssembly->isCreation());
m_object = m_evmAssembly->assemble();
if (m_evmAssembly->numSubs() > 0)
{
m_evmRuntimeAssembly = make_shared<evmasm::Assembly>(m_evmAssembly->sub(0));
solAssert(m_evmRuntimeAssembly && !m_evmRuntimeAssembly->isCreation());
m_runtimeObject = m_evmRuntimeAssembly->assemble();
}
}
} // namespace solidity::evmasm

View File

@ -0,0 +1,68 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <map>
#include <string>
#include <libsolutil/JSON.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/LinkerObject.h>
namespace solidity::evmasm
{
class EVMAssemblyStack
{
public:
explicit EVMAssemblyStack(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) {}
/// Runs parsing and analysis, returns false if input cannot be assembled.
/// Multiple calls overwrite the previous state.
bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source);
void assemble();
std::string const& name() const { return m_name; }
evmasm::LinkerObject const& object() const { return m_object; }
std::shared_ptr<evmasm::Assembly> const& evmAssembly() const { return m_evmAssembly; }
evmasm::LinkerObject const& runtimeObject() const { return m_runtimeObject; }
std::shared_ptr<evmasm::Assembly> const& evmRuntimeAssembly() const { return m_evmRuntimeAssembly; }
Json::Value json() const
{
return m_json;
}
private:
langutil::EVMVersion m_evmVersion;
std::string m_name;
Json::Value m_json;
std::shared_ptr<evmasm::Assembly> m_evmAssembly;
std::shared_ptr<evmasm::Assembly> m_evmRuntimeAssembly;
evmasm::LinkerObject m_object; ///< Deployment object (includes the runtime sub-object).
evmasm::LinkerObject m_runtimeObject; ///< Runtime object.
};
} // namespace solidity::evmasm

View File

@ -28,6 +28,7 @@ namespace solidity::evmasm
{ {
struct AssemblyException: virtual util::Exception {}; struct AssemblyException: virtual util::Exception {};
struct AssemblyImportException: virtual AssemblyException {};
struct OptimizerException: virtual AssemblyException {}; struct OptimizerException: virtual AssemblyException {};
struct StackTooDeepException: virtual OptimizerException {}; struct StackTooDeepException: virtual OptimizerException {};
struct ItemNotAvailableException: virtual OptimizerException {}; struct ItemNotAvailableException: virtual OptimizerException {};

View File

@ -72,6 +72,7 @@
#include <liblangutil/SemVerHandler.h> #include <liblangutil/SemVerHandler.h>
#include <liblangutil/SourceReferenceFormatter.h> #include <liblangutil/SourceReferenceFormatter.h>
#include <libevmasm/EVMAssemblyStack.h>
#include <libevmasm/Exceptions.h> #include <libevmasm/Exceptions.h>
#include <libsolutil/SwarmHash.h> #include <libsolutil/SwarmHash.h>
@ -438,6 +439,27 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
storeContractDefinitions(); storeContractDefinitions();
} }
void CompilerStack::importFromEVMAssemblyStack(std::string const& _sourceName, std::string const& _source)
{
solRequire(m_stackState == Empty, CompilerError, "");
m_evmAssemblyStack = std::make_unique<evmasm::EVMAssemblyStack>(m_evmVersion);
Json::Value evmAsmJson;
if (m_evmAssemblyStack->parseAndAnalyze(_sourceName, _source))
{
m_evmAssemblyStack->assemble();
string const name{m_evmAssemblyStack->name()};
Json::Value const& json = m_evmAssemblyStack->json();
m_sourceJsons[name] = json;
Contract& contract = m_contracts[name];
contract.evmAssembly = m_evmAssemblyStack->evmAssembly();
contract.evmRuntimeAssembly= m_evmAssemblyStack->evmRuntimeAssembly();
contract.object = m_evmAssemblyStack->object();
contract.runtimeObject = m_evmAssemblyStack->runtimeObject();
m_stackState = CompilationSuccessful;
}
}
bool CompilerStack::analyze() bool CompilerStack::analyze()
{ {
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed) if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed)
@ -674,6 +696,7 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con
bool CompilerStack::compile(State _stopAfter) bool CompilerStack::compile(State _stopAfter)
{ {
solAssert(m_compilationSourceType != CompilationSourceType::EvmAssemblyJSON);
m_stopAfter = _stopAfter; m_stopAfter = _stopAfter;
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
if (!parseAndAnalyze(_stopAfter)) if (!parseAndAnalyze(_stopAfter))
@ -716,7 +739,7 @@ bool CompilerStack::compile(State _stopAfter)
{ {
if ( if (
SourceLocation const* sourceLocation = SourceLocation const* sourceLocation =
boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError) boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError)
) )
{ {
string const* comment = _unimplementedError.comment(); string const* comment = _unimplementedError.comment();
@ -957,7 +980,13 @@ Json::Value CompilerStack::assemblyJSON(string const& _contractName) const
Contract const& currentContract = contract(_contractName); Contract const& currentContract = contract(_contractName);
if (currentContract.evmAssembly) if (currentContract.evmAssembly)
return currentContract.evmAssembly->assemblyJSON(sourceIndices()); {
std::vector<std::string> sources = sourceNames();
if (std::find(sources.begin(), sources.end(), CompilerContext::yulUtilityFileName()) == sources.end())
sources.emplace_back(CompilerContext::yulUtilityFileName());
currentContract.evmAssembly->setSourceList(sources);
return currentContract.evmAssembly->assemblyJSON();
}
else else
return Json::Value(); return Json::Value();
} }
@ -971,10 +1000,21 @@ map<string, unsigned> CompilerStack::sourceIndices() const
{ {
map<string, unsigned> indices; map<string, unsigned> indices;
unsigned index = 0; unsigned index = 0;
for (auto const& s: m_sources) if (m_evmAssemblyStack)
indices[s.first] = index++; {
solAssert(!indices.count(CompilerContext::yulUtilityFileName()), ""); for (auto const& s: m_evmAssemblyStack->evmAssembly()->sourceList())
indices[CompilerContext::yulUtilityFileName()] = index++; if (s != CompilerContext::yulUtilityFileName())
indices[s] = index++;
}
else
{
for (auto const& s: m_sources)
if (s.first != CompilerContext::yulUtilityFileName())
indices[s.first] = index++;
}
if (indices.find(CompilerContext::yulUtilityFileName()) == indices.end())
indices[CompilerContext::yulUtilityFileName()] = index++;
return indices; return indices;
} }
@ -1589,6 +1629,11 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
case CompilationSourceType::SolidityAST: case CompilationSourceType::SolidityAST:
sourceType = "SolidityAST"; sourceType = "SolidityAST";
break; break;
case CompilationSourceType::EvmAssemblyJSON:
sourceType = "EvmAssemblyJson";
break;
default:
solAssert(false);
} }
meta["language"] = sourceType; meta["language"] = sourceType;
meta["compiler"]["version"] = VersionStringStrict; meta["compiler"]["version"] = VersionStringStrict;

View File

@ -42,6 +42,7 @@
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <libevmasm/LinkerObject.h> #include <libevmasm/LinkerObject.h>
#include <libevmasm/EVMAssemblyStack.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
@ -121,7 +122,9 @@ public:
/// Regular compilation from Solidity source files. /// Regular compilation from Solidity source files.
Solidity, Solidity,
/// Compilation from an imported Solidity AST. /// Compilation from an imported Solidity AST.
SolidityAST SolidityAST,
/// Compilation from an imported EVM Assembly JSON.
EvmAssemblyJSON
}; };
/// Creates a new compiler stack. /// Creates a new compiler stack.
@ -230,6 +233,8 @@ public:
/// Will throw errors if the import fails /// Will throw errors if the import fails
void importASTs(std::map<std::string, Json::Value> const& _sources); void importASTs(std::map<std::string, Json::Value> const& _sources);
void importFromEVMAssemblyStack(std::string const& _sourceName, std::string const& _source);
/// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving,
/// typechecking, staticAnalysis) on previously parsed sources. /// typechecking, staticAnalysis) on previously parsed sources.
/// @returns false on error. /// @returns false on error.
@ -524,6 +529,7 @@ private:
/// If this is true, the stack will refuse to generate code. /// If this is true, the stack will refuse to generate code.
bool m_hasError = false; bool m_hasError = false;
MetadataFormat m_metadataFormat = defaultMetadataFormat(); MetadataFormat m_metadataFormat = defaultMetadataFormat();
std::unique_ptr<evmasm::EVMAssemblyStack> m_evmAssemblyStack;
}; };
} }

View File

@ -23,6 +23,18 @@
# ast import/export tests: # ast import/export tests:
# - first exporting a .sol file to JSON, then loading it into the compiler # - first exporting a .sol file to JSON, then loading it into the compiler
# and exporting it again. The second JSON should be identical to the first. # and exporting it again. The second JSON should be identical to the first.
#
# evm-assembly import/export tests:
# - first a .sol file will be exported to a combined json file, containing outputs
# for "asm" "bin" "bin-runtime" "opcodes" "srcmap" and "srcmap-runtime" (expected output).
# The "asm" output will then be used as import, where its output "bin" "bin-runtime"
# "opcodes" "srcmap" "srcmap-runtime" (obtained output) will be compared with the expected output.
# The expected output needs to be identical with the obtained output.
#
# Additionally to this, the direct import/export is tested by importing an
# evm-assembly json with --import-asm-json and directly exporting it again with
# --asm-json using the same solc invocation. The original asm json file used for the
# import and the resulting exported asm json file need to be identical.
set -euo pipefail set -euo pipefail
@ -40,7 +52,7 @@ source "${REPO_ROOT}/scripts/common.sh"
function print_usage function print_usage
{ {
echo "Usage: ${0} ast [--exit-on-error|--help]." echo "Usage: ${0} ast|evm-assembly [--exit-on-error|--help]."
} }
function print_used_commands function print_used_commands
@ -81,6 +93,7 @@ for PARAM in "$@"
do do
case "$PARAM" in case "$PARAM" in
ast) check_import_test_type_unset ; IMPORT_TEST_TYPE="ast" ;; ast) check_import_test_type_unset ; IMPORT_TEST_TYPE="ast" ;;
evm-assembly) check_import_test_type_unset ; IMPORT_TEST_TYPE="evm-assembly" ;;
--help) print_usage ; exit 0 ;; --help) print_usage ; exit 0 ;;
--exit-on-error) EXIT_ON_ERROR=1 ;; --exit-on-error) EXIT_ON_ERROR=1 ;;
*) fail "Unknown option '$PARAM'. Aborting. $(print_usage)" ;; *) fail "Unknown option '$PARAM'. Aborting. $(print_usage)" ;;
@ -89,6 +102,7 @@ done
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON" ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
SEMANTICTESTS_DIR="${REPO_ROOT}/test/libsolidity/semanticTests"
FAILED=0 FAILED=0
UNCOMPILABLE=0 UNCOMPILABLE=0
@ -153,6 +167,125 @@ function test_ast_import_export_equivalence
TESTED=$((TESTED + 1)) TESTED=$((TESTED + 1))
} }
function test_evmjson_import_export_equivalence
{
local sol_file="$1"
local input_files=( "${@:2}" )
local outputs=( "asm" "bin" "bin-runtime" "opcodes" "srcmap" "srcmap-runtime" )
local export_command=("$SOLC" --combined-json "$(IFS=, ; echo "${outputs[*]}")" --pretty-json --json-indent 4 "${input_files[@]}")
local success=1
if ! "${export_command[@]}" > expected.json 2> expected.error
then
success=0
printError "ERROR: (export) EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${export_command[*]}" ""
return 1
fi
fi
if ! "${export_command[@]}" "--optimize" > expected.optimize.json 2> expected.optimize.error
then
success=0
printError "ERROR: (export with --optimize) EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${export_command[*]} --optimize" ""
return 1
fi
fi
for input_json in "expected.json" "expected.optimize.json"
do
local optimize_param=""
if [[ "$input_json" == "expected.optimize.json" ]]
then
optimize_param="--optimize"
fi
# Note that we have some test files, that only consist of free functions.
# Those files don't define any contracts, so the resulting JSON does not have any
# keys. In this case `jq` returns an error like `jq: error: null (null) has no keys`.
# To not get spammed by these errors, errors are redirected to /dev/null.
for contract in $(jq '.contracts | keys | .[]' "$input_json" 2> /dev/null)
do
for output in "${outputs[@]}"
do
jq --raw-output ".contracts.${contract}.\"${output}\"" "$input_json" > "expected.${output}.json"
done
assembly=$(cat expected.asm.json)
[[ $assembly != "" && $assembly != "null" ]] || continue
local import_command=("${SOLC}" --combined-json "bin,bin-runtime,opcodes,asm,srcmap,srcmap-runtime" --pretty-json --json-indent 4 --import-asm-json expected.asm.json)
if ! "${import_command[@]}" > obtained.json 2> obtained.error
then
success=0
printError "ERROR: (import) EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${export_command[*]} ${optimize_param}" "${import_command[*]}"
return 1
fi
fi
for output in "${outputs[@]}"
do
for obtained_contract in $(jq '.contracts | keys | .[]' obtained.json 2> /dev/null)
do
jq --raw-output ".contracts.${obtained_contract}.\"${output}\"" obtained.json > "obtained.${output}.json"
# compare expected and obtained evm assembly json
if ! diff_files "expected.${output}.json" "obtained.${output}.json"
then
success=0
printError "ERROR: (${output}) EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${export_command[*]} ${optimize_param}" "${import_command[*]}"
return 1
fi
fi
done
done
# direct export via --asm-json, if imported with --import-asm-json.
if ! "${SOLC}" --asm-json --import-asm-json expected.asm.json --pretty-json --json-indent 4 | tail -n+2 > obtained_direct_import_export.json 2> obtained_direct_import_export.error
then
success=0
printError "ERROR: (direct) EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${SOLC} --asm-json --import-asm-json expected.asm.json --pretty-json --json-indent 4 | tail -n+4" ""
return 1
fi
fi
# reformat json files using jq.
jq . expected.asm.json > expected.asm.json.pretty
jq . obtained_direct_import_export.json > obtained_direct_import_export.json.pretty
# compare expected and obtained evm assembly.
if ! diff_files expected.asm.json.pretty obtained_direct_import_export.json.pretty
then
success=0
printError "ERROR: EVM Assembly JSON reimport failed for ${sol_file}"
if (( EXIT_ON_ERROR == 1 ))
then
print_used_commands "$(pwd)" "${export_command[*]} ${optimize_param}" "${import_command[*]}"
return 1
fi
fi
done
done
if (( success == 1 ))
then
TESTED=$((TESTED + 1))
else
FAILED=$((FAILED + 1))
fi
}
# function tests whether exporting and importing again is equivalent. # function tests whether exporting and importing again is equivalent.
# Results are recorded by incrementing the FAILED or UNCOMPILABLE global variable. # Results are recorded by incrementing the FAILED or UNCOMPILABLE global variable.
# Also, in case of a mismatch a diff is printed # Also, in case of a mismatch a diff is printed
@ -168,6 +301,7 @@ function test_import_export_equivalence {
case "$IMPORT_TEST_TYPE" in case "$IMPORT_TEST_TYPE" in
ast) compile_test="--ast-compact-json" ;; ast) compile_test="--ast-compact-json" ;;
evm-assembly) compile_test="--bin" ;;
*) assertFail "Unknown import test type '${IMPORT_TEST_TYPE}'. Aborting." ;; *) assertFail "Unknown import test type '${IMPORT_TEST_TYPE}'. Aborting." ;;
esac esac
@ -181,6 +315,7 @@ function test_import_export_equivalence {
then then
case "$IMPORT_TEST_TYPE" in case "$IMPORT_TEST_TYPE" in
ast) test_ast_import_export_equivalence "${sol_file}" "${input_files[@]}" ;; ast) test_ast_import_export_equivalence "${sol_file}" "${input_files[@]}" ;;
evm-assembly) test_evmjson_import_export_equivalence "${sol_file}" "${input_files[@]}" ;;
*) assertFail "Unknown import test type '${IMPORT_TEST_TYPE}'. Aborting." ;; *) assertFail "Unknown import test type '${IMPORT_TEST_TYPE}'. Aborting." ;;
esac esac
else else
@ -191,7 +326,20 @@ function test_import_export_equivalence {
# and print some details about the corresponding solc invocation. # and print some details about the corresponding solc invocation.
if (( solc_return_code == 2 )) if (( solc_return_code == 2 ))
then then
fail "\n\nERROR: Uncaught Exception while executing '$SOLC ${compile_test} ${input_files[*]}':\n${output}\n" # For the evm-assembly import/export tests, this script uses only the
# old code generator. Some semantic test can only be compiled with
# --via-ir (some need to be additionally compiled with --optimize).
# The tests that are meant to be compiled with --via-ir are throwing
# an UnimplementedFeatureError exception (e.g. Copying of type struct C.S
# memory[] memory to storage not yet supported, Copying nested calldata
# dynamic arrays to storage is not implemented in the old code generator.)
# We will just ignore these kind of exceptions for now.
# However, any other exception will be treated as a fatal error and the
# script execution will be terminated with an error.
if [[ "$output" != *"UnimplementedFeatureError"* ]]
then
fail "\n\nERROR: Uncaught Exception while executing '$SOLC ${compile_test} ${input_files[*]}':\n${output}\n"
fi
fi fi
fi fi
} }
@ -203,6 +351,7 @@ command_available jq --version
case "$IMPORT_TEST_TYPE" in case "$IMPORT_TEST_TYPE" in
ast) TEST_DIRS=("${SYNTAXTESTS_DIR}" "${ASTJSONTESTS_DIR}") ;; ast) TEST_DIRS=("${SYNTAXTESTS_DIR}" "${ASTJSONTESTS_DIR}") ;;
evm-assembly) TEST_DIRS=("${SEMANTICTESTS_DIR}") ;;
*) assertFail "Import test type not defined. $(print_usage)" ;; *) assertFail "Import test type not defined. $(print_usage)" ;;
esac esac

View File

@ -93,6 +93,7 @@ namespace
set<frontend::InputMode> const CompilerInputModes{ set<frontend::InputMode> const CompilerInputModes{
frontend::InputMode::Compiler, frontend::InputMode::Compiler,
frontend::InputMode::CompilerWithASTImport, frontend::InputMode::CompilerWithASTImport,
frontend::InputMode::EVMAssemblerJSON
}; };
} // anonymous namespace } // anonymous namespace
@ -167,28 +168,58 @@ static bool coloredOutput(CommandLineOptions const& _options)
(_options.formatting.coloredOutput.has_value() && _options.formatting.coloredOutput.value()); (_options.formatting.coloredOutput.has_value() && _options.formatting.coloredOutput.value());
} }
void CommandLineInterface::handleEVMAssembly(string const& _contract)
{
solAssert(CompilerInputModes.count(m_options.input.mode) == 1);
if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson)
{
string assembly;
if (m_options.compiler.outputs.asmJson)
assembly = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(_contract)), m_options.formatting.json);
else
assembly = m_compiler->assemblyString(_contract, m_fileReader.sourceUnits());
if (!m_options.output.dir.empty())
createFile(
m_compiler->filesystemFriendlyName(_contract) +
(m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"),
assembly
);
else
sout() << "EVM assembly:" << endl << assembly << endl;
}
}
void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleBinary(string const& _contract)
{ {
solAssert(CompilerInputModes.count(m_options.input.mode) == 1); solAssert(CompilerInputModes.count(m_options.input.mode) == 1);
string binary;
string binaryRuntime;
if (m_options.compiler.outputs.binary)
binary = objectWithLinkRefsHex(m_compiler->object(_contract));
if (m_options.compiler.outputs.binaryRuntime)
binaryRuntime = objectWithLinkRefsHex(m_compiler->runtimeObject(_contract));
if (m_options.compiler.outputs.binary) if (m_options.compiler.outputs.binary)
{ {
if (!m_options.output.dir.empty()) if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", objectWithLinkRefsHex(m_compiler->object(_contract))); createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", binary);
else else
{ {
sout() << "Binary:" << endl; sout() << "Binary:" << endl;
sout() << objectWithLinkRefsHex(m_compiler->object(_contract)) << endl; sout() << binary << endl;
} }
} }
if (m_options.compiler.outputs.binaryRuntime) if (m_options.compiler.outputs.binaryRuntime)
{ {
if (!m_options.output.dir.empty()) if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", objectWithLinkRefsHex(m_compiler->runtimeObject(_contract))); createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", binaryRuntime);
else else
{ {
sout() << "Binary of the runtime part:" << endl; sout() << "Binary of the runtime part:" << endl;
sout() << objectWithLinkRefsHex(m_compiler->runtimeObject(_contract)) << endl; sout() << binaryRuntime << endl;
} }
} }
} }
@ -197,12 +228,14 @@ void CommandLineInterface::handleOpcode(string const& _contract)
{ {
solAssert(CompilerInputModes.count(m_options.input.mode) == 1); solAssert(CompilerInputModes.count(m_options.input.mode) == 1);
string opcodes{evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion)};
if (!m_options.output.dir.empty()) if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion)); createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", opcodes);
else else
{ {
sout() << "Opcodes:" << endl; sout() << "Opcodes:" << endl;
sout() << uppercase << evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion); sout() << uppercase << opcodes;
sout() << endl; sout() << endl;
} }
} }
@ -702,6 +735,12 @@ void CommandLineInterface::processInput()
compile(); compile();
outputCompilationResults(); outputCompilationResults();
break; break;
case InputMode::EVMAssemblerJSON:
assembleFromEvmAssemblyJson();
handleCombinedJSON();
handleBytecode("");
handleEVMAssembly("");
break;
} }
} }
@ -718,6 +757,23 @@ void CommandLineInterface::printLicense()
sout() << licenseText << endl; sout() << licenseText << endl;
} }
void CommandLineInterface::assembleFromEvmAssemblyJson()
{
solAssert(m_options.input.mode == InputMode::EVMAssemblerJSON);
try
{
solAssert(m_fileReader.sourceUnits().size() == 1);
auto const iter = m_fileReader.sourceUnits().begin();
m_compiler = make_unique<CompilerStack>(m_universalCallback.callback());
m_compiler->importFromEVMAssemblyStack(iter->first, iter->second);
}
catch (evmasm::AssemblyImportException const& _exception)
{
solThrow(CommandLineExecutionError, "Assembly Import Error: "s + _exception.what());
}
solRequire(m_compiler != nullptr, CommandLineExecutionError, "Assembly Import Error: Could not create compiler object.");
}
void CommandLineInterface::compile() void CommandLineInterface::compile()
{ {
solAssert(CompilerInputModes.count(m_options.input.mode) == 1); solAssert(CompilerInputModes.count(m_options.input.mode) == 1);
@ -884,8 +940,7 @@ void CommandLineInterface::handleCombinedJSON()
); );
if (m_options.compiler.combinedJsonRequests->funDebugRuntime && m_compiler->compilationSuccessful()) if (m_options.compiler.combinedJsonRequests->funDebugRuntime && m_compiler->compilationSuccessful())
contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData( contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData(
m_compiler->runtimeObject(contractName).functionDebugData m_compiler->runtimeObject(contractName).functionDebugData);
);
if (m_options.compiler.combinedJsonRequests->signatureHashes) if (m_options.compiler.combinedJsonRequests->signatureHashes)
contractData[g_strSignatureHashes] = m_compiler->interfaceSymbols(contractName)["methods"]; contractData[g_strSignatureHashes] = m_compiler->interfaceSymbols(contractName)["methods"];
if (m_options.compiler.combinedJsonRequests->natspecDev) if (m_options.compiler.combinedJsonRequests->natspecDev)
@ -894,10 +949,9 @@ void CommandLineInterface::handleCombinedJSON()
contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName); contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName);
} }
bool needsSourceList = bool needsSourceList = m_options.compiler.combinedJsonRequests->ast ||
m_options.compiler.combinedJsonRequests->ast || m_options.compiler.combinedJsonRequests->srcMap ||
m_options.compiler.combinedJsonRequests->srcMap || m_options.compiler.combinedJsonRequests->srcMapRuntime;
m_options.compiler.combinedJsonRequests->srcMapRuntime;
if (needsSourceList) if (needsSourceList)
{ {
// Indices into this array are used to abbreviate source names in source locations. // Indices into this array are used to abbreviate source names in source locations.
@ -1152,6 +1206,53 @@ void CommandLineInterface::assembleYul(yul::YulStack::Language _language, yul::Y
else else
serr() << "No text representation found." << endl; serr() << "No text representation found." << endl;
} }
if (m_options.compiler.outputs.asmJson)
{
shared_ptr<evmasm::Assembly> assembly{stack.assembleEVMWithDeployed().first};
if (assembly)
{
std::function<map<string, unsigned>(yul::Object const&)> collectSourceIndices =
[&](yul::Object const& _object) -> map<string, unsigned> {
map<string, unsigned> sourceIndices;
if (_object.debugData && _object.debugData->sourceNames.has_value())
for (auto const& iter: *_object.debugData->sourceNames)
sourceIndices[*iter.second] = iter.first;
for (auto const& sub: _object.subObjects)
{
auto subObject = dynamic_cast<yul::Object const*>(sub.get());
if (subObject)
for (auto const& [name, index]: collectSourceIndices(*subObject))
sourceIndices[name] = index;
}
return sourceIndices;
};
if (stack.parserResult() && stack.parserResult()->debugData)
{
map<string, unsigned> sourceIndices = collectSourceIndices(*stack.parserResult());
// if sourceIndices are empty here, there were no source locations annotated in the yul source.
// in this case, we just add the filename of the yul file itself here.
if (sourceIndices.empty())
sourceIndices[src.first] = 0;
size_t max_index = 0;
for (auto const& [name, index]: sourceIndices)
if (max_index < index)
max_index = index;
vector<string> sourceList(max_index + 1, "<unknown>");
for (auto const& [name, index]: sourceIndices)
sourceList[index] = name;
assembly->setSourceList(sourceList);
sout() << util::jsonPrint(
removeNullMembers(
assembly->assemblyJSON()
),
m_options.formatting.json
) << endl;
return;
}
}
serr() << "Could not create Assembly JSON representation." << endl;
}
} }
} }
@ -1180,19 +1281,7 @@ void CommandLineInterface::outputCompilationResults()
sout() << endl << "======= " << contract << " =======" << endl; sout() << endl << "======= " << contract << " =======" << endl;
// do we need EVM assembly? // do we need EVM assembly?
if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) handleEVMAssembly(contract);
{
string ret;
if (m_options.compiler.outputs.asmJson)
ret = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(contract)), m_options.formatting.json);
else
ret = m_compiler->assemblyString(contract, m_fileReader.sourceUnits());
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret);
else
sout() << "EVM assembly:" << endl << ret << endl;
}
if (m_options.compiler.estimateGas) if (m_options.compiler.estimateGas)
handleGasEstimation(contract); handleGasEstimation(contract);

View File

@ -24,6 +24,7 @@
#include <solc/CommandLineParser.h> #include <solc/CommandLineParser.h>
#include <libevmasm/EVMAssemblyStack.h>
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/DebugSettings.h> #include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/FileReader.h> #include <libsolidity/interface/FileReader.h>
@ -84,6 +85,7 @@ private:
void printVersion(); void printVersion();
void printLicense(); void printLicense();
void compile(); void compile();
void assembleFromEvmAssemblyJson();
void serveLSP(); void serveLSP();
void link(); void link();
void writeLinkedFiles(); void writeLinkedFiles();
@ -98,6 +100,7 @@ private:
void handleCombinedJSON(); void handleCombinedJSON();
void handleAst(); void handleAst();
void handleEVMAssembly(std::string const& _contract);
void handleBinary(std::string const& _contract); void handleBinary(std::string const& _contract);
void handleOpcode(std::string const& _contract); void handleOpcode(std::string const& _contract);
void handleIR(std::string const& _contract); void handleIR(std::string const& _contract);

View File

@ -52,6 +52,7 @@ static string const g_strExperimentalViaIR = "experimental-via-ir";
static string const g_strGas = "gas"; static string const g_strGas = "gas";
static string const g_strHelp = "help"; static string const g_strHelp = "help";
static string const g_strImportAst = "import-ast"; static string const g_strImportAst = "import-ast";
static string const g_strImportEvmAssemblerJson = "import-asm-json";
static string const g_strInputFile = "input-file"; static string const g_strInputFile = "input-file";
static string const g_strYul = "yul"; static string const g_strYul = "yul";
static string const g_strYulDialect = "yul-dialect"; static string const g_strYulDialect = "yul-dialect";
@ -142,6 +143,7 @@ static map<InputMode, string> const g_inputModeName = {
{InputMode::StandardJson, "standard JSON"}, {InputMode::StandardJson, "standard JSON"},
{InputMode::Linker, "linker"}, {InputMode::Linker, "linker"},
{InputMode::LanguageServer, "language server (LSP)"}, {InputMode::LanguageServer, "language server (LSP)"},
{InputMode::EVMAssemblerJSON, "EVM assembler (JSON format)"},
}; };
void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames) void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
@ -468,8 +470,15 @@ void CommandLineParser::parseOutputSelection()
CompilerOutputs::componentName(&CompilerOutputs::binary), CompilerOutputs::componentName(&CompilerOutputs::binary),
CompilerOutputs::componentName(&CompilerOutputs::irOptimized), CompilerOutputs::componentName(&CompilerOutputs::irOptimized),
CompilerOutputs::componentName(&CompilerOutputs::astCompactJson), CompilerOutputs::componentName(&CompilerOutputs::astCompactJson),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
};
static set<string> const evmAssemblyJsonImportModeOutputs = {
CompilerOutputs::componentName(&CompilerOutputs::asm_),
CompilerOutputs::componentName(&CompilerOutputs::binary),
CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime),
CompilerOutputs::componentName(&CompilerOutputs::opcodes),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
}; };
switch (_mode) switch (_mode)
{ {
case InputMode::Help: case InputMode::Help:
@ -480,6 +489,8 @@ void CommandLineParser::parseOutputSelection()
case InputMode::Compiler: case InputMode::Compiler:
case InputMode::CompilerWithASTImport: case InputMode::CompilerWithASTImport:
return util::contains(compilerModeOutputs, _outputName); return util::contains(compilerModeOutputs, _outputName);
case InputMode::EVMAssemblerJSON:
return util::contains(evmAssemblyJsonImportModeOutputs, _outputName);
case InputMode::Assembler: case InputMode::Assembler:
return util::contains(assemblerModeOutputs, _outputName); return util::contains(assemblerModeOutputs, _outputName);
case InputMode::StandardJson: case InputMode::StandardJson:
@ -664,6 +675,10 @@ General Information)").c_str(),
"Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by " "Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by "
"--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str() "--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str()
) )
(
g_strImportEvmAssemblerJson.c_str(),
"Import EVM assembly from JSON. Assumes input is in the format used by --asm-json."
)
( (
g_strLSP.c_str(), g_strLSP.c_str(),
"Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend " "Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend "
@ -932,6 +947,7 @@ void CommandLineParser::processArgs()
g_strYul, g_strYul,
g_strImportAst, g_strImportAst,
g_strLSP, g_strLSP,
g_strImportEvmAssemblerJson,
}); });
if (m_args.count(g_strHelp) > 0) if (m_args.count(g_strHelp) > 0)
@ -950,6 +966,8 @@ void CommandLineParser::processArgs()
m_options.input.mode = InputMode::Linker; m_options.input.mode = InputMode::Linker;
else if (m_args.count(g_strImportAst) > 0) else if (m_args.count(g_strImportAst) > 0)
m_options.input.mode = InputMode::CompilerWithASTImport; m_options.input.mode = InputMode::CompilerWithASTImport;
else if (m_args.count(g_strImportEvmAssemblerJson) > 0)
m_options.input.mode = InputMode::EVMAssemblerJSON;
else else
m_options.input.mode = InputMode::Compiler; m_options.input.mode = InputMode::Compiler;
@ -1010,9 +1028,35 @@ void CommandLineParser::processArgs()
if (option != CompilerOutputs::componentName(&CompilerOutputs::astCompactJson)) if (option != CompilerOutputs::componentName(&CompilerOutputs::astCompactJson))
checkMutuallyExclusive({g_strStopAfter, option}); checkMutuallyExclusive({g_strStopAfter, option});
if (m_options.input.mode == InputMode::EVMAssemblerJSON)
{
static set<string> const supportedByEvmAsmJsonImport{
g_strImportEvmAssemblerJson,
CompilerOutputs::componentName(&CompilerOutputs::asm_),
CompilerOutputs::componentName(&CompilerOutputs::binary),
CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
CompilerOutputs::componentName(&CompilerOutputs::opcodes),
g_strCombinedJson,
g_strInputFile,
g_strJsonIndent,
g_strPrettyJson,
"srcmap",
"srcmap-runtime",
};
for (auto const& option: m_args)
if (!option.second.defaulted() && !supportedByEvmAsmJsonImport.count(option.first))
solThrow(
CommandLineValidationError,
"Option --" + option.first + " is not supported with --"+g_strImportEvmAssemblerJson+"."
);
}
if ( if (
m_options.input.mode != InputMode::Compiler && m_options.input.mode != InputMode::Compiler &&
m_options.input.mode != InputMode::CompilerWithASTImport && m_options.input.mode != InputMode::CompilerWithASTImport &&
m_options.input.mode != InputMode::EVMAssemblerJSON &&
m_options.input.mode != InputMode::Assembler m_options.input.mode != InputMode::Assembler
) )
{ {
@ -1372,7 +1416,10 @@ void CommandLineParser::processArgs()
if (m_options.input.mode == InputMode::Compiler) if (m_options.input.mode == InputMode::Compiler)
m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0); m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0);
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport); solAssert(
m_options.input.mode == InputMode::Compiler ||
m_options.input.mode == InputMode::CompilerWithASTImport ||
m_options.input.mode == InputMode::EVMAssemblerJSON);
} }
void CommandLineParser::parseCombinedJsonOption() void CommandLineParser::parseCombinedJsonOption()
@ -1388,6 +1435,29 @@ void CommandLineParser::parseCombinedJsonOption()
m_options.compiler.combinedJsonRequests = CombinedJsonRequests{}; m_options.compiler.combinedJsonRequests = CombinedJsonRequests{};
for (auto&& [componentName, component]: CombinedJsonRequests::componentMap()) for (auto&& [componentName, component]: CombinedJsonRequests::componentMap())
m_options.compiler.combinedJsonRequests.value().*component = (requests.count(componentName) > 0); m_options.compiler.combinedJsonRequests.value().*component = (requests.count(componentName) > 0);
if (m_options.input.mode == InputMode::EVMAssemblerJSON && m_options.compiler.combinedJsonRequests.has_value())
{
static bool CombinedJsonRequests::* invalidOptions[]{
&CombinedJsonRequests::abi,
&CombinedJsonRequests::ast,
&CombinedJsonRequests::funDebug,
&CombinedJsonRequests::funDebugRuntime,
&CombinedJsonRequests::generatedSources,
&CombinedJsonRequests::generatedSourcesRuntime,
&CombinedJsonRequests::metadata,
&CombinedJsonRequests::natspecDev,
&CombinedJsonRequests::natspecUser,
&CombinedJsonRequests::signatureHashes,
&CombinedJsonRequests::storageLayout
};
for (auto const invalidOption: invalidOptions)
if (m_options.compiler.combinedJsonRequests.value().*invalidOption)
solThrow(
CommandLineValidationError,
"Invalid option to --" + g_strCombinedJson + ": " + CombinedJsonRequests::componentName(invalidOption) + " for --" + g_strImportEvmAssemblerJson);
}
} }
size_t CommandLineParser::countEnabledOptions(vector<string> const& _optionNames) const size_t CommandLineParser::countEnabledOptions(vector<string> const& _optionNames) const

View File

@ -57,6 +57,7 @@ enum class InputMode
Linker, Linker,
Assembler, Assembler,
LanguageServer, LanguageServer,
EVMAssemblerJSON
}; };
struct CompilerOutputs struct CompilerOutputs

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
--pretty-json --json-indent 4 --combined-json bin,bin-runtime,opcodes,asm,srcmap,srcmap-runtime --asm --bin --bin-runtime --asm-json --import-asm-json -

View File

@ -0,0 +1,91 @@
{
"contracts":
{
"<stdin>":
{
"asm":
{
".code":
[
{
"begin": 0,
"end": 0,
"name": "PUSH",
"source": -1,
"value": "0"
}
],
".data":
{
"0":
{
".code":
[
{
"begin": 0,
"end": 0,
"name": "PUSH",
"source": -1,
"value": "1"
}
]
}
},
"sourceList":
[
"contract.sol",
"#utility.yul"
]
},
"bin": "6000fe",
"bin-runtime": "6001",
"opcodes": "PUSH1 0x0 INVALID ",
"srcmap": "0:0::-:0",
"srcmap-runtime": "0:0::-:0"
}
},
"sourceList":
[
"contract.sol",
"#utility.yul"
],
"version": "<VERSION REMOVED>"
}
Binary:
6000fe
Binary of the runtime part:
6001
EVM assembly:
{
".code":
[
{
"begin": 0,
"end": 0,
"name": "PUSH",
"source": -1,
"value": "0"
}
],
".data":
{
"0":
{
".code":
[
{
"begin": 0,
"end": 0,
"name": "PUSH",
"source": -1,
"value": "1"
}
]
}
},
"sourceList":
[
"contract.sol",
"#utility.yul"
]
}

View File

@ -0,0 +1,22 @@
{
".code": [
{
"name": "PUSH",
"value": "0"
}
],
".data": {
"0": {
".code": [
{
"name": "PUSH",
"value": "1"
}
]
}
},
"sourceList": [
"contract.sol",
"#utility.yul"
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1,6 @@
Opcodes:
PREVRANDAO PREVRANDAO
EVM assembly:
/* */
prevrandao
prevrandao

View File

@ -0,0 +1,6 @@
{
".code": [
{ "name": "DIFFICULTY" },
{ "name": "PREVRANDAO" }
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Member '.data' contains a value for '0' that is not a valid hexadecimal string.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,11 @@
{
".code": [
{
"name": "PUSH",
"value": "0"
}
],
".data": {
"0": "no-hex-string"
}
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Key inside '.data' '0' can only be a valid hex-string or an object.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,16 @@
{
".code": [
{
"name": "PUSH",
"value": "0"
}
],
".data": {
"0": [
0,
1,
2,
3
]
}
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction 'PUSH')

View File

@ -0,0 +1,9 @@
{
".code": [
{
"name": "PUSH",
"value": "0",
"jumpType": "[in]"
}
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Member 'value' defined for instruction 'DIFFICULTY', but the instruction does not need a value.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,8 @@
{
".code": [
{
"name": "DIFFICULTY",
"value": "0"
}
]
}

View File

@ -0,0 +1 @@
--optimize --import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Option --optimize is not supported with --import-asm-json.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
{
".code": [
{ "name": "DIFFICULTY" },
{ "name": "PREVRANDAO" }
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Member 'value' was not defined for instruction 'PUSH', but the instruction needs a value.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
{
".code": [
{ "name": "PUSH" }
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: Unknown member '_name'. Valid members are begin, end, jumpType, modifierDepth, name, source, value.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
{
".code": [
{ "_name": "DIFFICULTY" }
]
}

View File

@ -0,0 +1 @@
--import-asm-json - --opcodes --asm

View File

@ -0,0 +1 @@
Assembly Import Error: JUMPDEST instruction found that was not followed by tag.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,10 @@
{
".code": [
{
"name": "tag",
"value": "0x00"
},
{ "name": "JUMPDEST" },
{ "name": "JUMPDEST" }
]
}

View File

@ -1 +1 @@
The following outputs are not supported in assembler mode: --abi, --asm-json, --bin-runtime, --devdoc, --hashes, --ir, --metadata, --opcodes, --storage-layout, --userdoc. The following outputs are not supported in assembler mode: --abi, --bin-runtime, --devdoc, --hashes, --ir, --metadata, --opcodes, --storage-layout, --userdoc.

View File

@ -55,13 +55,9 @@ BOOST_AUTO_TEST_SUITE(Assembler)
BOOST_AUTO_TEST_CASE(all_assembly_items) BOOST_AUTO_TEST_CASE(all_assembly_items)
{ {
map<string, unsigned> indices = {
{ "root.asm", 0 },
{ "sub.asm", 1 },
{ "verbatim.asm", 2 }
};
EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
Assembly _assembly{evmVersion, false, {}}; Assembly _assembly{evmVersion, false, {}};
_assembly.setSourceList({"root.asm", "sub.asm", "verbatim.asm"});
auto root_asm = make_shared<string>("root.asm"); auto root_asm = make_shared<string>("root.asm");
_assembly.setSourceLocation({1, 3, root_asm}); _assembly.setSourceLocation({1, 3, root_asm});
@ -212,7 +208,7 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
}; };
Json::Value jsonValue; Json::Value jsonValue;
BOOST_CHECK(util::jsonParseStrict(json, jsonValue)); BOOST_CHECK(util::jsonParseStrict(json, jsonValue));
BOOST_CHECK_EQUAL(util::jsonCompactPrint(_assembly.assemblyJSON(indices)), util::jsonCompactPrint(jsonValue)); BOOST_CHECK_EQUAL(util::jsonCompactPrint(_assembly.assemblyJSON()), util::jsonCompactPrint(jsonValue));
} }
BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps) BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)
@ -301,12 +297,9 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)
BOOST_AUTO_TEST_CASE(immutable) BOOST_AUTO_TEST_CASE(immutable)
{ {
map<string, unsigned> indices = {
{ "root.asm", 0 },
{ "sub.asm", 1 }
};
EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
Assembly _assembly{evmVersion, true, {}}; Assembly _assembly{evmVersion, true, {}};
_assembly.setSourceList({"root.asm", "sub.asm"});
auto root_asm = make_shared<string>("root.asm"); auto root_asm = make_shared<string>("root.asm");
_assembly.setSourceLocation({1, 3, root_asm}); _assembly.setSourceLocation({1, 3, root_asm});
@ -383,7 +376,7 @@ BOOST_AUTO_TEST_CASE(immutable)
"}\n" "}\n"
); );
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
util::jsonCompactPrint(_assembly.assemblyJSON(indices)), util::jsonCompactPrint(_assembly.assemblyJSON()),
"{\".code\":[" "{\".code\":["
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"},"

View File

@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(version)
BOOST_AUTO_TEST_CASE(multiple_input_modes) BOOST_AUTO_TEST_CASE(multiple_input_modes)
{ {
array<string, 9> inputModeOptions = { array<string, 10> inputModeOptions = {
"--help", "--help",
"--license", "--license",
"--version", "--version",
@ -158,10 +158,11 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes)
"--strict-assembly", "--strict-assembly",
"--yul", "--yul",
"--import-ast", "--import-ast",
"--import-asm-json",
}; };
string expectedMessage = string expectedMessage =
"The following options are mutually exclusive: " "The following options are mutually exclusive: "
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast, --lsp. " "--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast, --lsp, --import-asm-json. "
"Select at most one."; "Select at most one.";
for (string const& mode1: inputModeOptions) for (string const& mode1: inputModeOptions)