From 108490e630fc7f58a405f8b4dca2af27bf5d4e1a Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Tue, 1 Nov 2022 23:58:45 -0500 Subject: [PATCH] [libevmasm] Add support to import evm assembly json. --- libevmasm/Assembly.cpp | 303 +++++++++++++++++- libevmasm/Assembly.h | 36 ++- libevmasm/AssemblyItem.cpp | 12 + libevmasm/AssemblyItem.h | 1 + libevmasm/CMakeLists.txt | 2 + libevmasm/EVMAssemblyStack.cpp | 58 ++++ libevmasm/EVMAssemblyStack.h | 68 ++++ libevmasm/Exceptions.h | 1 + libsolidity/interface/CompilerStack.cpp | 57 +++- libsolidity/interface/CompilerStack.h | 8 +- scripts/ASTImportTest.sh | 153 ++++++++- solc/CommandLineInterface.cpp | 139 ++++++-- solc/CommandLineInterface.h | 3 + solc/CommandLineParser.cpp | 74 ++++- solc/CommandLineParser.h | 1 + .../asm_json_export_yul_without_debug/output | 3 + .../asm_json_import_all_valid_flags/args | 1 + .../asm_json_import_all_valid_flags/output | 91 ++++++ .../asm_json_import_all_valid_flags/stdin | 22 ++ .../args | 1 + .../output | 6 + .../stdin | 6 + .../asm_json_import_invalid_data_no_hex/args | 1 + .../asm_json_import_invalid_data_no_hex/err | 1 + .../asm_json_import_invalid_data_no_hex/exit | 1 + .../asm_json_import_invalid_data_no_hex/stdin | 11 + .../args | 1 + .../err | 1 + .../exit | 1 + .../stdin | 16 + .../args | 1 + .../err | 1 + .../exit | 1 + .../stdin | 9 + .../asm_json_import_invalid_value/args | 1 + .../asm_json_import_invalid_value/err | 1 + .../asm_json_import_invalid_value/exit | 1 + .../asm_json_import_invalid_value/stdin | 8 + .../asm_json_import_no_optimiser/args | 1 + .../asm_json_import_no_optimiser/err | 1 + .../asm_json_import_no_optimiser/exit | 1 + .../asm_json_import_no_optimiser/stdin | 6 + .../asm_json_import_no_value/args | 1 + .../cmdlineTests/asm_json_import_no_value/err | 1 + .../asm_json_import_no_value/exit | 1 + .../asm_json_import_no_value/stdin | 5 + .../asm_json_import_other_fields/args | 1 + .../asm_json_import_other_fields/err | 1 + .../asm_json_import_other_fields/exit | 1 + .../asm_json_import_other_fields/stdin | 5 + .../asm_json_import_untagged_jumpdest/args | 1 + .../asm_json_import_untagged_jumpdest/err | 1 + .../asm_json_import_untagged_jumpdest/exit | 1 + .../asm_json_import_untagged_jumpdest/stdin | 10 + .../strict_asm_output_selection_invalid/err | 2 +- test/libevmasm/Assembler.cpp | 15 +- test/solc/CommandLineInterface.cpp | 5 +- 57 files changed, 1099 insertions(+), 63 deletions(-) create mode 100644 libevmasm/EVMAssemblyStack.cpp create mode 100644 libevmasm/EVMAssemblyStack.h create mode 100644 test/cmdlineTests/asm_json_export_yul_without_debug/output create mode 100644 test/cmdlineTests/asm_json_import_all_valid_flags/args create mode 100644 test/cmdlineTests/asm_json_import_all_valid_flags/output create mode 100644 test/cmdlineTests/asm_json_import_all_valid_flags/stdin create mode 100644 test/cmdlineTests/asm_json_import_difficulty_prevrandao/args create mode 100644 test/cmdlineTests/asm_json_import_difficulty_prevrandao/output create mode 100644 test/cmdlineTests/asm_json_import_difficulty_prevrandao/stdin create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_hex/args create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_hex/err create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_hex/exit create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_object/args create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_object/err create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_object/exit create mode 100644 test/cmdlineTests/asm_json_import_invalid_data_no_object/stdin create mode 100644 test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/args create mode 100644 test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/err create mode 100644 test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/exit create mode 100644 test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/stdin create mode 100644 test/cmdlineTests/asm_json_import_invalid_value/args create mode 100644 test/cmdlineTests/asm_json_import_invalid_value/err create mode 100644 test/cmdlineTests/asm_json_import_invalid_value/exit create mode 100644 test/cmdlineTests/asm_json_import_invalid_value/stdin create mode 100644 test/cmdlineTests/asm_json_import_no_optimiser/args create mode 100644 test/cmdlineTests/asm_json_import_no_optimiser/err create mode 100644 test/cmdlineTests/asm_json_import_no_optimiser/exit create mode 100644 test/cmdlineTests/asm_json_import_no_optimiser/stdin create mode 100644 test/cmdlineTests/asm_json_import_no_value/args create mode 100644 test/cmdlineTests/asm_json_import_no_value/err create mode 100644 test/cmdlineTests/asm_json_import_no_value/exit create mode 100644 test/cmdlineTests/asm_json_import_no_value/stdin create mode 100644 test/cmdlineTests/asm_json_import_other_fields/args create mode 100644 test/cmdlineTests/asm_json_import_other_fields/err create mode 100644 test/cmdlineTests/asm_json_import_other_fields/exit create mode 100644 test/cmdlineTests/asm_json_import_other_fields/stdin create mode 100644 test/cmdlineTests/asm_json_import_untagged_jumpdest/args create mode 100644 test/cmdlineTests/asm_json_import_untagged_jumpdest/err create mode 100644 test/cmdlineTests/asm_json_import_untagged_jumpdest/exit create mode 100644 test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index f524aad66..99f39aa1c 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -34,13 +34,15 @@ #include #include -#include +#include +#include #include #include #include #include +#include using namespace solidity; 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 const& _sourceList) +{ + solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object."); + static set 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(_json["name"]), AssemblyImportException, "Member 'name' missing or not of type string."); + solRequire(isOfTypeIfExists(_json, "begin"), AssemblyImportException, "Optional member 'begin' not of type int."); + solRequire(isOfTypeIfExists(_json, "end"), AssemblyImportException, "Optional member 'end' not of type int."); + solRequire(isOfTypeIfExists(_json, "source"), AssemblyImportException, "Optional member 'source' not of type int."); + solRequire(isOfTypeIfExists(_json, "value"), AssemblyImportException, "Optional member 'value' not of type string."); + solRequire( + isOfTypeIfExists(_json, "modifierDepth"), + AssemblyImportException, + "Optional member 'modifierDepth' not of type int." + ); + solRequire( + isOfTypeIfExists(_json, "jumpType"), + AssemblyImportException, + "Optional member 'jumpType' not of type string." + ); + + string name = get(_json["name"]); + solRequire(!name.empty(), AssemblyImportException, "Member 'name' was empty."); + + SourceLocation location; + location.start = get(_json["begin"]); + location.end = get(_json["end"]); + int srcIndex = getOrDefault(_json["source"], -1); + size_t modifierDepth = static_cast(getOrDefault(_json["modifierDepth"], 0)); + string value = getOrDefault(_json["value"], ""); + string jumpType = getOrDefault(_json["jumpType"], ""); + + auto updateUsedTags = [&](u256 const& data) + { + m_usedTags = max(m_usedTags, static_cast(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(_sourceList.size()), AssemblyImportException, "srcIndex out of bound."); + if (srcIndex != -1) + location.sourceName = std::make_shared(_sourceList[static_cast(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 { @@ -221,7 +424,7 @@ std::string Assembly::assemblyString( return tmp.str(); } -Json::Value Assembly::assemblyJSON(std::map const& _sourceIndices, bool _includeSourceList) const +Json::Value Assembly::assemblyJSON(bool _includeSourceList) const { Json::Value root; root[".code"] = Json::arrayValue; @@ -231,9 +434,9 @@ Json::Value Assembly::assemblyJSON(std::map const& _sourc int sourceIndex = -1; if (item.location().sourceName) { - auto iter = _sourceIndices.find(*item.location().sourceName); - if (iter != _sourceIndices.end()) - sourceIndex = static_cast(iter->second); + for (size_t index = 0; index < m_sourceList.size(); ++index) + if (m_sourceList[index] == *item.location().sourceName) + sourceIndex = static_cast(index); } auto [name, data] = item.nameAndData(m_evmVersion); @@ -271,8 +474,8 @@ Json::Value Assembly::assemblyJSON(std::map const& _sourc { root["sourceList"] = Json::arrayValue; Json::Value& jsonSourceList = root["sourceList"]; - for (auto const& [name, index]: _sourceIndices) - jsonSourceList[index] = name; + for (int index = 0; index < static_cast(m_sourceList.size()); ++index) + jsonSourceList[index] = m_sourceList[static_cast(index)]; } if (!m_data.empty() || !m_subs.empty()) @@ -285,9 +488,10 @@ Json::Value Assembly::assemblyJSON(std::map const& _sourc for (size_t i = 0; i < m_subs.size(); ++i) { - std::stringstream hexStr; - hexStr << std::hex << i; - data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices, /*_includeSourceList = */false); + stringstream hexStr; + hexStr << hex << i; + m_subs[i]->setSourceList(m_sourceList); + data[hexStr.str()] = m_subs[i]->assemblyJSON(false); } } @@ -297,7 +501,84 @@ Json::Value Assembly::assemblyJSON(std::map const& _sourc return root; } -AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional _sourceID) +shared_ptr Assembly::fromJSON(Json::Value const& _json, vector const& _sourceList, int _level) +{ + solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object."); + static set 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 result = make_shared(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 subassembly(Assembly::fromJSON(code, result->m_sourceList, _level + 1)); + solAssert(subassembly); + result->m_subs.emplace_back(make_shared(*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 _sourceID) { assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); if (m_namedTags.count(_name)) diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index bd37b3a7f..cec69d07e 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -151,10 +151,30 @@ public: /// Create a JSON representation of the assembly. Json::Value assemblyJSON( - std::map const& _sourceIndices = std::map(), bool _includeSourceList = true ) 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 fromJSON( + Json::Value const& _json, + std::vector const& _sourceList = {}, + int _level = 0 + ); + + void setSourceList(std::vector const& _sourceList) + { + m_sourceList = _sourceList; + } + + std::vector const& sourceList() + { + return m_sourceList; + } + /// Mark this assembly as invalid. Calling ``assemble`` on it will throw. void markAsInvalid() { m_invalid = true; } @@ -171,6 +191,18 @@ protected: 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 const& _sourceList); + private: bool m_invalid = false; @@ -217,7 +249,7 @@ protected: /// Internal name of the assembly object, only used with the Yul backend /// currently std::string m_name; - + std::vector m_sourceList; langutil::SourceLocation m_currentSourceLocation; public: diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 6c2b673c6..144787b0e 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -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 text; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 077c1912d..742a011a4 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -174,6 +174,7 @@ public: langutil::SourceLocation const& location() const { return m_location; } void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } + void setJumpType(std::string const& _jumpType); JumpType getJumpType() const { return m_jumpType; } std::string getJumpTypeAsString() const; diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index 6563b2c0f..dbe9d14f8 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -3,6 +3,8 @@ set(sources Assembly.h AssemblyItem.cpp AssemblyItem.h + EVMAssemblyStack.cpp + EVMAssemblyStack.h BlockDeduplicator.cpp BlockDeduplicator.h CommonSubexpressionEliminator.cpp diff --git a/libevmasm/EVMAssemblyStack.cpp b/libevmasm/EVMAssemblyStack.cpp new file mode 100644 index 000000000..f81e535a9 --- /dev/null +++ b/libevmasm/EVMAssemblyStack.cpp @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include + +#include + +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(m_evmAssembly->sub(0)); + solAssert(m_evmRuntimeAssembly && !m_evmRuntimeAssembly->isCreation()); + m_runtimeObject = m_evmRuntimeAssembly->assemble(); + } +} + +} // namespace solidity::evmasm diff --git a/libevmasm/EVMAssemblyStack.h b/libevmasm/EVMAssemblyStack.h new file mode 100644 index 000000000..104a978e1 --- /dev/null +++ b/libevmasm/EVMAssemblyStack.h @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include + +#include +#include + +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 const& evmAssembly() const { return m_evmAssembly; } + + evmasm::LinkerObject const& runtimeObject() const { return m_runtimeObject; } + + std::shared_ptr 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 m_evmAssembly; + std::shared_ptr m_evmRuntimeAssembly; + evmasm::LinkerObject m_object; ///< Deployment object (includes the runtime sub-object). + evmasm::LinkerObject m_runtimeObject; ///< Runtime object. +}; + +} // namespace solidity::evmasm diff --git a/libevmasm/Exceptions.h b/libevmasm/Exceptions.h index 027577576..ae6d1bf9c 100644 --- a/libevmasm/Exceptions.h +++ b/libevmasm/Exceptions.h @@ -28,6 +28,7 @@ namespace solidity::evmasm { struct AssemblyException: virtual util::Exception {}; +struct AssemblyImportException: virtual AssemblyException {}; struct OptimizerException: virtual AssemblyException {}; struct StackTooDeepException: virtual OptimizerException {}; struct ItemNotAvailableException: virtual OptimizerException {}; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index bbebc2508..9781b6278 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -72,6 +72,7 @@ #include #include +#include #include #include @@ -438,6 +439,27 @@ void CompilerStack::importASTs(map const& _sources) storeContractDefinitions(); } +void CompilerStack::importFromEVMAssemblyStack(std::string const& _sourceName, std::string const& _source) +{ + solRequire(m_stackState == Empty, CompilerError, ""); + m_evmAssemblyStack = std::make_unique(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() { if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed) @@ -674,6 +696,7 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con bool CompilerStack::compile(State _stopAfter) { + solAssert(m_compilationSourceType != CompilationSourceType::EvmAssemblyJSON); m_stopAfter = _stopAfter; if (m_stackState < AnalysisPerformed) if (!parseAndAnalyze(_stopAfter)) @@ -716,7 +739,7 @@ bool CompilerStack::compile(State _stopAfter) { if ( SourceLocation const* sourceLocation = - boost::get_error_info(_unimplementedError) + boost::get_error_info(_unimplementedError) ) { string const* comment = _unimplementedError.comment(); @@ -957,7 +980,13 @@ Json::Value CompilerStack::assemblyJSON(string const& _contractName) const Contract const& currentContract = contract(_contractName); if (currentContract.evmAssembly) - return currentContract.evmAssembly->assemblyJSON(sourceIndices()); + { + std::vector 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 return Json::Value(); } @@ -971,10 +1000,21 @@ map CompilerStack::sourceIndices() const { map indices; unsigned index = 0; - for (auto const& s: m_sources) - indices[s.first] = index++; - solAssert(!indices.count(CompilerContext::yulUtilityFileName()), ""); - indices[CompilerContext::yulUtilityFileName()] = index++; + if (m_evmAssemblyStack) + { + for (auto const& s: m_evmAssemblyStack->evmAssembly()->sourceList()) + 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; } @@ -1589,6 +1629,11 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con case CompilationSourceType::SolidityAST: sourceType = "SolidityAST"; break; + case CompilationSourceType::EvmAssemblyJSON: + sourceType = "EvmAssemblyJson"; + break; + default: + solAssert(false); } meta["language"] = sourceType; meta["compiler"]["version"] = VersionStringStrict; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 8880770b8..d8ca74fb0 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -42,6 +42,7 @@ #include #include +#include #include #include @@ -121,7 +122,9 @@ public: /// Regular compilation from Solidity source files. Solidity, /// Compilation from an imported Solidity AST. - SolidityAST + SolidityAST, + /// Compilation from an imported EVM Assembly JSON. + EvmAssemblyJSON }; /// Creates a new compiler stack. @@ -230,6 +233,8 @@ public: /// Will throw errors if the import fails void importASTs(std::map const& _sources); + void importFromEVMAssemblyStack(std::string const& _sourceName, std::string const& _source); + /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// typechecking, staticAnalysis) on previously parsed sources. /// @returns false on error. @@ -524,6 +529,7 @@ private: /// If this is true, the stack will refuse to generate code. bool m_hasError = false; MetadataFormat m_metadataFormat = defaultMetadataFormat(); + std::unique_ptr m_evmAssemblyStack; }; } diff --git a/scripts/ASTImportTest.sh b/scripts/ASTImportTest.sh index c24526195..f5f8a7ef5 100755 --- a/scripts/ASTImportTest.sh +++ b/scripts/ASTImportTest.sh @@ -23,6 +23,18 @@ # ast import/export tests: # - 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. +# +# 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 @@ -40,7 +52,7 @@ source "${REPO_ROOT}/scripts/common.sh" 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 @@ -81,6 +93,7 @@ for PARAM in "$@" do case "$PARAM" in 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 ;; --exit-on-error) EXIT_ON_ERROR=1 ;; *) fail "Unknown option '$PARAM'. Aborting. $(print_usage)" ;; @@ -89,6 +102,7 @@ done SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON" +SEMANTICTESTS_DIR="${REPO_ROOT}/test/libsolidity/semanticTests" FAILED=0 UNCOMPILABLE=0 @@ -153,6 +167,125 @@ function test_ast_import_export_equivalence 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. # Results are recorded by incrementing the FAILED or UNCOMPILABLE global variable. # Also, in case of a mismatch a diff is printed @@ -168,6 +301,7 @@ function test_import_export_equivalence { case "$IMPORT_TEST_TYPE" in ast) compile_test="--ast-compact-json" ;; + evm-assembly) compile_test="--bin" ;; *) assertFail "Unknown import test type '${IMPORT_TEST_TYPE}'. Aborting." ;; esac @@ -181,6 +315,7 @@ function test_import_export_equivalence { then case "$IMPORT_TEST_TYPE" in 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." ;; esac else @@ -191,7 +326,20 @@ function test_import_export_equivalence { # and print some details about the corresponding solc invocation. if (( solc_return_code == 2 )) 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 } @@ -203,6 +351,7 @@ command_available jq --version case "$IMPORT_TEST_TYPE" in ast) TEST_DIRS=("${SYNTAXTESTS_DIR}" "${ASTJSONTESTS_DIR}") ;; + evm-assembly) TEST_DIRS=("${SEMANTICTESTS_DIR}") ;; *) assertFail "Import test type not defined. $(print_usage)" ;; esac diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 7a28900b3..2ac6fac4d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -93,6 +93,7 @@ namespace set const CompilerInputModes{ frontend::InputMode::Compiler, frontend::InputMode::CompilerWithASTImport, + frontend::InputMode::EVMAssemblerJSON }; } // anonymous namespace @@ -167,28 +168,58 @@ static bool coloredOutput(CommandLineOptions const& _options) (_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) { 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.output.dir.empty()) - createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", objectWithLinkRefsHex(m_compiler->object(_contract))); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", binary); else { sout() << "Binary:" << endl; - sout() << objectWithLinkRefsHex(m_compiler->object(_contract)) << endl; + sout() << binary << endl; } } if (m_options.compiler.outputs.binaryRuntime) { 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 { 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); + string opcodes{evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion)}; + 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 { sout() << "Opcodes:" << endl; - sout() << uppercase << evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion); + sout() << uppercase << opcodes; sout() << endl; } } @@ -702,6 +735,12 @@ void CommandLineInterface::processInput() compile(); outputCompilationResults(); break; + case InputMode::EVMAssemblerJSON: + assembleFromEvmAssemblyJson(); + handleCombinedJSON(); + handleBytecode(""); + handleEVMAssembly(""); + break; } } @@ -718,6 +757,23 @@ void CommandLineInterface::printLicense() 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(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() { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); @@ -884,8 +940,7 @@ void CommandLineInterface::handleCombinedJSON() ); if (m_options.compiler.combinedJsonRequests->funDebugRuntime && m_compiler->compilationSuccessful()) contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData( - m_compiler->runtimeObject(contractName).functionDebugData - ); + m_compiler->runtimeObject(contractName).functionDebugData); if (m_options.compiler.combinedJsonRequests->signatureHashes) contractData[g_strSignatureHashes] = m_compiler->interfaceSymbols(contractName)["methods"]; if (m_options.compiler.combinedJsonRequests->natspecDev) @@ -894,10 +949,9 @@ void CommandLineInterface::handleCombinedJSON() contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName); } - bool needsSourceList = - m_options.compiler.combinedJsonRequests->ast || - m_options.compiler.combinedJsonRequests->srcMap || - m_options.compiler.combinedJsonRequests->srcMapRuntime; + bool needsSourceList = m_options.compiler.combinedJsonRequests->ast || + m_options.compiler.combinedJsonRequests->srcMap || + m_options.compiler.combinedJsonRequests->srcMapRuntime; if (needsSourceList) { // 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 serr() << "No text representation found." << endl; } + + if (m_options.compiler.outputs.asmJson) + { + shared_ptr assembly{stack.assembleEVMWithDeployed().first}; + if (assembly) + { + std::function(yul::Object const&)> collectSourceIndices = + [&](yul::Object const& _object) -> map { + map 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(sub.get()); + if (subObject) + for (auto const& [name, index]: collectSourceIndices(*subObject)) + sourceIndices[name] = index; + } + return sourceIndices; + }; + if (stack.parserResult() && stack.parserResult()->debugData) + { + map 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 sourceList(max_index + 1, ""); + 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; // do we need EVM assembly? - if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) - { - 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; - } + handleEVMAssembly(contract); if (m_options.compiler.estimateGas) handleGasEstimation(contract); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 5de69270b..c53b4c57f 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -84,6 +85,7 @@ private: void printVersion(); void printLicense(); void compile(); + void assembleFromEvmAssemblyJson(); void serveLSP(); void link(); void writeLinkedFiles(); @@ -98,6 +100,7 @@ private: void handleCombinedJSON(); void handleAst(); + void handleEVMAssembly(std::string const& _contract); void handleBinary(std::string const& _contract); void handleOpcode(std::string const& _contract); void handleIR(std::string const& _contract); diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 060c1ad8f..ba9110678 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -52,6 +52,7 @@ static string const g_strExperimentalViaIR = "experimental-via-ir"; static string const g_strGas = "gas"; static string const g_strHelp = "help"; 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_strYul = "yul"; static string const g_strYulDialect = "yul-dialect"; @@ -142,6 +143,7 @@ static map const g_inputModeName = { {InputMode::StandardJson, "standard JSON"}, {InputMode::Linker, "linker"}, {InputMode::LanguageServer, "language server (LSP)"}, + {InputMode::EVMAssemblerJSON, "EVM assembler (JSON format)"}, }; void CommandLineParser::checkMutuallyExclusive(vector const& _optionNames) @@ -468,8 +470,15 @@ void CommandLineParser::parseOutputSelection() CompilerOutputs::componentName(&CompilerOutputs::binary), CompilerOutputs::componentName(&CompilerOutputs::irOptimized), CompilerOutputs::componentName(&CompilerOutputs::astCompactJson), + CompilerOutputs::componentName(&CompilerOutputs::asmJson), + }; + static set const evmAssemblyJsonImportModeOutputs = { + CompilerOutputs::componentName(&CompilerOutputs::asm_), + CompilerOutputs::componentName(&CompilerOutputs::binary), + CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime), + CompilerOutputs::componentName(&CompilerOutputs::opcodes), + CompilerOutputs::componentName(&CompilerOutputs::asmJson), }; - switch (_mode) { case InputMode::Help: @@ -480,6 +489,8 @@ void CommandLineParser::parseOutputSelection() case InputMode::Compiler: case InputMode::CompilerWithASTImport: return util::contains(compilerModeOutputs, _outputName); + case InputMode::EVMAssemblerJSON: + return util::contains(evmAssemblyJsonImportModeOutputs, _outputName); case InputMode::Assembler: return util::contains(assemblerModeOutputs, _outputName); 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 " "--" + 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(), "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_strImportAst, g_strLSP, + g_strImportEvmAssemblerJson, }); if (m_args.count(g_strHelp) > 0) @@ -950,6 +966,8 @@ void CommandLineParser::processArgs() m_options.input.mode = InputMode::Linker; else if (m_args.count(g_strImportAst) > 0) m_options.input.mode = InputMode::CompilerWithASTImport; + else if (m_args.count(g_strImportEvmAssemblerJson) > 0) + m_options.input.mode = InputMode::EVMAssemblerJSON; else m_options.input.mode = InputMode::Compiler; @@ -1010,9 +1028,35 @@ void CommandLineParser::processArgs() if (option != CompilerOutputs::componentName(&CompilerOutputs::astCompactJson)) checkMutuallyExclusive({g_strStopAfter, option}); + if (m_options.input.mode == InputMode::EVMAssemblerJSON) + { + static set 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 ( m_options.input.mode != InputMode::Compiler && m_options.input.mode != InputMode::CompilerWithASTImport && + m_options.input.mode != InputMode::EVMAssemblerJSON && m_options.input.mode != InputMode::Assembler ) { @@ -1372,7 +1416,10 @@ void CommandLineParser::processArgs() if (m_options.input.mode == InputMode::Compiler) 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() @@ -1388,6 +1435,29 @@ void CommandLineParser::parseCombinedJsonOption() m_options.compiler.combinedJsonRequests = CombinedJsonRequests{}; for (auto&& [componentName, component]: CombinedJsonRequests::componentMap()) 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 const& _optionNames) const diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index 6b391e724..1871a89e7 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -57,6 +57,7 @@ enum class InputMode Linker, Assembler, LanguageServer, + EVMAssemblerJSON }; struct CompilerOutputs diff --git a/test/cmdlineTests/asm_json_export_yul_without_debug/output b/test/cmdlineTests/asm_json_export_yul_without_debug/output new file mode 100644 index 000000000..607e92267 --- /dev/null +++ b/test/cmdlineTests/asm_json_export_yul_without_debug/output @@ -0,0 +1,3 @@ + +======= (EVM) ======= +{".code":[{"begin":59,"end":76,"name":"PUSH","source":0,"value":"80"},{"begin":100,"end":102,"name":"DUP1","source":0},{"begin":96,"end":98,"name":"PUSH","source":0,"value":"40"},{"begin":89,"end":103,"name":"MSTORE","source":0},{"begin":119,"end":130,"name":"CALLVALUE","source":0},{"begin":116,"end":147,"name":"ISZERO","source":0},{"begin":116,"end":147,"name":"PUSH [tag]","source":0,"value":"1"},{"begin":116,"end":147,"name":"JUMPI","source":0},{"begin":143,"end":144,"name":"PUSH","source":0,"value":"0"},{"begin":140,"end":141,"name":"DUP1","source":0},{"begin":133,"end":145,"name":"REVERT","source":0},{"begin":116,"end":147,"name":"tag","source":0,"value":"1"},{"begin":116,"end":147,"name":"JUMPDEST","source":0},{"begin":173,"end":177,"name":"PUSH","source":0,"value":"2"},{"begin":167,"end":171,"name":"PUSH","source":0,"value":"0"},{"begin":160,"end":178,"name":"SSTORE","source":0},{"begin":204,"end":208,"name":"PUSH","source":0,"value":"0"},{"begin":198,"end":202,"name":"PUSH","source":0,"value":"1"},{"begin":191,"end":209,"name":"SSTORE","source":0},{"begin":232,"end":257,"name":"PUSH #[$]","source":0,"value":"0000000000000000000000000000000000000000000000000000000000000000"},{"begin":312,"end":314,"name":"DUP1","source":0},{"begin":283,"end":310,"name":"PUSH [$]","source":0,"value":"0000000000000000000000000000000000000000000000000000000000000000"},{"begin":279,"end":281,"name":"DUP4","source":0},{"begin":270,"end":315,"name":"CODECOPY","source":0},{"begin":339,"end":341,"name":"DUP1","source":0},{"begin":335,"end":337,"name":"DUP3","source":0},{"begin":328,"end":342,"name":"RETURN","source":0}],".data":{"0":{".auxdata":"",".code":[{"begin":443,"end":460,"name":"PUSH","source":0,"value":"80"},{"begin":488,"end":490,"name":"DUP1","source":0},{"begin":484,"end":486,"name":"PUSH","source":0,"value":"40"},{"begin":477,"end":491,"name":"MSTORE","source":0},{"begin":537,"end":538,"name":"PUSH","source":0,"value":"4"},{"begin":521,"end":535,"name":"CALLDATASIZE","source":0},{"begin":518,"end":539,"name":"LT","source":0},{"begin":508,"end":1367,"name":"PUSH [tag]","source":0,"value":"1"},{"begin":508,"end":1367,"name":"JUMPI","source":0},{"begin":589,"end":590,"name":"PUSH","source":0,"value":"0"},{"begin":640,"end":642,"name":"DUP1","source":0},{"begin":627,"end":643,"name":"CALLDATALOAD","source":0},{"begin":622,"end":625,"name":"PUSH","source":0,"value":"E0"},{"begin":618,"end":644,"name":"SHR","source":0},{"begin":670,"end":680,"name":"PUSH","source":0,"value":"B4F40C61"},{"begin":665,"end":970,"name":"DUP2","source":0},{"begin":665,"end":970,"name":"EQ","source":0},{"begin":665,"end":970,"name":"PUSH [tag]","source":0,"value":"3"},{"begin":665,"end":970,"name":"JUMPI","source":0},{"begin":996,"end":1006,"name":"PUSH","source":0,"value":"E5AA3D58"},{"begin":991,"end":1349,"name":"DUP2","source":0},{"begin":991,"end":1349,"name":"EQ","source":0},{"begin":991,"end":1349,"name":"PUSH [tag]","source":0,"value":"4"},{"begin":991,"end":1349,"name":"JUMPI","source":0},{"begin":611,"end":1349,"name":"PUSH [tag]","source":0,"value":"2"},{"begin":611,"end":1349,"name":"JUMP","source":0},{"begin":665,"end":970,"name":"tag","source":0,"value":"3"},{"begin":665,"end":970,"name":"JUMPDEST","source":0},{"begin":710,"end":721,"name":"CALLVALUE","source":0},{"begin":707,"end":740,"name":"ISZERO","source":0},{"begin":707,"end":740,"name":"PUSH [tag]","source":0,"value":"5"},{"begin":707,"end":740,"name":"JUMPI","source":0},{"begin":735,"end":737,"name":"DUP2","source":0},{"begin":731,"end":733,"name":"DUP3","source":0},{"begin":724,"end":738,"name":"REVERT","source":0},{"begin":707,"end":740,"name":"tag","source":0,"value":"5"},{"begin":707,"end":740,"name":"JUMPDEST","source":0},{"begin":801,"end":803,"name":"DUP2","source":0},{"begin":796,"end":797,"name":"PUSH","source":0,"value":"3"},{"begin":792,"end":798,"name":"NOT","source":0},{"begin":776,"end":790,"name":"CALLDATASIZE","source":0},{"begin":772,"end":799,"name":"ADD","source":0},{"begin":768,"end":804,"name":"SLT","source":0},{"begin":765,"end":823,"name":"ISZERO","source":0},{"begin":765,"end":823,"name":"PUSH [tag]","source":0,"value":"6"},{"begin":765,"end":823,"name":"JUMPI","source":0},{"begin":818,"end":820,"name":"DUP2","source":0},{"begin":814,"end":816,"name":"DUP3","source":0},{"begin":807,"end":821,"name":"REVERT","source":0},{"begin":765,"end":823,"name":"tag","source":0,"value":"6"},{"begin":765,"end":823,"name":"JUMPDEST","source":0},{"begin":906,"end":907,"name":"PUSH","source":0,"value":"1"},{"begin":859,"end":908,"name":"SLOAD","source":0},{"begin":855,"end":857,"name":"DUP4","source":0},{"begin":848,"end":909,"name":"MSTORE","source":0},{"begin":945,"end":947,"name":"PUSH","source":0,"value":"20"},{"begin":941,"end":943,"name":"DUP4","source":0},{"begin":934,"end":948,"name":"RETURN","source":0},{"begin":991,"end":1349,"name":"tag","source":0,"value":"4"},{"begin":991,"end":1349,"name":"JUMPDEST","source":0},{"begin":1036,"end":1047,"name":"CALLVALUE","source":0},{"begin":1033,"end":1066,"name":"ISZERO","source":0},{"begin":1033,"end":1066,"name":"PUSH [tag]","source":0,"value":"7"},{"begin":1033,"end":1066,"name":"JUMPI","source":0},{"begin":1061,"end":1063,"name":"DUP2","source":0},{"begin":1057,"end":1059,"name":"DUP3","source":0},{"begin":1050,"end":1064,"name":"REVERT","source":0},{"begin":1033,"end":1066,"name":"tag","source":0,"value":"7"},{"begin":1033,"end":1066,"name":"JUMPDEST","source":0},{"begin":1127,"end":1129,"name":"DUP2","source":0},{"begin":1122,"end":1123,"name":"PUSH","source":0,"value":"3"},{"begin":1118,"end":1124,"name":"NOT","source":0},{"begin":1102,"end":1116,"name":"CALLDATASIZE","source":0},{"begin":1098,"end":1125,"name":"ADD","source":0},{"begin":1094,"end":1130,"name":"SLT","source":0},{"begin":1091,"end":1149,"name":"ISZERO","source":0},{"begin":1091,"end":1149,"name":"PUSH [tag]","source":0,"value":"8"},{"begin":1091,"end":1149,"name":"JUMPI","source":0},{"begin":1144,"end":1146,"name":"DUP2","source":0},{"begin":1140,"end":1142,"name":"DUP3","source":0},{"begin":1133,"end":1147,"name":"REVERT","source":0},{"begin":1091,"end":1149,"name":"tag","source":0,"value":"8"},{"begin":1091,"end":1149,"name":"JUMPDEST","source":0},{"begin":1190,"end":1192,"name":"DUP2","source":0},{"begin":1184,"end":1193,"name":"SLOAD","source":0},{"begin":1238,"end":1240,"name":"PUSH","source":0,"value":"40"},{"begin":1232,"end":1241,"name":"MLOAD","source":0},{"begin":1281,"end":1283,"name":"DUP2","source":0},{"begin":1273,"end":1279,"name":"DUP2","source":0},{"begin":1266,"end":1284,"name":"MSTORE","source":0},{"begin":1324,"end":1326,"name":"PUSH","source":0,"value":"20"},{"begin":1316,"end":1322,"name":"DUP2","source":0},{"begin":1309,"end":1327,"name":"RETURN","source":0},{"begin":611,"end":1349,"name":"tag","source":0,"value":"2"},{"begin":611,"end":1349,"name":"JUMPDEST","source":0},{"begin":611,"end":1349,"name":"POP","source":0},{"begin":557,"end":1367,"name":"POP","source":0},{"begin":508,"end":1367,"name":"tag","source":0,"value":"1"},{"begin":508,"end":1367,"name":"JUMPDEST","source":0},{"begin":1394,"end":1395,"name":"PUSH","source":0,"value":"0"},{"begin":1391,"end":1392,"name":"DUP1","source":0},{"begin":1384,"end":1396,"name":"REVERT","source":0}]}},"sourceList":[""]} diff --git a/test/cmdlineTests/asm_json_import_all_valid_flags/args b/test/cmdlineTests/asm_json_import_all_valid_flags/args new file mode 100644 index 000000000..e6010eac3 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_all_valid_flags/args @@ -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 - diff --git a/test/cmdlineTests/asm_json_import_all_valid_flags/output b/test/cmdlineTests/asm_json_import_all_valid_flags/output new file mode 100644 index 000000000..48b7b3e21 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_all_valid_flags/output @@ -0,0 +1,91 @@ +{ + "contracts": + { + "": + { + "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": "" +} +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" + ] +} diff --git a/test/cmdlineTests/asm_json_import_all_valid_flags/stdin b/test/cmdlineTests/asm_json_import_all_valid_flags/stdin new file mode 100644 index 000000000..bda42c5a3 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_all_valid_flags/stdin @@ -0,0 +1,22 @@ +{ + ".code": [ + { + "name": "PUSH", + "value": "0" + } + ], + ".data": { + "0": { + ".code": [ + { + "name": "PUSH", + "value": "1" + } + ] + } + }, + "sourceList": [ + "contract.sol", + "#utility.yul" + ] +} diff --git a/test/cmdlineTests/asm_json_import_difficulty_prevrandao/args b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_difficulty_prevrandao/output b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/output new file mode 100644 index 000000000..3d19f83d6 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/output @@ -0,0 +1,6 @@ +Opcodes: +PREVRANDAO PREVRANDAO +EVM assembly: + /* */ + prevrandao + prevrandao diff --git a/test/cmdlineTests/asm_json_import_difficulty_prevrandao/stdin b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/stdin new file mode 100644 index 000000000..70d8d364e --- /dev/null +++ b/test/cmdlineTests/asm_json_import_difficulty_prevrandao/stdin @@ -0,0 +1,6 @@ +{ + ".code": [ + { "name": "DIFFICULTY" }, + { "name": "PREVRANDAO" } + ] +} diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_hex/args b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_hex/err b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/err new file mode 100644 index 000000000..a28903be7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/err @@ -0,0 +1 @@ +Assembly Import Error: Member '.data' contains a value for '0' that is not a valid hexadecimal string. diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_hex/exit b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin new file mode 100644 index 000000000..4b5fdd8f9 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin @@ -0,0 +1,11 @@ +{ + ".code": [ + { + "name": "PUSH", + "value": "0" + } + ], + ".data": { + "0": "no-hex-string" + } +} diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_object/args b/test/cmdlineTests/asm_json_import_invalid_data_no_object/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_object/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_object/err b/test/cmdlineTests/asm_json_import_invalid_data_no_object/err new file mode 100644 index 000000000..9192c875e --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_object/err @@ -0,0 +1 @@ +Assembly Import Error: Key inside '.data' '0' can only be a valid hex-string or an object. diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_object/exit b/test/cmdlineTests/asm_json_import_invalid_data_no_object/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_object/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_invalid_data_no_object/stdin b/test/cmdlineTests/asm_json_import_invalid_data_no_object/stdin new file mode 100644 index 000000000..d7f976ac6 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_data_no_object/stdin @@ -0,0 +1,16 @@ +{ + ".code": [ + { + "name": "PUSH", + "value": "0" + } + ], + ".data": { + "0": [ + 0, + 1, + 2, + 3 + ] + } +} diff --git a/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/args b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/err b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/err new file mode 100644 index 000000000..183518103 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/err @@ -0,0 +1 @@ +Assembly Import Error: Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction 'PUSH') diff --git a/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/exit b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/stdin b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/stdin new file mode 100644 index 000000000..923ce3ece --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_jumptype_instruction/stdin @@ -0,0 +1,9 @@ +{ + ".code": [ + { + "name": "PUSH", + "value": "0", + "jumpType": "[in]" + } + ] +} diff --git a/test/cmdlineTests/asm_json_import_invalid_value/args b/test/cmdlineTests/asm_json_import_invalid_value/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_value/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_invalid_value/err b/test/cmdlineTests/asm_json_import_invalid_value/err new file mode 100644 index 000000000..c9fdeec82 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_value/err @@ -0,0 +1 @@ +Assembly Import Error: Member 'value' defined for instruction 'DIFFICULTY', but the instruction does not need a value. diff --git a/test/cmdlineTests/asm_json_import_invalid_value/exit b/test/cmdlineTests/asm_json_import_invalid_value/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_value/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_invalid_value/stdin b/test/cmdlineTests/asm_json_import_invalid_value/stdin new file mode 100644 index 000000000..e8da44996 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_invalid_value/stdin @@ -0,0 +1,8 @@ +{ + ".code": [ + { + "name": "DIFFICULTY", + "value": "0" + } + ] +} diff --git a/test/cmdlineTests/asm_json_import_no_optimiser/args b/test/cmdlineTests/asm_json_import_no_optimiser/args new file mode 100644 index 000000000..f649e9740 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_optimiser/args @@ -0,0 +1 @@ +--optimize --import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_no_optimiser/err b/test/cmdlineTests/asm_json_import_no_optimiser/err new file mode 100644 index 000000000..f9f4475ca --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_optimiser/err @@ -0,0 +1 @@ +Option --optimize is not supported with --import-asm-json. diff --git a/test/cmdlineTests/asm_json_import_no_optimiser/exit b/test/cmdlineTests/asm_json_import_no_optimiser/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_optimiser/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_no_optimiser/stdin b/test/cmdlineTests/asm_json_import_no_optimiser/stdin new file mode 100644 index 000000000..70d8d364e --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_optimiser/stdin @@ -0,0 +1,6 @@ +{ + ".code": [ + { "name": "DIFFICULTY" }, + { "name": "PREVRANDAO" } + ] +} diff --git a/test/cmdlineTests/asm_json_import_no_value/args b/test/cmdlineTests/asm_json_import_no_value/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_value/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_no_value/err b/test/cmdlineTests/asm_json_import_no_value/err new file mode 100644 index 000000000..d229e07d3 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_value/err @@ -0,0 +1 @@ +Assembly Import Error: Member 'value' was not defined for instruction 'PUSH', but the instruction needs a value. diff --git a/test/cmdlineTests/asm_json_import_no_value/exit b/test/cmdlineTests/asm_json_import_no_value/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_value/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_no_value/stdin b/test/cmdlineTests/asm_json_import_no_value/stdin new file mode 100644 index 000000000..5556712bd --- /dev/null +++ b/test/cmdlineTests/asm_json_import_no_value/stdin @@ -0,0 +1,5 @@ +{ + ".code": [ + { "name": "PUSH" } + ] +} diff --git a/test/cmdlineTests/asm_json_import_other_fields/args b/test/cmdlineTests/asm_json_import_other_fields/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_other_fields/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_other_fields/err b/test/cmdlineTests/asm_json_import_other_fields/err new file mode 100644 index 000000000..bcbfd631c --- /dev/null +++ b/test/cmdlineTests/asm_json_import_other_fields/err @@ -0,0 +1 @@ +Assembly Import Error: Unknown member '_name'. Valid members are begin, end, jumpType, modifierDepth, name, source, value. diff --git a/test/cmdlineTests/asm_json_import_other_fields/exit b/test/cmdlineTests/asm_json_import_other_fields/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_other_fields/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_other_fields/stdin b/test/cmdlineTests/asm_json_import_other_fields/stdin new file mode 100644 index 000000000..bb82f2de0 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_other_fields/stdin @@ -0,0 +1,5 @@ +{ + ".code": [ + { "_name": "DIFFICULTY" } + ] +} diff --git a/test/cmdlineTests/asm_json_import_untagged_jumpdest/args b/test/cmdlineTests/asm_json_import_untagged_jumpdest/args new file mode 100644 index 000000000..2b4b3a153 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_untagged_jumpdest/args @@ -0,0 +1 @@ +--import-asm-json - --opcodes --asm diff --git a/test/cmdlineTests/asm_json_import_untagged_jumpdest/err b/test/cmdlineTests/asm_json_import_untagged_jumpdest/err new file mode 100644 index 000000000..a66226c20 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_untagged_jumpdest/err @@ -0,0 +1 @@ +Assembly Import Error: JUMPDEST instruction found that was not followed by tag. diff --git a/test/cmdlineTests/asm_json_import_untagged_jumpdest/exit b/test/cmdlineTests/asm_json_import_untagged_jumpdest/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/asm_json_import_untagged_jumpdest/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin b/test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin new file mode 100644 index 000000000..a40284cdb --- /dev/null +++ b/test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin @@ -0,0 +1,10 @@ +{ + ".code": [ + { + "name": "tag", + "value": "0x00" + }, + { "name": "JUMPDEST" }, + { "name": "JUMPDEST" } + ] +} diff --git a/test/cmdlineTests/strict_asm_output_selection_invalid/err b/test/cmdlineTests/strict_asm_output_selection_invalid/err index 9c9104482..fd6ffc1c7 100644 --- a/test/cmdlineTests/strict_asm_output_selection_invalid/err +++ b/test/cmdlineTests/strict_asm_output_selection_invalid/err @@ -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. diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index febe4a59b..c8446610c 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -55,13 +55,9 @@ BOOST_AUTO_TEST_SUITE(Assembler) BOOST_AUTO_TEST_CASE(all_assembly_items) { - map indices = { - { "root.asm", 0 }, - { "sub.asm", 1 }, - { "verbatim.asm", 2 } - }; EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); Assembly _assembly{evmVersion, false, {}}; + _assembly.setSourceList({"root.asm", "sub.asm", "verbatim.asm"}); auto root_asm = make_shared("root.asm"); _assembly.setSourceLocation({1, 3, root_asm}); @@ -212,7 +208,7 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) }; Json::Value 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) @@ -301,12 +297,9 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps) BOOST_AUTO_TEST_CASE(immutable) { - map indices = { - { "root.asm", 0 }, - { "sub.asm", 1 } - }; EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); Assembly _assembly{evmVersion, true, {}}; + _assembly.setSourceList({"root.asm", "sub.asm"}); auto root_asm = make_shared("root.asm"); _assembly.setSourceLocation({1, 3, root_asm}); @@ -383,7 +376,7 @@ BOOST_AUTO_TEST_CASE(immutable) "}\n" ); BOOST_CHECK_EQUAL( - util::jsonCompactPrint(_assembly.assemblyJSON(indices)), + util::jsonCompactPrint(_assembly.assemblyJSON()), "{\".code\":[" "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp index 8124b1545..300224b35 100644 --- a/test/solc/CommandLineInterface.cpp +++ b/test/solc/CommandLineInterface.cpp @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(version) BOOST_AUTO_TEST_CASE(multiple_input_modes) { - array inputModeOptions = { + array inputModeOptions = { "--help", "--license", "--version", @@ -158,10 +158,11 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes) "--strict-assembly", "--yul", "--import-ast", + "--import-asm-json", }; string expectedMessage = "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."; for (string const& mode1: inputModeOptions)