libevmasm: Add support to import evm assembly json.

This commit is contained in:
Alexander Arlt 2022-03-22 16:56:35 -05:00
parent 5f63b3ca21
commit f042c6c32f
12 changed files with 477 additions and 67 deletions

View File

@ -74,6 +74,117 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
}
}
void Assembly::addAssemblyItemsFromJSON(Json::Value const& _code)
{
solAssert(_code.isArray(), "");
for (auto const& jsonItem: _code)
m_items.emplace_back(loadItemFromJSON(jsonItem));
for (auto current = m_items.begin(); current != m_items.end(); ++current)
{
// During the assembly json export a `JUMPDEST` is always generated after a `tag`.
// So we just ignore exactly these `JUMPDEST`'s.
auto const next = std::next(current);
if (
next != m_items.end() &&
current->type() == AssemblyItemType::Tag &&
next->type() == AssemblyItemType::Operation &&
next->instruction() == Instruction::JUMPDEST
)
m_items.erase(next);
}
}
AssemblyItem Assembly::loadItemFromJSON(Json::Value const& _json)
{
std::string name = _json["name"].isString() ? _json["name"].asString() : "";
int begin = _json["begin"].isInt() ? _json["begin"].asInt() : -1;
int end = _json["end"].isInt() ? _json["end"].asInt() : -1;
int srcIndex = _json["source"].isInt() ? _json["source"].asInt() : -1;
size_t modifierDepth = _json["modifierDepth"].isInt() ? static_cast<size_t>(_json["modifierDepth"].asInt()) : 0;
std::string value = _json["value"].isString() ? _json["value"].asString() : "";
std::string jumpType = _json["jumpType"].isString() ? _json["jumpType"].asString() : "";
solAssert(!name.empty(), "");
auto updateUsedTags = [&](u256 const& data) {
auto tag = static_cast<unsigned>(data);
if (m_usedTags <= tag)
m_usedTags = tag + 1;
return data;
};
auto immutableHash = [&](string const& _immutableName) -> h256 {
h256 hash(util::keccak256(value));
m_immutables[hash] = _immutableName;
return hash;
};
auto libraryHash = [&](string const& _libraryName) -> h256 {
h256 hash(util::keccak256(value));
m_libraries[hash] = _libraryName;
return hash;
};
SourceLocation location;
location.start = begin;
location.end = end;
if (srcIndex > -1 && srcIndex < static_cast<int>(sources().size()))
location.sourceName = sources()[static_cast<size_t>(srcIndex)];
AssemblyItem result(0);
if (c_instructions.find(name) != c_instructions.end())
{
AssemblyItem item{c_instructions.at(name), location};
item.m_modifierDepth = modifierDepth;
if (!jumpType.empty())
item.setJumpType(jumpType);
result = item;
}
else
{
if (name == "PUSH")
{
AssemblyItem item{AssemblyItemType::Push, u256("0x" + value), location};
if (!jumpType.empty())
item.setJumpType(jumpType);
result = item;
}
else if (name == "PUSH [ErrorTag]")
result = {AssemblyItemType::PushTag, 0, location};
else if (name == "PUSH [tag]")
result = {AssemblyItemType::PushTag, updateUsedTags(u256(value)), location};
else if (name == "PUSH [$]")
result = {AssemblyItemType::PushSub, u256("0x" + value), location};
else if (name == "PUSH #[$]")
result = {AssemblyItemType::PushSubSize, u256("0x" + value), location};
else if (name == "PUSHSIZE")
result = {AssemblyItemType::PushProgramSize, 0, location};
else if (name == "PUSHLIB")
result = {AssemblyItemType::PushLibraryAddress, libraryHash(value), location};
else if (name == "PUSHDEPLOYADDRESS")
result = {AssemblyItemType::PushDeployTimeAddress, 0, location};
else if (name == "PUSHIMMUTABLE")
result = {AssemblyItemType::PushImmutable, immutableHash(value), location};
else if (name == "ASSIGNIMMUTABLE")
result = {AssemblyItemType::AssignImmutable, immutableHash(value), location};
else if (name == "tag")
result = {AssemblyItemType::Tag, updateUsedTags(u256(value)), location};
else if (name == "PUSH data")
result = {AssemblyItemType::PushData, u256("0x" + value), location};
else if (name == "VERBATIM")
{
AssemblyItem item(fromHex(value), 0, 0);
item.setLocation(location);
result = item;
}
else
assertThrow(false, InvalidOpcode, "");
}
result.m_modifierDepth = modifierDepth;
return result;
}
namespace
{
@ -298,6 +409,43 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices,
return root;
}
bool Assembly::loadFromAssemblyJSON(Json::Value const& _json, bool _loadSources /* = true */)
{
if (!_json[".code"].isArray())
return false;
bool success{true};
if (_loadSources)
{
vector<string> sourceList;
if (_json.isMember("sourceList"))
for (auto const& it: _json["sourceList"])
sourceList.emplace_back(it.asString());
setSources(sourceList);
}
addAssemblyItemsFromJSON(_json[".code"]);
if (_json[".auxdata"].isString())
m_auxiliaryData = fromHex(_json[".auxdata"].asString());
Json::Value const& data = _json[".data"];
for (Json::ValueConstIterator itr = data.begin(); itr != data.end(); itr++)
{
solAssert(itr.key().isString(), "");
std::string key = itr.key().asString();
Json::Value const& code = data[key];
if (code.isString())
m_data[h256(fromHex(key))] = fromHex(code.asString());
else
{
shared_ptr<Assembly> subassembly = make_shared<Assembly>(false, "");
subassembly->setSources(sources());
success &= subassembly->loadFromAssemblyJSON(code, false);
m_subs.emplace_back(subassembly);
}
}
return success;
}
AssemblyItem Assembly::namedTag(string const& _name, size_t _params, size_t _returns, optional<uint64_t> _sourceID)
{
assertThrow(!_name.empty(), AssemblyException, "Empty named tag.");

View File

@ -152,6 +152,12 @@ public:
bool _includeSourceList = true
) const;
/// Loads the JSON representation of assembly.
/// @param _json JSON object containing assembly
/// @param _loadSources true, if source list should be included, false otherwise.
/// @returns true on success, false otherwise
bool loadFromAssemblyJSON(Json::Value const& _json, bool _loadSources = true);
/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
void markAsInvalid() { m_invalid = true; }
@ -160,6 +166,22 @@ public:
bool isCreation() const { return m_creation; }
/// Set the source list.
void setSources(std::vector<std::shared_ptr<std::string const>> _sources)
{
m_sources = std::move(_sources);
}
/// Set the source list from simple vector<string>.
void setSources(std::vector<std::string> const& _sources)
{
for (auto const& item: _sources)
m_sources.emplace_back(std::make_shared<std::string>(item));
}
/// @returns List of sources.
std::vector<std::shared_ptr<std::string const>> sources() const& { return m_sources; }
protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
@ -168,6 +190,14 @@ protected:
unsigned codeSize(unsigned subTagSize) const;
/// Add all assembly items from given JSON array.
void addAssemblyItemsFromJSON(Json::Value const& _code);
/// Creates an AssemblyItem from a given JSON representation.
/// @param _json JSON representation of an assembly item
/// @returns AssemblyItem from a given JSON representation.
AssemblyItem loadItemFromJSON(Json::Value const& _json);
private:
bool m_invalid = false;
@ -214,6 +244,7 @@ protected:
std::string m_name;
langutil::SourceLocation m_currentSourceLocation;
std::vector<std::shared_ptr<std::string const>> m_sources;
public:
size_t m_currentModifierDepth = 0;

