Skip to content

Commit 67e11fc

Browse files
aarltcameelr0qs
committed
EVM assembly import via --import-asm-json
Co-authored-by: Kamil Śliwak <[email protected]> Co-authored-by: r0qs <[email protected]>
1 parent 3e823e0 commit 67e11fc

File tree

79 files changed

+2195
-16
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2195
-16
lines changed

Diff for: Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Language Features:
77
Compiler Features:
88
* Code Generator: Remove redundant overflow checks of certain ``for`` loops when the counter variable cannot overflow.
99
* Commandline Interface: Add ``--no-import-callback`` option that prevents the compiler from loading source files not given explicitly on the CLI or in Standard JSON input.
10+
* Commandline Interface: Add an experimental ``--import-asm-json`` option that can import EVM assembly in the format used by ``--asm-json``.
1011
* Commandline Interface: Use proper severity and coloring also for error messages produced outside of the compilation pipeline.
1112
* EVM: Deprecate support for "homestead", "tangerineWhistle", "spuriousDragon" and "byzantium" EVM versions.
1213
* Parser: Remove the experimental error recovery mode (``--error-recovery`` / ``settings.parserErrorRecovery``).

Diff for: libevmasm/Assembly.cpp

+356-1
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,27 @@
3434
#include <liblangutil/CharStream.h>
3535
#include <liblangutil/Exceptions.h>
3636

37-
#include <json/json.h>
37+
#include <libsolutil/JSON.h>
38+
#include <libsolutil/StringUtils.h>
39+
40+
#include <fmt/format.h>
3841

3942
#include <range/v3/algorithm/any_of.hpp>
43+
#include <range/v3/view/drop_exactly.hpp>
4044
#include <range/v3/view/enumerate.hpp>
45+
#include <range/v3/view/map.hpp>
4146

4247
#include <fstream>
4348
#include <limits>
49+
#include <iterator>
4450

4551
using namespace solidity;
4652
using namespace solidity::evmasm;
4753
using namespace solidity::langutil;
4854
using namespace solidity::util;
4955

56+
std::map<std::string, std::shared_ptr<std::string const>> Assembly::s_sharedSourceNames;
57+
5058
AssemblyItem const& Assembly::append(AssemblyItem _i)
5159
{
5260
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
@@ -73,6 +81,205 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
7381
}
7482
}
7583

