mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	[libevmasm] Add support to import evm assembly json.
This commit is contained in:
		
							parent
							
								
									69c034b1c0
								
							
						
					
					
						commit
						108490e630
					
				| @ -34,13 +34,15 @@ | ||||
| #include <liblangutil/CharStream.h> | ||||
| #include <liblangutil/Exceptions.h> | ||||
| 
 | ||||
| #include <json/json.h> | ||||
| #include <libsolutil/JSON.h> | ||||
| #include <libsolutil/StringUtils.h> | ||||
| 
 | ||||
| #include <range/v3/algorithm/any_of.hpp> | ||||
| #include <range/v3/view/enumerate.hpp> | ||||
| 
 | ||||
| #include <fstream> | ||||
| #include <limits> | ||||
| #include <iterator> | ||||
| 
 | ||||
| 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<std::string> const& _sourceList) | ||||
| { | ||||
| 	solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object."); | ||||
| 	static set<string> const validMembers{"name", "begin", "end", "source", "value", "modifierDepth", "jumpType"}; | ||||
| 	for (auto const& member: _json.getMemberNames()) | ||||
| 		solRequire( | ||||
| 			validMembers.count(member), | ||||
| 			AssemblyImportException, | ||||
| 			"Unknown member '" + member + "'. Valid members are " + | ||||
| 			solidity::util::joinHumanReadable(validMembers, ", ") + "." | ||||
| 		); | ||||
| 	solRequire(isOfType<string>(_json["name"]), AssemblyImportException, "Member 'name' missing or not of type string."); | ||||
| 	solRequire(isOfTypeIfExists<int>(_json, "begin"), AssemblyImportException, "Optional member 'begin' not of type int."); | ||||
| 	solRequire(isOfTypeIfExists<int>(_json, "end"), AssemblyImportException, "Optional member 'end' not of type int."); | ||||
| 	solRequire(isOfTypeIfExists<int>(_json, "source"), AssemblyImportException, "Optional member 'source' not of type int."); | ||||
| 	solRequire(isOfTypeIfExists<string>(_json, "value"), AssemblyImportException, "Optional member 'value' not of type string."); | ||||
| 	solRequire( | ||||
| 		isOfTypeIfExists<int>(_json, "modifierDepth"), | ||||
| 		AssemblyImportException, | ||||
| 		"Optional member 'modifierDepth' not of type int." | ||||
| 	); | ||||
| 	solRequire( | ||||
| 		isOfTypeIfExists<string>(_json, "jumpType"), | ||||
| 		AssemblyImportException, | ||||
| 		"Optional member 'jumpType' not of type string." | ||||
| 	); | ||||
| 
 | ||||
| 	string name = get<string>(_json["name"]); | ||||
| 	solRequire(!name.empty(), AssemblyImportException, "Member 'name' was empty."); | ||||
| 
 | ||||
| 	SourceLocation location; | ||||
| 	location.start = get<int>(_json["begin"]); | ||||
| 	location.end = get<int>(_json["end"]); | ||||
| 	int srcIndex = getOrDefault<int>(_json["source"], -1); | ||||
| 	size_t modifierDepth = static_cast<size_t>(getOrDefault<int>(_json["modifierDepth"], 0)); | ||||
| 	string value = getOrDefault<string>(_json["value"], ""); | ||||
| 	string jumpType = getOrDefault<string>(_json["jumpType"], ""); | ||||
| 
 | ||||
| 	auto updateUsedTags = [&](u256 const& data) | ||||
| 	{ | ||||
| 		m_usedTags = max(m_usedTags, static_cast<unsigned>(data) + 1); | ||||
| 		return data; | ||||
| 	}; | ||||
| 
 | ||||
| 	auto storeImmutableHash = [&](string const& _immutableName) -> h256 | ||||
| 	{ | ||||
| 		h256 hash(util::keccak256(_immutableName)); | ||||
| 		solAssert(m_immutables.count(hash) == 0 || m_immutables[hash] == _immutableName); | ||||
| 		m_immutables[hash] = _immutableName; | ||||
| 		return hash; | ||||
| 	}; | ||||
| 
 | ||||
| 	auto storeLibraryHash = [&](string const& _libraryName) -> h256 | ||||
| 	{ | ||||
| 		h256 hash(util::keccak256(_libraryName)); | ||||
| 		solAssert(m_libraries.count(hash) == 0 || m_libraries[hash] == _libraryName); | ||||
| 		m_libraries[hash] = _libraryName; | ||||
| 		return hash; | ||||
| 	}; | ||||
| 
 | ||||
| 	auto requireValueDefinedForInstruction = [&](string const& _name, string const& _value) | ||||
| 	{ | ||||
| 		solRequire( | ||||
| 			!_value.empty(), | ||||
| 			AssemblyImportException, | ||||
| 			"Member 'value' was not defined for instruction '" + _name + "', but the instruction needs a value." | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| 	auto requireValueUndefinedForInstruction = [&](string const& _name, string const& _value) | ||||
| 	{ | ||||
| 		solRequire( | ||||
| 			_value.empty(), | ||||
| 			AssemblyImportException, | ||||
| 			"Member 'value' defined for instruction '" + _name + "', but the instruction does not need a value." | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| 	solRequire(srcIndex >= -1 && srcIndex < static_cast<int>(_sourceList.size()), AssemblyImportException, "srcIndex out of bound."); | ||||
| 	if (srcIndex != -1) | ||||
| 		location.sourceName = std::make_shared<std::string>(_sourceList[static_cast<size_t>(srcIndex)]); | ||||
| 
 | ||||
| 	AssemblyItem result(0); | ||||
| 
 | ||||
| 	if (c_instructions.count(name)) | ||||
| 	{ | ||||
| 		AssemblyItem item{c_instructions.at(name), location}; | ||||
| 		if (!jumpType.empty()) | ||||
| 		{ | ||||
| 			if (item.instruction() == Instruction::JUMP || item.instruction() == Instruction::JUMPI) | ||||
| 				item.setJumpType(jumpType); | ||||
| 			else | ||||
| 				solThrow( | ||||
| 					AssemblyImportException, | ||||
| 					"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')" | ||||
| 				); | ||||
| 		} | ||||
| 		InstructionInfo info = instructionInfo(item.instruction(), m_evmVersion); | ||||
| 		if (info.args == 0) | ||||
| 			requireValueUndefinedForInstruction(name, value); | ||||
| 		result = item; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		solRequire( | ||||
| 			jumpType.empty(), | ||||
| 			AssemblyImportException, | ||||
| 			"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')" | ||||
| 		); | ||||
| 		if (name == "PUSH") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::Push, u256("0x" + value)}; | ||||
| 		} | ||||
| 		else if (name == "PUSH [ErrorTag]") | ||||
| 		{ | ||||
| 			requireValueUndefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushTag, 0}; | ||||
| 		} | ||||
| 		else if (name == "PUSH [tag]") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushTag, updateUsedTags(u256(value))}; | ||||
| 		} | ||||
| 		else if (name == "PUSH [$]") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushSub, u256("0x" + value)}; | ||||
| 		} | ||||
| 		else if (name == "PUSH #[$]") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushSubSize, u256("0x" + value)}; | ||||
| 		} | ||||
| 		else if (name == "PUSHSIZE") | ||||
| 		{ | ||||
| 			requireValueUndefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushProgramSize, 0}; | ||||
| 		} | ||||
| 		else if (name == "PUSHLIB") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushLibraryAddress, storeLibraryHash(value)}; | ||||
| 		} | ||||
| 		else if (name == "PUSHDEPLOYADDRESS") | ||||
| 		{ | ||||
| 			requireValueUndefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushDeployTimeAddress, 0}; | ||||
| 		} | ||||
| 		else if (name == "PUSHIMMUTABLE") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushImmutable, storeImmutableHash(value)}; | ||||
| 		} | ||||
| 		else if (name == "ASSIGNIMMUTABLE") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::AssignImmutable, storeImmutableHash(value)}; | ||||
| 		} | ||||
| 		else if (name == "tag") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::Tag, updateUsedTags(u256(value))}; | ||||
| 		} | ||||
| 		else if (name == "PUSH data") | ||||
| 		{ | ||||
| 			requireValueDefinedForInstruction(name, value); | ||||
| 			result = {AssemblyItemType::PushData, u256("0x" + value)}; | ||||
| 		} | ||||
| 		else if (name == "VERBATIM") | ||||
| 		{ | ||||
| 			requireValueUndefinedForInstruction(name, value); | ||||
| 			AssemblyItem item(fromHex(value), 0, 0); | ||||
| 			result = item; | ||||
| 		} | ||||
| 		else | ||||
| 			solThrow(InvalidOpcode, "Invalid opcode: " + name); | ||||
| 	} | ||||
| 	result.setLocation(location); | ||||
| 	result.m_modifierDepth = modifierDepth; | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| 
 | ||||