View File

@ -243,6 +243,18 @@ string AssemblyItem::getJumpTypeAsString() const
}
}
void AssemblyItem::setJumpType(std::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
assertThrow(false, AssemblyException, "Invalid jump type.");
}
string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
{
string text;

View File

@ -173,6 +173,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;

View File

@ -415,6 +415,28 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
storeContractDefinitions();
}
void CompilerStack::importEvmAssemblyJson(std::map<std::string, Json::Value> const& _sources)
{
solAssert(_sources.size() == 1, "");
solAssert(m_sources.empty(), "");
solAssert(m_sourceOrder.empty(), "");
if (m_stackState != Empty)
solThrow(CompilerError, "Must call importEvmAssemblyJson only before the SourcesSet state.");
Json::Value jsonValue = _sources.begin()->second;
if (jsonValue.isMember("sourceList"))
for (auto const& item: jsonValue["sourceList"])
{
Source source;
source.charStream = std::make_shared<CharStream>(item.asString(), "");
m_sources.emplace(std::make_pair(item.asString(), source));
m_sourceOrder.push_back(&m_sources[item.asString()]);
}
m_evmAssemblyJson[_sources.begin()->first] = jsonValue;
m_importedSources = true;
m_stackState = SourcesSet;
}
bool CompilerStack::analyze()
{
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed)
@ -604,6 +626,9 @@ bool CompilerStack::parseAndAnalyze(State _stopAfter)
{
m_stopAfter = _stopAfter;
if (!m_evmAssemblyJson.empty())
return true;
bool success = parse();
if (m_stackState >= m_stopAfter)
return success;
@ -653,55 +678,85 @@ bool CompilerStack::compile(State _stopAfter)
// Only compile contracts individually which have been requested.
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
try
if (!m_evmAssemblyJson.empty())
{
solAssert(m_importedSources, "");
solAssert(m_evmAssemblyJson.size() == 1, "");
string const evmSourceName = m_evmAssemblyJson.begin()->first;
Json::Value const evmJson = m_evmAssemblyJson.begin()->second;
evmasm::Assembly::OptimiserSettings optimiserSettings;
optimiserSettings.evmVersion = m_evmVersion;
optimiserSettings.expectedExecutionsPerDeployment = m_optimiserSettings.expectedExecutionsPerDeployment;
optimiserSettings.runCSE = m_optimiserSettings.runCSE;
optimiserSettings.runConstantOptimiser = m_optimiserSettings.runConstantOptimiser;
optimiserSettings.runDeduplicate = m_optimiserSettings.runDeduplicate;
optimiserSettings.runInliner = m_optimiserSettings.runInliner;
optimiserSettings.runJumpdestRemover = m_optimiserSettings.runJumpdestRemover;
optimiserSettings.runPeephole = m_optimiserSettings.runPeephole;
m_contracts[evmSourceName].evmAssembly = make_shared<evmasm::Assembly>(true, evmSourceName);
m_contracts[evmSourceName].evmAssembly->loadFromAssemblyJSON(m_evmAssemblyJson[evmSourceName]);
if (m_optimiserSettings.enabled)
m_contracts[evmSourceName].evmAssembly->optimise(optimiserSettings);
m_contracts[evmSourceName].object = m_contracts[evmSourceName].evmAssembly->assemble();
m_contracts[evmSourceName].evmRuntimeAssembly = make_shared<evmasm::Assembly>(false, evmSourceName);
m_contracts[evmSourceName].evmRuntimeAssembly->setSources(m_contracts[evmSourceName].evmAssembly->sources());
m_contracts[evmSourceName].evmRuntimeAssembly->loadFromAssemblyJSON(m_evmAssemblyJson[evmSourceName][".data"]["0"], false);
if (m_optimiserSettings.enabled)
m_contracts[evmSourceName].evmRuntimeAssembly->optimise(optimiserSettings);
m_contracts[evmSourceName].runtimeObject = m_contracts[evmSourceName].evmRuntimeAssembly->assemble();
}
else
{
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
if (m_viaIR || m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEvmBytecode)
try
{
if (m_viaIR)
generateEVMFromIR(*contract);
else
compileContract(*contract, otherCompilers);
if (m_viaIR || m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEvmBytecode)
{
if (m_viaIR)
generateEVMFromIR(*contract);
else
compileContract(*contract, otherCompilers);
}
if (m_generateEwasm)
generateEwasm(*contract);
}
if (m_generateEwasm)
generateEwasm(*contract);
}
catch (Error const& _error)
{
if (_error.type() != Error::Type::CodeGenerationError)
throw;
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
return false;
}
catch (UnimplementedFeatureError const& _unimplementedError)
{
if (
SourceLocation const* sourceLocation =
boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError)
)
catch (Error const& _error)
{
string const* comment = _unimplementedError.comment();
m_errorReporter.error(
1834_error,
Error::Type::CodeGenerationError,
*sourceLocation,
"Unimplemented feature error" +
((comment && !comment->empty()) ? ": " + *comment : string{}) +
" in " +
_unimplementedError.lineInfo()
);
if (_error.type() != Error::Type::CodeGenerationError)
throw;
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
return false;
}
else
throw;
catch (UnimplementedFeatureError const& _unimplementedError)
{
if (SourceLocation const* sourceLocation
= boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError))
{
string const* comment = _unimplementedError.comment();
m_errorReporter.error(
1834_error,
Error::Type::CodeGenerationError,
*sourceLocation,
"Unimplemented feature error"
+ ((comment && !comment->empty()) ? ": " + *comment : string{}) + " in "
+ _unimplementedError.lineInfo());
return false;
}
else
throw;
}
}
}
}
m_stackState = CompilationSuccessful;
this->link();
return true;
@ -937,14 +992,21 @@ vector<string> CompilerStack::sourceNames() const
return names;
}
map<string, unsigned> CompilerStack::sourceIndices() const
map<string, unsigned> CompilerStack::sourceIndices(bool _includeInternalSources /* = true */) const
{
map<string, unsigned> 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_evmAssemblyJson.empty())
{
for (auto const& s: m_sources)
indices[s.first] = index++;
solAssert(!indices.count(CompilerContext::yulUtilityFileName()), "");
if (_includeInternalSources)
indices[CompilerContext::yulUtilityFileName()] = index++;
}
else
for (auto const& s: m_sourceOrder)
indices[s->charStream->source()] = index++;
return indices;
}