84+
void Assembly::importAssemblyItemsFromJSON(Json::Value const& _code, std::vector<std::string> const& _sourceList)
85+
{
86+
solAssert(m_items.empty());
87+
solRequire(_code.isArray(), AssemblyImportException, "Supplied JSON is not an array.");
88+
for (auto jsonItemIter = std::begin(_code); jsonItemIter != std::end(_code); ++jsonItemIter)
89+
{
90+
AssemblyItem const& newItem = m_items.emplace_back(createAssemblyItemFromJSON(*jsonItemIter, _sourceList));
91+
if (newItem == Instruction::JUMPDEST)
92+
solThrow(AssemblyImportException, "JUMPDEST instruction without a tag");
93+
else if (newItem.type() == AssemblyItemType::Tag)
94+
{
95+
++jsonItemIter;
96+
if (jsonItemIter != std::end(_code) && createAssemblyItemFromJSON(*jsonItemIter, _sourceList) != Instruction::JUMPDEST)
97+
solThrow(AssemblyImportException, "JUMPDEST expected after tag.");
98+
}
99+
}
100+
}
101+
102+
AssemblyItem Assembly::createAssemblyItemFromJSON(Json::Value const& _json, std::vector<std::string> const& _sourceList)
103+
{
104+
solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object.");
105+
static std::set<std::string> const validMembers{"name", "begin", "end", "source", "value", "modifierDepth", "jumpType"};
106+
for (std::string const& member: _json.getMemberNames())
107+
solRequire(
108+
validMembers.count(member),
109+
AssemblyImportException,
110+
fmt::format(
111+
"Unknown member '{}'. Valid members are: {}.",
112+
member,
113+
solidity::util::joinHumanReadable(validMembers, ", ")
114+
)
115+
);
116+
solRequire(isOfType<std::string>(_json["name"]), AssemblyImportException, "Member 'name' missing or not of type string.");
117+
solRequire(isOfTypeIfExists<int>(_json, "begin"), AssemblyImportException, "Optional member 'begin' not of type int.");
118+
solRequire(isOfTypeIfExists<int>(_json, "end"), AssemblyImportException, "Optional member 'end' not of type int.");
119+
solRequire(isOfTypeIfExists<int>(_json, "source"), AssemblyImportException, "Optional member 'source' not of type int.");
120+
solRequire(isOfTypeIfExists<std::string>(_json, "value"), AssemblyImportException, "Optional member 'value' not of type string.");
121+
solRequire(isOfTypeIfExists<int>(_json, "modifierDepth"), AssemblyImportException, "Optional member 'modifierDepth' not of type int.");
122+
solRequire(isOfTypeIfExists<std::string>(_json, "jumpType"), AssemblyImportException, "Optional member 'jumpType' not of type string.");
123+
124+
std::string name = get<std::string>(_json["name"]);
125+
solRequire(!name.empty(), AssemblyImportException, "Member 'name' is empty.");
126+
127+
SourceLocation location;
128+
location.start = get<int>(_json["begin"]);
129+
location.end = get<int>(_json["end"]);
130+
int srcIndex = getOrDefault<int>(_json["source"], -1);
131+
size_t modifierDepth = static_cast<size_t>(getOrDefault<int>(_json["modifierDepth"], 0));
132+
std::string value = getOrDefault<std::string>(_json["value"], "");
133+
std::string jumpType = getOrDefault<std::string>(_json["jumpType"], "");
134+
135+
auto updateUsedTags = [&](u256 const& data)
136+
{
137+
m_usedTags = std::max(m_usedTags, static_cast<unsigned>(data) + 1);
138+
return data;
139+
};
140+
141+
auto storeImmutableHash = [&](std::string const& _immutableName) -> h256
142+
{
143+
h256 hash(util::keccak256(_immutableName));
144+
solAssert(m_immutables.count(hash) == 0 || m_immutables[hash] == _immutableName);
145+
m_immutables[hash] = _immutableName;
146+
return hash;
147+
};
148+
149+
auto storeLibraryHash = [&](std::string const& _libraryName) -> h256
150+
{
151+
h256 hash(util::keccak256(_libraryName));
152+
solAssert(m_libraries.count(hash) == 0 || m_libraries[hash] == _libraryName);
153+
m_libraries[hash] = _libraryName;
154+
return hash;
155+
};
156+
157+
auto requireValueDefinedForInstruction = [&](std::string const& _name, std::string const& _value)
158+
{
159+
solRequire(
160+
!_value.empty(),
161+
AssemblyImportException,
162+
"Member 'value' is missing for instruction '" + _name + "', but the instruction needs a value."
163+
);
164+
};
165+
166+
auto requireValueUndefinedForInstruction = [&](std::string const& _name, std::string const& _value)
167+
{
168+
solRequire(
169+
_value.empty(),
170+
AssemblyImportException,
171+
"Member 'value' defined for instruction '" + _name + "', but the instruction does not need a value."
172+
);
173+
};
174+
175+
solRequire(srcIndex >= -1 && srcIndex < static_cast<int>(_sourceList.size()), AssemblyImportException, "Source index out of bounds.");
176+
if (srcIndex != -1)
177+
location.sourceName = sharedSourceName(_sourceList[static_cast<size_t>(srcIndex)]);
178+
179+
AssemblyItem result(0);
180+
181+
if (c_instructions.count(name))
182+
{
183+
AssemblyItem item{c_instructions.at(name), location};
184+
if (!jumpType.empty())
185+
{
186+
if (item.instruction() == Instruction::JUMP || item.instruction() == Instruction::JUMPI)
187+
{
188+
std::optional<AssemblyItem::JumpType> parsedJumpType = AssemblyItem::parseJumpType(jumpType);
189+
if (!parsedJumpType.has_value())
190+
solThrow(AssemblyImportException, "Invalid jump type.");
191+
item.setJumpType(parsedJumpType.value());
192+
}
193+
else
194+
solThrow(
195+
AssemblyImportException,
196+
"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')"
197+
);
198+
}
199+
requireValueUndefinedForInstruction(name, value);
200+
result = item;
201+
}
202+
else
203+
{
204+
solRequire(
205+
jumpType.empty(),
206+
AssemblyImportException,
207+
"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')"
208+
);
209+
if (name == "PUSH")
210+
{
211+
requireValueDefinedForInstruction(name, value);
212+
result = {AssemblyItemType::Push, u256("0x" + value)};
213+
}
214+
else if (name == "PUSH [ErrorTag]")
215+
{
216+
requireValueUndefinedForInstruction(name, value);
217+
result = {AssemblyItemType::PushTag, 0};
218+
}
219+
else if (name == "PUSH [tag]")
220+
{
221+
requireValueDefinedForInstruction(name, value);
222+
result = {AssemblyItemType::PushTag, updateUsedTags(u256(value))};
223+
}
224+
else if (name == "PUSH [$]")
225+
{
226+
requireValueDefinedForInstruction(name, value);
227+
result = {AssemblyItemType::PushSub, u256("0x" + value)};
228+
}
229+
else if (name == "PUSH #[$]")
230+
{
231+
requireValueDefinedForInstruction(name, value);
232+
result = {AssemblyItemType::PushSubSize, u256("0x" + value)};
233+
}
234+
else if (name == "PUSHSIZE")
235+
{
236+
requireValueUndefinedForInstruction(name, value);
237+
result = {AssemblyItemType::PushProgramSize, 0};
238+
}
239+
else if (name == "PUSHLIB")
240+
{
241+
requireValueDefinedForInstruction(name, value);
242+
result = {AssemblyItemType::PushLibraryAddress, storeLibraryHash(value)};
243+
}
244+
else if (name == "PUSHDEPLOYADDRESS")
245+
{
246+
requireValueUndefinedForInstruction(name, value);
247+
result = {AssemblyItemType::PushDeployTimeAddress, 0};
248+
}
249+
else if (name == "PUSHIMMUTABLE")
250+
{
251+
requireValueDefinedForInstruction(name, value);
252+
result = {AssemblyItemType::PushImmutable, storeImmutableHash(value)};
253+
}
254+
else if (name == "ASSIGNIMMUTABLE")
255+
{
256+
requireValueDefinedForInstruction(name, value);
257+
result = {AssemblyItemType::AssignImmutable, storeImmutableHash(value)};
258+
}
259+
else if (name == "tag")
260+
{
261+
requireValueDefinedForInstruction(name, value);
262+
result = {AssemblyItemType::Tag, updateUsedTags(u256(value))};
263+
}
264+
else if (name == "PUSH data")
265+
{
266+
requireValueDefinedForInstruction(name, value);
267+
result = {AssemblyItemType::PushData, u256("0x" + value)};
268+
}
269+
else if (name == "VERBATIM")
270+
{
271+
requireValueDefinedForInstruction(name, value);
272+
AssemblyItem item(fromHex(value), 0, 0);
273+
result = item;
274+
}
275+
else
276+
solThrow(InvalidOpcode, "Invalid opcode: " + name);
277+
}
278+
result.setLocation(location);
279+
result.m_modifierDepth = modifierDepth;
280+
return result;
281+
}
282+
76283
namespace
77284
{
78285

@@ -297,6 +504,154 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc
297504
return root;
298505
}
299506