| @ -221,7 +424,7 @@ std::string Assembly::assemblyString( | ||||
| 	return tmp.str(); | ||||
| } | ||||
| 
 | ||||
| Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList) const | ||||
| Json::Value Assembly::assemblyJSON(bool _includeSourceList) const | ||||
| { | ||||
| 	Json::Value root; | ||||
| 	root[".code"] = Json::arrayValue; | ||||
| @ -231,9 +434,9 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc | ||||
| 		int sourceIndex = -1; | ||||
| 		if (item.location().sourceName) | ||||
| 		{ | ||||
| 			auto iter = _sourceIndices.find(*item.location().sourceName); | ||||
| 			if (iter != _sourceIndices.end()) | ||||
| 				sourceIndex = static_cast<int>(iter->second); | ||||
| 			for (size_t index = 0; index < m_sourceList.size(); ++index) | ||||
| 				if (m_sourceList[index] == *item.location().sourceName) | ||||
| 					sourceIndex = static_cast<int>(index); | ||||
| 		} | ||||
| 
 | ||||
| 		auto [name, data] = item.nameAndData(m_evmVersion); | ||||
| @ -271,8 +474,8 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc | ||||
| 	{ | ||||
| 		root["sourceList"] = Json::arrayValue; | ||||
| 		Json::Value& jsonSourceList = root["sourceList"]; | ||||
| 		for (auto const& [name, index]: _sourceIndices) | ||||
| 			jsonSourceList[index] = name; | ||||
| 		for (int index = 0; index < static_cast<int>(m_sourceList.size()); ++index) | ||||
| 			jsonSourceList[index] = m_sourceList[static_cast<size_t>(index)]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!m_data.empty() || !m_subs.empty()) | ||||
| @ -285,9 +488,10 @@ Json::Value Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourc | ||||
| 
 | ||||
| 		for (size_t i = 0; i < m_subs.size(); ++i) | ||||
| 		{ | ||||
| 			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<std::string, unsigned> const& _sourc | ||||
| 	return root; | ||||
| } | ||||
| 
 | ||||
| AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID) | ||||
| shared_ptr<Assembly> Assembly::fromJSON(Json::Value const& _json, vector<string> const& _sourceList, int _level) | ||||
| { | ||||
| 	solRequire(_json.isObject(), AssemblyImportException, "Supplied JSON is not an object."); | ||||
| 	static set<string> const validMembers{".code", ".data", ".auxdata", "sourceList"}; | ||||
| 	for (auto const& attribute: _json.getMemberNames()) | ||||
| 		solRequire(validMembers.count(attribute), AssemblyImportException, "Unknown attribute '" + attribute + "'."); | ||||
| 	solRequire(_json.isMember(".code"), AssemblyImportException, "Member '.code' does not exist."); | ||||
| 	solRequire(_json[".code"].isArray(), AssemblyImportException, "Member '.code' is not an array."); | ||||
| 	for (auto const& codeItem: _json[".code"]) | ||||
| 		solRequire(codeItem.isObject(), AssemblyImportException, "Item of '.code' array is not an object."); | ||||
| 
 | ||||
| 	if (_level == 0) | ||||
| 	{ | ||||
| 		if (_json.isMember("sourceList")) | ||||
| 		{ | ||||
| 			solRequire(_json["sourceList"].isArray(), AssemblyImportException, "Optional member 'sourceList' is not an array."); | ||||
| 			for (auto const& sourceListItem: _json["sourceList"]) | ||||
| 				solRequire(sourceListItem.isString(), AssemblyImportException, "Item of 'sourceList' array is not of type string."); | ||||
| 		} | ||||
| 	} else | ||||
| 		solRequire( | ||||
| 			!_json.isMember("sourceList"), | ||||
| 			AssemblyImportException, | ||||
| 			"Member 'sourceList' is only allowed in root JSON object." | ||||
| 		); | ||||
| 
 | ||||
| 	shared_ptr<Assembly> result = make_shared<Assembly>(langutil::EVMVersion(), _level == 0, ""); | ||||
| 	if (_json.isMember("sourceList")) | ||||
| 	{ | ||||
| 		solAssert(_level == 0); | ||||
| 		for (auto const& it: _json["sourceList"]) | ||||
| 			result->m_sourceList.emplace_back(it.asString()); | ||||
| 	} else | ||||
| 		result->m_sourceList = _sourceList; | ||||
| 
 | ||||
| 	result->importAssemblyItemsFromJSON(_json[".code"]); | ||||
| 	if (_json[".auxdata"]) | ||||
| 	{ | ||||
| 		solRequire(_json[".auxdata"].isString(), AssemblyImportException, "Optional member '.auxdata' is not of type string."); | ||||
| 		bytes auxdata{fromHex(_json[".auxdata"].asString())}; | ||||
| 		solRequire(!auxdata.empty(), AssemblyImportException, "Optional member '.auxdata' is not a valid hexadecimal string."); | ||||
| 		result->m_auxiliaryData = auxdata; | ||||
| 	} | ||||
| 
 | ||||
| 	if (_json.isMember(".data")) | ||||
| 	{ | ||||
| 		solRequire(_json[".data"].isObject(), AssemblyImportException, "Optional member '.data' is not an object."); | ||||
| 		Json::Value const& data = _json[".data"]; | ||||
| 		for (Json::ValueConstIterator dataIter = data.begin(); dataIter != data.end(); dataIter++) | ||||
| 		{ | ||||
| 			solRequire(dataIter.key().isString(), AssemblyImportException, "Key inside '.data' is not of type string."); | ||||
| 			string dataItemID = dataIter.key().asString(); | ||||
| 			Json::Value const& code = data[dataItemID]; | ||||
| 			if (code.isString()) | ||||
| 			{ | ||||
| 				bytes data_value{fromHex(code.asString())}; | ||||
| 				solRequire( | ||||
| 					!data_value.empty(), | ||||
| 					AssemblyImportException, | ||||
| 					"Member '.data' contains a value for '" + dataItemID + "' that is not a valid hexadecimal string."); | ||||
| 				result->m_data[h256(fromHex(dataItemID))] = fromHex(code.asString()); | ||||
| 			} | ||||
| 			else if (code.isObject()) | ||||
| 			{ | ||||
| 				shared_ptr<Assembly> subassembly(Assembly::fromJSON(code, result->m_sourceList, _level + 1)); | ||||
| 				solAssert(subassembly); | ||||
| 				result->m_subs.emplace_back(make_shared<Assembly>(*subassembly)); | ||||
| 				// TODO: this shouldn't be enough for the general case.
 | ||||
| 				result->encodeSubPath({0, 0}); | ||||
| 			} | ||||
| 			else | ||||
| 				solThrow(AssemblyImportException, "Key inside '.data' '" + dataItemID + "' can only be a valid hex-string or an object."); | ||||
| 		} | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| AssemblyItem Assembly::namedTag(string const& _name, size_t _params, size_t _returns, optional<uint64_t> _sourceID) | ||||
| { | ||||
| 	assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); | ||||
| 	if (m_namedTags.count(_name)) | ||||
|  | ||||
| @ -151,10 +151,30 @@ public: | ||||
| 
 | ||||
| 	/// Create a JSON representation of the assembly.
 | ||||
| 	Json::Value assemblyJSON( | ||||
| 		std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>(), | ||||
| 		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<Assembly> fromJSON( | ||||
| 		Json::Value const& _json, | ||||
| 		std::vector<std::string> const& _sourceList = {}, | ||||
| 		int _level = 0 | ||||
| 	); | ||||
| 
 | ||||
| 	void setSourceList(std::vector<std::string> const& _sourceList) | ||||
| 	{ | ||||
| 		m_sourceList = _sourceList; | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<std::string> const& sourceList() | ||||
| 	{ | ||||
| 		return m_sourceList; | ||||
| 	} | ||||
| 
 | ||||
| 	/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
 | ||||
| 	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<std::string> 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<std::string> m_sourceList; | ||||
| 	langutil::SourceLocation m_currentSourceLocation; | ||||
| 
 | ||||
| public: | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,8 @@ set(sources | ||||
| 	Assembly.h | ||||
| 	AssemblyItem.cpp | ||||
| 	AssemblyItem.h | ||||
| 	EVMAssemblyStack.cpp | ||||
| 	EVMAssemblyStack.h | ||||
| 	BlockDeduplicator.cpp | ||||
| 	BlockDeduplicator.h | ||||
| 	CommonSubexpressionEliminator.cpp | ||||
|  | ||||
							
								
								
									
										58
									
								
								libevmasm/EVMAssemblyStack.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								libevmasm/EVMAssemblyStack.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #include <libevmasm/EVMAssemblyStack.h> | ||||
| 
 | ||||
| #include <libsolutil/JSON.h> | ||||
| #include <liblangutil/Exceptions.h> | ||||
| #include <libsolidity/codegen/CompilerContext.h> | ||||
| 
 | ||||
| #include <utility> | ||||
| 
 | ||||
| using namespace solidity::util; | ||||
| using namespace solidity::langutil; | ||||
| using namespace solidity::frontend; | ||||
| using namespace std; | ||||
| 
 | ||||
| namespace solidity::evmasm | ||||
| { | ||||
| 
 | ||||
| bool EVMAssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) | ||||
| { | ||||
| 	m_name = _sourceName; | ||||
| 	if (jsonParseStrict(_source, m_json)) | ||||
| 	{ | ||||
| 		m_evmAssembly = evmasm::Assembly::fromJSON(m_json); | ||||
| 		return m_evmAssembly != nullptr; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void EVMAssemblyStack::assemble() | ||||
| { | ||||
| 	solAssert(m_evmAssembly->isCreation()); | ||||
| 	m_object = m_evmAssembly->assemble(); | ||||
| 	if (m_evmAssembly->numSubs() > 0) | ||||
| 	{ | ||||
| 		m_evmRuntimeAssembly = make_shared<evmasm::Assembly>(m_evmAssembly->sub(0)); | ||||
| 		solAssert(m_evmRuntimeAssembly && !m_evmRuntimeAssembly->isCreation()); | ||||
| 		m_runtimeObject = m_evmRuntimeAssembly->assemble(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| } // namespace solidity::evmasm
 | ||||
							
								
								
									
										68
									
								
								libevmasm/EVMAssemblyStack.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								libevmasm/EVMAssemblyStack.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <string> | ||||
| 
 | ||||
| #include <libsolutil/JSON.h> | ||||
| 
 | ||||
| #include <libevmasm/Assembly.h> | ||||
| #include <libevmasm/LinkerObject.h> | ||||
| 
 | ||||
| namespace solidity::evmasm | ||||
| { | ||||
| 
 | ||||
| class EVMAssemblyStack | ||||
| { | ||||
| public: | ||||
| 	explicit EVMAssemblyStack(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) {} | ||||
| 
 | ||||
| 	/// Runs parsing and analysis, returns false if input cannot be assembled.
 | ||||
| 	/// Multiple calls overwrite the previous state.
 | ||||
| 	bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source); | ||||
| 
 | ||||
| 	void assemble(); | ||||
| 
 | ||||
| 	std::string const& name() const { return m_name; } | ||||
| 
 | ||||
| 	evmasm::LinkerObject const& object() const { return m_object; } | ||||
| 
 | ||||
| 	std::shared_ptr<evmasm::Assembly> const& evmAssembly() const { return m_evmAssembly; } | ||||
| 
 | ||||
| 	evmasm::LinkerObject const& runtimeObject() const { return m_runtimeObject; } | ||||
| 
 | ||||
| 	std::shared_ptr<evmasm::Assembly> const& evmRuntimeAssembly() const { return m_evmRuntimeAssembly; } | ||||
| 
 | ||||
| 	Json::Value json() const | ||||
| 	{ | ||||
| 		return m_json; | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	langutil::EVMVersion m_evmVersion; | ||||
| 	std::string m_name; | ||||
| 	Json::Value m_json; | ||||
| 	std::shared_ptr<evmasm::Assembly> m_evmAssembly; | ||||
| 	std::shared_ptr<evmasm::Assembly> m_evmRuntimeAssembly; | ||||
| 	evmasm::LinkerObject m_object; ///< Deployment object (includes the runtime sub-object).
 | ||||
| 	evmasm::LinkerObject m_runtimeObject; ///< Runtime object.
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace solidity::evmasm
 | ||||
| @ -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 {}; | ||||
|  | ||||
| @ -72,6 +72,7 @@ | ||||
| #include <liblangutil/SemVerHandler.h> | ||||
| #include <liblangutil/SourceReferenceFormatter.h> | ||||
| 
 | ||||
| #include <libevmasm/EVMAssemblyStack.h> | ||||
| #include <libevmasm/Exceptions.h> | ||||
| 
 | ||||
| #include <libsolutil/SwarmHash.h> | ||||
| @ -438,6 +439,27 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources) | ||||
| 	storeContractDefinitions(); | ||||
| } | ||||
| 
 | ||||
| void CompilerStack::importFromEVMAssemblyStack(std::string const& _sourceName, std::string const& _source) | ||||
| { | ||||
| 	solRequire(m_stackState == Empty, CompilerError, ""); | ||||
| 	m_evmAssemblyStack = std::make_unique<evmasm::EVMAssemblyStack>(m_evmVersion); | ||||
| 	Json::Value evmAsmJson; | ||||
| 	if (m_evmAssemblyStack->parseAndAnalyze(_sourceName, _source)) | ||||
| 	{ | ||||
| 		m_evmAssemblyStack->assemble(); | ||||
| 		string const name{m_evmAssemblyStack->name()}; | ||||
| 		Json::Value const& json = m_evmAssemblyStack->json(); | ||||
| 		m_sourceJsons[name] = json; | ||||
| 		Contract& contract = m_contracts[name]; | ||||
| 		contract.evmAssembly = m_evmAssemblyStack->evmAssembly(); | ||||
| 		contract.evmRuntimeAssembly= m_evmAssemblyStack->evmRuntimeAssembly(); | ||||
| 		contract.object = m_evmAssemblyStack->object(); | ||||
| 		contract.runtimeObject = m_evmAssemblyStack->runtimeObject(); | ||||
| 
 | ||||
| 		m_stackState = CompilationSuccessful; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool CompilerStack::analyze() | ||||
| { | ||||
| 	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<langutil::errinfo_sourceLocation>(_unimplementedError) | ||||
| 								boost::get_error_info<langutil::errinfo_sourceLocation>(_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<std::string> sources = sourceNames(); | ||||
| 		if (std::find(sources.begin(), sources.end(), CompilerContext::yulUtilityFileName()) == sources.end()) | ||||
| 			sources.emplace_back(CompilerContext::yulUtilityFileName()); | ||||
| 		currentContract.evmAssembly->setSourceList(sources); | ||||
| 		return currentContract.evmAssembly->assemblyJSON(); | ||||
| 	} | ||||
| 	else | ||||
| 		return Json::Value(); | ||||
| } | ||||
| @ -971,10 +1000,21 @@ map<string, unsigned> CompilerStack::sourceIndices() 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_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; | ||||
|  | ||||
| @ -42,6 +42,7 @@ | ||||
| #include <liblangutil/SourceLocation.h> | ||||
| 
 | ||||
| #include <libevmasm/LinkerObject.h> | ||||
| #include <libevmasm/EVMAssemblyStack.h> | ||||
| 
 | ||||
| #include <libsolutil/Common.h> | ||||
| #include <libsolutil/FixedHash.h> | ||||
| @ -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<std::string, Json::Value> 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<evmasm::EVMAssemblyStack> m_evmAssemblyStack; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -93,6 +93,7 @@ namespace | ||||
| set<frontend::InputMode> 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<CompilerStack>(m_universalCallback.callback()); | ||||
| 		m_compiler->importFromEVMAssemblyStack(iter->first, iter->second); | ||||
| 	} | ||||
| 	catch (evmasm::AssemblyImportException const& _exception) | ||||
| 	{ | ||||
| 		solThrow(CommandLineExecutionError, "Assembly Import Error: "s + _exception.what()); | ||||
| 	} | ||||
| 	solRequire(m_compiler != nullptr, CommandLineExecutionError, "Assembly Import Error: Could not create compiler object."); | ||||
| } | ||||
| 
 | ||||
| void CommandLineInterface::compile() | ||||
| { | ||||
| 	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<evmasm::Assembly> assembly{stack.assembleEVMWithDeployed().first}; | ||||
| 			if (assembly) | ||||
| 			{ | ||||
| 				std::function<map<string, unsigned>(yul::Object const&)> collectSourceIndices = | ||||
| 					[&](yul::Object const& _object) -> map<string, unsigned> { | ||||
| 					map<string, unsigned> sourceIndices; | ||||
| 					if (_object.debugData && _object.debugData->sourceNames.has_value()) | ||||
| 						for (auto const& iter: *_object.debugData->sourceNames) | ||||
| 							sourceIndices[*iter.second] = iter.first; | ||||
| 					for (auto const& sub: _object.subObjects) | ||||
| 					{ | ||||
| 						auto subObject = dynamic_cast<yul::Object const*>(sub.get()); | ||||
| 						if (subObject) | ||||
| 							for (auto const& [name, index]: collectSourceIndices(*subObject)) | ||||
| 								sourceIndices[name] = index; | ||||
| 					} | ||||
| 					return sourceIndices; | ||||
| 				}; | ||||
| 				if (stack.parserResult() && stack.parserResult()->debugData) | ||||
| 				{ | ||||
| 					map<string, unsigned> sourceIndices = collectSourceIndices(*stack.parserResult()); | ||||
| 					// if sourceIndices are empty here, there were no source locations annotated in the yul source.
 | ||||
| 					// in this case, we just add the filename of the yul file itself here.
 | ||||
| 					if (sourceIndices.empty()) | ||||
| 						sourceIndices[src.first] = 0; | ||||
| 					size_t max_index = 0; | ||||
| 					for (auto const& [name, index]: sourceIndices) | ||||
| 						if (max_index < index) | ||||
| 							max_index = index; | ||||
| 					vector<string> sourceList(max_index + 1, "<unknown>"); | ||||
| 					for (auto const& [name, index]: sourceIndices) | ||||
| 						sourceList[index] = name; | ||||
| 					assembly->setSourceList(sourceList); | ||||
| 					sout() << util::jsonPrint( | ||||
| 						removeNullMembers( | ||||
| 							assembly->assemblyJSON() | ||||
| 						), | ||||
| 						m_options.formatting.json | ||||
| 					) << endl; | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 			serr() << "Could not create Assembly JSON representation." << endl; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -1180,19 +1281,7 @@ void CommandLineInterface::outputCompilationResults() | ||||
| 			sout() << endl << "======= " << contract << " =======" << endl; | ||||
| 
 | ||||
| 		// 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); | ||||
|  | ||||
| @ -24,6 +24,7 @@ | ||||
| 
 | ||||
| #include <solc/CommandLineParser.h> | ||||
| 
 | ||||
| #include <libevmasm/EVMAssemblyStack.h> | ||||
| #include <libsolidity/interface/CompilerStack.h> | ||||
| #include <libsolidity/interface/DebugSettings.h> | ||||
| #include <libsolidity/interface/FileReader.h> | ||||
| @ -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); | ||||
|  | ||||
| @ -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<InputMode, string> 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<string> 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<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) | ||||
| 		{ | ||||
| 		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<string> const supportedByEvmAsmJsonImport{ | ||||
| 			g_strImportEvmAssemblerJson, | ||||
| 			CompilerOutputs::componentName(&CompilerOutputs::asm_), | ||||
| 			CompilerOutputs::componentName(&CompilerOutputs::binary), | ||||
| 			CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime), | ||||
| 			CompilerOutputs::componentName(&CompilerOutputs::asmJson), | ||||
| 			CompilerOutputs::componentName(&CompilerOutputs::opcodes), | ||||
| 			g_strCombinedJson, | ||||
| 			g_strInputFile, | ||||
| 			g_strJsonIndent, | ||||
| 			g_strPrettyJson, | ||||
| 			"srcmap", | ||||
| 			"srcmap-runtime", | ||||
| 		}; | ||||
| 
 | ||||
| 		for (auto const& option: m_args) | ||||
| 			if (!option.second.defaulted() && !supportedByEvmAsmJsonImport.count(option.first)) | ||||
| 				solThrow( | ||||
| 					CommandLineValidationError, | ||||
| 					"Option --" + option.first + " is not supported with --"+g_strImportEvmAssemblerJson+"." | ||||
| 				); | ||||
| 	} | ||||
| 
 | ||||
| 	if ( | ||||
| 		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<string> const& _optionNames) const | ||||
|  | ||||
| @ -57,6 +57,7 @@ enum class InputMode | ||||
| 	Linker, | ||||
| 	Assembler, | ||||
| 	LanguageServer, | ||||
| 	EVMAssemblerJSON | ||||
| }; | ||||
| 
 | ||||
| struct CompilerOutputs | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --pretty-json --json-indent 4 --combined-json bin,bin-runtime,opcodes,asm,srcmap,srcmap-runtime --asm --bin --bin-runtime --asm-json --import-asm-json - | ||||
							
								
								
									
										91
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/output
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/output
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| { | ||||
|     "contracts": | ||||
|     { | ||||
|         "<stdin>": | ||||
|         { | ||||
|             "asm": | ||||
|             { | ||||
|                 ".code": | ||||
|                 [ | ||||
|                     { | ||||
|                         "begin": 0, | ||||
|                         "end": 0, | ||||
|                         "name": "PUSH", | ||||
|                         "source": -1, | ||||
|                         "value": "0" | ||||
|                     } | ||||
|                 ], | ||||
|                 ".data": | ||||
|                 { | ||||
|                     "0": | ||||
|                     { | ||||
|                         ".code": | ||||
|                         [ | ||||
|                             { | ||||
|                                 "begin": 0, | ||||
|                                 "end": 0, | ||||
|                                 "name": "PUSH", | ||||
|                                 "source": -1, | ||||
|                                 "value": "1" | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|                 }, | ||||
|                 "sourceList": | ||||
|                 [ | ||||
|                     "contract.sol", | ||||
|                     "#utility.yul" | ||||
|                 ] | ||||
|             }, | ||||
|             "bin": "6000fe", | ||||
|             "bin-runtime": "6001", | ||||
|             "opcodes": "PUSH1 0x0 INVALID ", | ||||
|             "srcmap": "0:0::-:0", | ||||
|             "srcmap-runtime": "0:0::-:0" | ||||
|         } | ||||
|     }, | ||||
|     "sourceList": | ||||
|     [ | ||||
|         "contract.sol", | ||||
|         "#utility.yul" | ||||
|     ], | ||||
|     "version": "<VERSION REMOVED>" | ||||
| } | ||||
| Binary: | ||||
| 6000fe | ||||
| Binary of the runtime part: | ||||
| 6001 | ||||
| EVM assembly: | ||||
| { | ||||
|     ".code": | ||||
|     [ | ||||
|         { | ||||
|             "begin": 0, | ||||
|             "end": 0, | ||||
|             "name": "PUSH", | ||||
|             "source": -1, | ||||
|             "value": "0" | ||||
|         } | ||||
|     ], | ||||
|     ".data": | ||||
|     { | ||||
|         "0": | ||||
|         { | ||||
|             ".code": | ||||
|             [ | ||||
|                 { | ||||
|                     "begin": 0, | ||||
|                     "end": 0, | ||||
|                     "name": "PUSH", | ||||
|                     "source": -1, | ||||
|                     "value": "1" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|     "sourceList": | ||||
|     [ | ||||
|         "contract.sol", | ||||
|         "#utility.yul" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										22
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								test/cmdlineTests/asm_json_import_all_valid_flags/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "PUSH", | ||||
|       "value": "0" | ||||
|     } | ||||
|   ], | ||||
|   ".data": { | ||||
|     "0": { | ||||
|       ".code": [ | ||||
|         { | ||||
|           "name": "PUSH", | ||||
|           "value": "1" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "sourceList": [ | ||||
|     "contract.sol", | ||||
|     "#utility.yul" | ||||
|   ] | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
| @ -0,0 +1,6 @@ | ||||
| Opcodes: | ||||
| PREVRANDAO PREVRANDAO  | ||||
| EVM assembly: | ||||
|     /*   */ | ||||
|   prevrandao | ||||
|   prevrandao | ||||
| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { "name": "DIFFICULTY" }, | ||||
|     { "name": "PREVRANDAO" } | ||||
|   ] | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Member '.data' contains a value for '0' that is not a valid hexadecimal string. | ||||
| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										11
									
								
								test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/cmdlineTests/asm_json_import_invalid_data_no_hex/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "PUSH", | ||||
|       "value": "0" | ||||
|     } | ||||
|   ], | ||||
|   ".data": { | ||||
|     "0": "no-hex-string" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Key inside '.data' '0' can only be a valid hex-string or an object. | ||||
| @ -0,0 +1 @@ | ||||
| 1 | ||||
| @ -0,0 +1,16 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "PUSH", | ||||
|       "value": "0" | ||||
|     } | ||||
|   ], | ||||
|   ".data": { | ||||
|     "0": [ | ||||
|       0, | ||||
|       1, | ||||
|       2, | ||||
|       3 | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction 'PUSH') | ||||
| @ -0,0 +1 @@ | ||||
| 1 | ||||
| @ -0,0 +1,9 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "PUSH", | ||||
|       "value": "0", | ||||
|       "jumpType": "[in]" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/err
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/err
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Member 'value' defined for instruction 'DIFFICULTY', but the instruction does not need a value. | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/exit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_invalid_value/exit
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										8
									
								
								test/cmdlineTests/asm_json_import_invalid_value/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								test/cmdlineTests/asm_json_import_invalid_value/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "DIFFICULTY", | ||||
|       "value": "0" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --optimize --import-asm-json - --opcodes --asm | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/err
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/err
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Option --optimize is not supported with --import-asm-json. | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/exit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/exit
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										6
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/cmdlineTests/asm_json_import_no_optimiser/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { "name": "DIFFICULTY" }, | ||||
|     { "name": "PREVRANDAO" } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/err
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/err
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Member 'value' was not defined for instruction 'PUSH', but the instruction needs a value. | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/exit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_no_value/exit
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										5
									
								
								test/cmdlineTests/asm_json_import_no_value/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/cmdlineTests/asm_json_import_no_value/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { "name": "PUSH" } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/err
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/err
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Assembly Import Error: Unknown member '_name'. Valid members are begin, end, jumpType, modifierDepth, name, source, value. | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/exit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_other_fields/exit
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										5
									
								
								test/cmdlineTests/asm_json_import_other_fields/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/cmdlineTests/asm_json_import_other_fields/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { "_name": "DIFFICULTY" } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/args
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/args
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --import-asm-json - --opcodes --asm | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/err
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/err
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Assembly Import Error: JUMPDEST instruction found that was not followed by tag. | ||||
							
								
								
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/exit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/exit
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| 1 | ||||
							
								
								
									
										10
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								test/cmdlineTests/asm_json_import_untagged_jumpdest/stdin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| { | ||||
|   ".code": [ | ||||
|     { | ||||
|       "name": "tag", | ||||
|       "value": "0x00" | ||||
|     }, | ||||
|     { "name": "JUMPDEST" }, | ||||
|     { "name": "JUMPDEST" } | ||||
|   ] | ||||
| } | ||||
| @ -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. | ||||
|  | ||||
| @ -55,13 +55,9 @@ BOOST_AUTO_TEST_SUITE(Assembler) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| { | ||||
| 	map<string, unsigned> indices = { | ||||
| 		{ "root.asm", 0 }, | ||||
| 		{ "sub.asm", 1 }, | ||||
| 		{ "verbatim.asm", 2 } | ||||
| 	}; | ||||
| 	EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); | ||||
| 	Assembly _assembly{evmVersion, false, {}}; | ||||
| 	_assembly.setSourceList({"root.asm", "sub.asm", "verbatim.asm"}); | ||||
| 	auto root_asm = make_shared<string>("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<string, unsigned> 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<string>("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\"}," | ||||
|  | ||||
| @ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(version) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(multiple_input_modes) | ||||
| { | ||||
| 	array<string, 9> inputModeOptions = { | ||||
| 	array<string, 10> 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) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user