View File

@ -222,6 +222,10 @@ public:
/// Will throw errors if the import fails
void importASTs(std::map<std::string, Json::Value> const& _sources);
/// Imports given Evm Assembly Json. Leads to the same internal state as parse().
/// Will throw errors if the import fails
void importEvmAssemblyJson(std::map<std::string, Json::Value> const& _sources);
/// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving,
/// typechecking, staticAnalysis) on previously parsed sources.
/// @returns false on error.
@ -240,7 +244,7 @@ public:
/// @returns a mapping assigning each source name its index inside the vector returned
/// by sourceNames().
std::map<std::string, unsigned> sourceIndices() const;
std::map<std::string, unsigned> sourceIndices(bool _includeInternalSources = true) const;
/// @returns the previously used character stream, useful for counting lines during error reporting.
langutil::CharStream const& charStream(std::string const& _sourceName) const override;
@ -498,6 +502,7 @@ private:
std::map<std::string const, Source> m_sources;
// if imported, store AST-JSONS for each filename
std::map<std::string, Json::Value> m_sourceJsons;
std::map<std::string, Json::Value> m_evmAssemblyJson;
std::vector<std::string> m_unhandledSMTLib2Queries;
std::map<util::h256, std::string> m_smtlib2Responses;
std::shared_ptr<GlobalContext> m_globalContext;