507+
std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> Assembly::fromJSON(
508+
Json::Value const& _json,
509+
std::vector<std::string> const& _sourceList,
510+
size_t _level
511+
)
512+
{
513+
solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object.");
514+
static std::set<std::string> const validMembers{".code", ".data", ".auxdata", "sourceList"};
515+
for (std::string const& attribute: _json.getMemberNames())
516+
solRequire(validMembers.count(attribute), AssemblyImportException, "Unknown attribute '" + attribute + "'.");
517+
518+
if (_level == 0)
519+
{
520+
if (_json.isMember("sourceList"))
521+
{
522+
solRequire(_json["sourceList"].isArray(), AssemblyImportException, "Optional member 'sourceList' is not an array.");
523+
for (Json::Value const& sourceName: _json["sourceList"])
524+
solRequire(sourceName.isString(), AssemblyImportException, "The 'sourceList' array contains an item that is not a string.");
525+
}
526+
}
527+
else
528+
solRequire(
529+
!_json.isMember("sourceList"),
530+
AssemblyImportException,
531+
"Member 'sourceList' may only be present in the root JSON object."
532+
);
533+
534+
auto result = std::make_shared<Assembly>(EVMVersion{}, _level == 0 /* _creation */, "" /* _name */);
535+
std::vector<std::string> parsedSourceList;
536+
if (_json.isMember("sourceList"))
537+
{
538+
solAssert(_level == 0);
539+
solAssert(_sourceList.empty());
540+
for (Json::Value const& sourceName: _json["sourceList"])
541+
{
542+
solRequire(
543+
std::find(parsedSourceList.begin(), parsedSourceList.end(), sourceName.asString()) == parsedSourceList.end(),
544+
AssemblyImportException,
545+
"Items in 'sourceList' array are not unique."
546+
);
547+
parsedSourceList.emplace_back(sourceName.asString());
548+
}
549+
}
550+
551+
solRequire(_json.isMember(".code"), AssemblyImportException, "Member '.code' is missing.");
552+
solRequire(_json[".code"].isArray(), AssemblyImportException, "Member '.code' is not an array.");
553+
for (Json::Value const& codeItem: _json[".code"])
554+
solRequire(codeItem.isObject(), AssemblyImportException, "The '.code' array contains an item that is not an object.");
555+
556+
result->importAssemblyItemsFromJSON(_json[".code"], _level == 0 ? parsedSourceList : _sourceList);
557+
558+
if (_json[".auxdata"])
559+
{
560+
solRequire(_json[".auxdata"].isString(), AssemblyImportException, "Optional member '.auxdata' is not a string.");
561+
result->m_auxiliaryData = fromHex(_json[".auxdata"].asString());
562+
solRequire(!result->m_auxiliaryData.empty(), AssemblyImportException, "Optional member '.auxdata' is not a valid hexadecimal string.");
563+
}
564+
565+
if (_json.isMember(".data"))
566+
{
567+
solRequire(_json[".data"].isObject(), AssemblyImportException, "Optional member '.data' is not an object.");
568+
Json::Value const& data = _json[".data"];
569+
std::map<size_t, std::shared_ptr<Assembly>> subAssemblies;
570+
for (Json::ValueConstIterator dataIter = data.begin(); dataIter != data.end(); dataIter++)
571+
{
572+
solAssert(dataIter.key().isString());
573+
std::string dataItemID = dataIter.key().asString();
574+
Json::Value const& dataItem = data[dataItemID];
575+
if (dataItem.isString())
576+
{
577+
solRequire(
578+
dataItem.asString().empty() || !fromHex(dataItem.asString()).empty(),
579+
AssemblyImportException,
580+
"The value for key '" + dataItemID + "' inside '.data' is not a valid hexadecimal string."
581+
);
582+
result->m_data[h256(fromHex(dataItemID))] = fromHex(dataItem.asString());
583+
}
584+
else if (dataItem.isObject())
585+
{
586+
size_t index{};
587+
try
588+
{
589+
// Using signed variant because stoul() still accepts negative numbers and
590+
// just lets them wrap around.
591+
int parsedDataItemID = std::stoi(dataItemID, nullptr, 16);
592+
solRequire(parsedDataItemID >= 0, AssemblyImportException, "The key '" + dataItemID + "' inside '.data' is out of the supported integer range.");
593+
index = static_cast<size_t>(parsedDataItemID);
594+
}
595+
catch (std::invalid_argument const&)
596+
{
597+
solThrow(AssemblyImportException, "The key '" + dataItemID + "' inside '.data' is not an integer.");
598+
}
599+
catch (std::out_of_range const&)
600+
{
601+
solThrow(AssemblyImportException, "The key '" + dataItemID + "' inside '.data' is out of the supported integer range.");
602+
}
603+
604+
auto [subAssembly, emptySourceList] = Assembly::fromJSON(dataItem, _level == 0 ? parsedSourceList : _sourceList, _level + 1);
605+
solAssert(subAssembly);
606+
solAssert(emptySourceList.empty());
607+
solAssert(subAssemblies.count(index) == 0);
608+
subAssemblies[index] = subAssembly;
609+
}
610+
else
611+
solThrow(AssemblyImportException, "The value of key '" + dataItemID + "' inside '.data' is neither a hex string nor an object.");
612+
}
613+
614+
if (!subAssemblies.empty())
615+
solRequire(
616+
ranges::max(subAssemblies | ranges::views::keys) == subAssemblies.size() - 1,
617+
AssemblyImportException,
618+
fmt::format(
619+
"Invalid subassembly indices in '.data'. Not all numbers between 0 and {} are present.",
620+
subAssemblies.size() - 1
621+
)
622+
);
623+
624+
result->m_subs = subAssemblies | ranges::views::values | ranges::to<std::vector>;
625+
}
626+
627+
if (_level == 0)
628+
result->encodeAllPossibleSubPathsInAssemblyTree();
629+
630+
return std::make_pair(result, _level == 0 ? parsedSourceList : std::vector<std::string>{});
631+
}
632+
633+
void Assembly::encodeAllPossibleSubPathsInAssemblyTree(std::vector<size_t> _pathFromRoot, std::vector<Assembly*> _assembliesOnPath)
634+
{
635+
_assembliesOnPath.push_back(this);
636+
for (_pathFromRoot.push_back(0); _pathFromRoot.back() < m_subs.size(); ++_pathFromRoot.back())
637+
{
638+
for (size_t distanceFromRoot = 0; distanceFromRoot < _assembliesOnPath.size(); ++distanceFromRoot)
639+
_assembliesOnPath[distanceFromRoot]->encodeSubPath(
640+
_pathFromRoot | ranges::views::drop_exactly(distanceFromRoot) | ranges::to<std::vector>
641+
);
642+
643+
m_subs[_pathFromRoot.back()]->encodeAllPossibleSubPathsInAssemblyTree(_pathFromRoot, _assembliesOnPath);
644+
}
645+
}
646+
647+
std::shared_ptr<std::string const> Assembly::sharedSourceName(std::string const& _name) const
648+
{
649+
if (s_sharedSourceNames.find(_name) == s_sharedSourceNames.end())
650+
s_sharedSourceNames[_name] = std::make_shared<std::string>(_name);
651+
652+
return s_sharedSourceNames[_name];
653+
}
654+
300655
AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID)
301656
{
302657
assertThrow(!_name.empty(), AssemblyException, "Empty named tag.");

0 commit comments

Comments
 (0)