View File

@ -155,6 +155,8 @@ struct OptimiserSettings
/// This specifies an estimate on how often each opcode in this assembly will be executed,
/// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
size_t expectedExecutionsPerDeployment = 200;
/// Flag reflecting whether optimizer is enabled.
bool enabled = false;
};
}

View File

@ -161,7 +161,11 @@ static bool coloredOutput(CommandLineOptions const& _options)
void CommandLineInterface::handleBinary(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (m_options.compiler.outputs.binary)
{
@ -187,7 +191,11 @@ void CommandLineInterface::handleBinary(string const& _contract)
void CommandLineInterface::handleOpcode(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", evmasm::disassemble(m_compiler->object(_contract).bytecode));
@ -201,7 +209,11 @@ void CommandLineInterface::handleOpcode(string const& _contract)
void CommandLineInterface::handleIR(string const& _contractName)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.ir)
return;
@ -217,7 +229,11 @@ void CommandLineInterface::handleIR(string const& _contractName)
void CommandLineInterface::handleIROptimized(string const& _contractName)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.irOptimized)
return;
@ -233,7 +249,11 @@ void CommandLineInterface::handleIROptimized(string const& _contractName)
void CommandLineInterface::handleEwasm(string const& _contractName)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.ewasm)
return;
@ -256,7 +276,11 @@ void CommandLineInterface::handleEwasm(string const& _contractName)
void CommandLineInterface::handleBytecode(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (m_options.compiler.outputs.opcodes)
handleOpcode(_contract);
@ -266,7 +290,11 @@ void CommandLineInterface::handleBytecode(string const& _contract)
void CommandLineInterface::handleSignatureHashes(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.signatureHashes)
return;
@ -298,7 +326,11 @@ void CommandLineInterface::handleSignatureHashes(string const& _contract)
void CommandLineInterface::handleMetadata(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.metadata)
return;
@ -312,7 +344,11 @@ void CommandLineInterface::handleMetadata(string const& _contract)
void CommandLineInterface::handleABI(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.abi)
return;
@ -326,7 +362,11 @@ void CommandLineInterface::handleABI(string const& _contract)
void CommandLineInterface::handleStorageLayout(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.storageLayout)
return;
@ -340,7 +380,11 @@ void CommandLineInterface::handleStorageLayout(string const& _contract)
void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
bool enabled = false;
std::string suffix;
@ -383,7 +427,11 @@ void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contra
void CommandLineInterface::handleGasEstimation(string const& _contract)
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
Json::Value estimates = m_compiler->gasEstimates(_contract);
sout() << "Gas estimation:" << endl;
@ -556,6 +604,25 @@ map<string, Json::Value> CommandLineInterface::parseAstFromInput()
return sourceJsons;
}
map<string, Json::Value> CommandLineInterface::parseEvmAssemblyJsonFromInput()
{
solAssert(m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "");
solAssert(m_fileReader.sourceUnits().size() == 1, "");
map<string, Json::Value> sourceJsons;
for (auto const& iter: m_fileReader.sourceUnits())
{
Json::Value evmAsmJson;
astAssert(jsonParseStrict(iter.second, evmAsmJson), "Input file could not be parsed to JSON");
astAssert(evmAsmJson.isMember(".code"), "Invalid Format for assembly-JSON: Must have '.code'-object");
astAssert(evmAsmJson.isMember(".data"), "Invalid Format for assembly-JSON: Must have '.data'-object");
sourceJsons[iter.first] = evmAsmJson;
}
return sourceJsons;
}
void CommandLineInterface::createFile(string const& _fileName, string const& _data)
{
namespace fs = boost::filesystem;
@ -659,6 +726,7 @@ void CommandLineInterface::processInput()
break;
case InputMode::Compiler:
case InputMode::CompilerWithASTImport:
case InputMode::CompilerWithEvmAssemblyJsonImport:
compile();
outputCompilationResults();
}
@ -679,7 +747,11 @@ void CommandLineInterface::printLicense()
void CommandLineInterface::compile()
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
m_compiler = make_unique<CompilerStack>(m_fileReader.reader());
@ -726,7 +798,18 @@ void CommandLineInterface::compile()
m_compiler->setOptimiserSettings(m_options.optimiserSettings());
if (m_options.input.mode == InputMode::CompilerWithASTImport)
if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport)
{
try
{
m_compiler->importEvmAssemblyJson(parseEvmAssemblyJsonFromInput());
}
catch (Exception const& _exc)
{
solThrow(CommandLineExecutionError, "Failed to import Evm Assembly JSON: "s + _exc.what());
}
}
else if (m_options.input.mode == InputMode::CompilerWithASTImport)
{
try
{
@ -786,7 +869,11 @@ void CommandLineInterface::compile()
void CommandLineInterface::handleCombinedJSON()
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.combinedJsonRequests.has_value())
return;
@ -878,7 +965,11 @@ void CommandLineInterface::handleCombinedJSON()
void CommandLineInterface::handleAst()
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
if (!m_options.compiler.outputs.astCompactJson)
return;
@ -1120,7 +1211,11 @@ void CommandLineInterface::assemble(yul::YulStack::Language _language, yul::YulS
void CommandLineInterface::outputCompilationResults()
{
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::CompilerWithEvmAssemblyJsonImport, ""
);
handleCombinedJSON();

View File

@ -115,6 +115,8 @@ private:
/// or standard-json output
std::map<std::string, Json::Value> parseAstFromInput();
std::map<std::string, Json::Value> parseEvmAssemblyJsonFromInput();
/// Create a file in the given directory
/// @arg _fileName the name of the file
/// @arg _data to be written

View File

@ -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";
@ -138,6 +139,7 @@ static map<InputMode, string> const g_inputModeName = {
{InputMode::StandardJson, "standard JSON"},
{InputMode::Linker, "linker"},
{InputMode::LanguageServer, "language server (LSP)"},
{InputMode::CompilerWithEvmAssemblyJsonImport, "assembler (EVM ASM JSON import)"},
};
void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
@ -277,6 +279,8 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const
solAssert(settings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps);
}
settings.enabled = optimizer.enabled;
return settings;
}
@ -461,6 +465,13 @@ void CommandLineParser::parseOutputSelection()
CompilerOutputs::componentName(&CompilerOutputs::ewasm),
CompilerOutputs::componentName(&CompilerOutputs::ewasmIR),
};
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)
{
@ -472,6 +483,8 @@ void CommandLineParser::parseOutputSelection()
case InputMode::Compiler:
case InputMode::CompilerWithASTImport:
return util::contains(compilerModeOutputs, _outputName);
case InputMode::CompilerWithEvmAssemblyJsonImport:
return util::contains(evmAssemblyJsonImportModeOutputs, _outputName);
case InputMode::Assembler:
return util::contains(assemblerModeOutputs, _outputName);
case InputMode::StandardJson:
@ -650,6 +663,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 JSON, assumes input holds the EVM Assembly in JSON format."
)
(
g_strLSP.c_str(),
"Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend "
@ -906,6 +923,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::CompilerWithEvmAssemblyJsonImport;
else
m_options.input.mode = InputMode::Compiler;
@ -970,9 +989,27 @@ void CommandLineParser::processArgs()
for (auto& option: conflictingWithStopAfter)
checkMutuallyExclusive({g_strStopAfter, option});
array<string, 11> const conflictingWithAsmJsonImport{
CompilerOutputs::componentName(&CompilerOutputs::ir),
CompilerOutputs::componentName(&CompilerOutputs::irOptimized),
CompilerOutputs::componentName(&CompilerOutputs::ewasm),
CompilerOutputs::componentName(&CompilerOutputs::ewasmIR),
g_strGas,
CompilerOutputs::componentName(&CompilerOutputs::metadata),
CompilerOutputs::componentName(&CompilerOutputs::natspecDev),
CompilerOutputs::componentName(&CompilerOutputs::natspecUser),
CompilerOutputs::componentName(&CompilerOutputs::signatureHashes),
CompilerOutputs::componentName(&CompilerOutputs::storageLayout),
CompilerOutputs::componentName(&CompilerOutputs::astCompactJson),
};
for (auto& option: conflictingWithAsmJsonImport)
checkMutuallyExclusive({g_strImportEvmAssemblerJson, option});
if (
m_options.input.mode != InputMode::Compiler &&
m_options.input.mode != InputMode::CompilerWithASTImport &&
m_options.input.mode != InputMode::CompilerWithEvmAssemblyJsonImport &&
m_options.input.mode != InputMode::Assembler
)
{
@ -1289,7 +1326,11 @@ 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::CompilerWithEvmAssemblyJsonImport
);
}
void CommandLineParser::parseCombinedJsonOption()

View File

@ -56,7 +56,8 @@ enum class InputMode
StandardJson,
Linker,
Assembler,
LanguageServer
LanguageServer,
CompilerWithEvmAssemblyJsonImport
};
struct CompilerOutputs

View File

@ -673,6 +673,16 @@ SOLTMPDIR=$(mktemp -d)
fi
)
rm -r "$SOLTMPDIR"
printTask "Testing EVM Assembly JSON import/export..."
SOLTMPDIR=$(mktemp -d)
(
cd "$SOLTMPDIR"
if ! "$REPO_ROOT/scripts/ImportExportTest.sh" evm-assembly
then
rm -r "$SOLTMPDIR"
fail
fi
)
printTask "Testing AST export with stop-after=parsing..."
"$REPO_ROOT/test/stopAfterParseTests.sh"