From 952101996cb6a198e493e253a07e7184e8359bbf Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Tue, 16 Jun 2020 17:13:46 +0200 Subject: [PATCH 1/2] Removing expectDeposit() from AsmAnalysis as unused function --- libyul/AsmAnalysis.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 8d8db59ab..552191c4d 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -102,7 +102,6 @@ private: /// Vists the expression and expects it to return a single boolean value. /// Reports an error otherwise. void expectBoolExpression(Expression const& _expr); - bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location); /// Verifies that a variable to be assigned to exists, can be assigned to /// and has the same type as the value. From 6f97e6153c57f879de93f39590293d794f10b391 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Wed, 17 Jun 2020 11:17:35 +0200 Subject: [PATCH 2/2] [yul] Adding support for accessing subobjects via `.` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kamil ƚliwak --- libevmasm/Assembly.cpp | 80 +++++++++++--- libevmasm/Assembly.h | 10 ++ libevmasm/AssemblyItem.cpp | 18 ++- libevmasm/AssemblyItem.h | 2 +- libyul/AsmAnalysis.cpp | 2 +- libyul/AssemblyStack.cpp | 2 +- libyul/CompilabilityChecker.cpp | 8 +- libyul/Object.cpp | 60 ++++++++-- libyul/Object.h | 21 +++- libyul/backends/evm/AbstractAssembly.h | 4 +- libyul/backends/evm/AsmCodeGen.cpp | 26 +++-- libyul/backends/evm/AsmCodeGen.h | 4 +- libyul/backends/evm/EVMAssembly.cpp | 4 +- libyul/backends/evm/EVMAssembly.h | 4 +- libyul/backends/evm/EVMDialect.cpp | 26 +++-- libyul/backends/evm/EVMObjectCompiler.cpp | 6 +- libyul/backends/evm/NoOutputAssembly.cpp | 4 +- libyul/backends/evm/NoOutputAssembly.h | 4 +- libyul/backends/wasm/EVMToEwasmTranslator.cpp | 2 +- .../input.json | 17 +++ .../output.json | 4 + test/libevmasm/Assembler.cpp | 19 ++++ test/libyul/Common.cpp | 2 +- .../libyul/objectCompiler/subObjectAccess.yul | 104 ++++++++++++++++++ 24 files changed, 357 insertions(+), 76 deletions(-) create mode 100644 test/cmdlineTests/standard_yul_object_invalid_sub/input.json create mode 100644 test/cmdlineTests/standard_yul_object_invalid_sub/output.json create mode 100644 test/libyul/objectCompiler/subObjectAccess.yul diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b3a0dbde0..b1de71e3b 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -92,8 +92,8 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& class Functionalizer { public: - Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes): - m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes) + Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes, Assembly const& _assembly): + m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly) {} void feed(AssemblyItem const& _item) @@ -104,6 +104,9 @@ public: m_location = _item.location(); printLocation(); } + + string expression = _item.toAssemblyText(m_assembly); + if (!( _item.canBeFunctional() && _item.returnValues() <= 1 && @@ -111,10 +114,9 @@ public: )) { flush(); - m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl; + m_out << m_prefix << (_item.type() == Tag ? "" : " ") << expression << endl; return; } - string expression = _item.toAssemblyText(); if (_item.arguments() > 0) { expression += "("; @@ -160,13 +162,14 @@ private: ostream& m_out; string const& m_prefix; StringMap const& m_sourceCodes; + Assembly const& m_assembly; }; } void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { - Functionalizer f(_out, _prefix, _sourceCodes); + Functionalizer f(_out, _prefix, _sourceCodes, *this); for (auto const& i: m_items) f.feed(i); @@ -639,7 +642,7 @@ LinkerObject const& Assembly::assemble() const case PushSubSize: { assertThrow(i.data() <= numeric_limits::max(), AssemblyException, ""); - auto s = m_subs.at(static_cast(i.data()))->assemble().bytecode.size(); + auto s = subAssemblyById(static_cast(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); uint8_t b = max(1, util::bytesRequired(s)); ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b); @@ -707,25 +710,20 @@ LinkerObject const& Assembly::assemble() const // Append an INVALID here to help tests find miscompilation. ret.bytecode.push_back(uint8_t(Instruction::INVALID)); - for (size_t i = 0; i < m_subs.size(); ++i) + for (auto const& [subIdPath, bytecodeOffset]: subRef) { - auto references = subRef.equal_range(i); - if (references.first == references.second) - continue; - for (auto ref = references.first; ref != references.second; ++ref) - { - bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } - ret.append(m_subs[i]->assemble()); + bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef); + toBigEndian(ret.bytecode.size(), r); + ret.append(subAssemblyById(subIdPath)->assemble()); } + for (auto const& i: tagRef) { size_t subId; size_t tagId; tie(subId, tagId) = i.second; assertThrow(subId == numeric_limits::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id"); - std::vector const& tagPositions = + vector const& tagPositions = subId == numeric_limits::max() ? m_tagPositionsInBytecode : m_subs[subId]->m_tagPositionsInBytecode; @@ -758,3 +756,51 @@ LinkerObject const& Assembly::assemble() const } return ret; } + +vector Assembly::decodeSubPath(size_t _subObjectId) const +{ + if (_subObjectId < m_subs.size()) + return {_subObjectId}; + + auto subIdPathIt = find_if( + m_subPaths.begin(), + m_subPaths.end(), + [_subObjectId](auto const& subId) { return subId.second == _subObjectId; } + ); + + assertThrow(subIdPathIt != m_subPaths.end(), AssemblyException, ""); + return subIdPathIt->first; +} + +size_t Assembly::encodeSubPath(vector const& _subPath) +{ + assertThrow(!_subPath.empty(), AssemblyException, ""); + if (_subPath.size() == 1) + { + assertThrow(_subPath[0] < m_subs.size(), AssemblyException, ""); + return _subPath[0]; + } + + if (m_subPaths.find(_subPath) == m_subPaths.end()) + { + size_t objectId = numeric_limits::max() - m_subPaths.size(); + assertThrow(objectId >= m_subs.size(), AssemblyException, ""); + m_subPaths[_subPath] = objectId; + } + + return m_subPaths[_subPath]; +} + +Assembly const* Assembly::subAssemblyById(size_t _subId) const +{ + vector subIds = decodeSubPath(_subId); + Assembly const* currentAssembly = this; + for (size_t currentSubId: subIds) + { + currentAssembly = currentAssembly->m_subs.at(currentSubId).get(); + assertThrow(currentAssembly, AssemblyException, ""); + } + + assertThrow(currentAssembly != this, AssemblyException, ""); + return currentAssembly; +} diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 312521b71..354304dcb 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -146,6 +146,9 @@ public: /// Mark this assembly as invalid. Calling ``assemble`` on it will throw. void markAsInvalid() { m_invalid = true; } + std::vector decodeSubPath(size_t _subObjectId) const; + size_t encodeSubPath(std::vector const& _subPath); + protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. Also takes an argument containing the tags of this assembly @@ -166,6 +169,9 @@ private: static std::string toStringInHex(u256 _value); bool m_invalid = false; + + Assembly const* subAssemblyById(size_t _subId) const; + protected: /// 0 is reserved for exception unsigned m_usedTags = 1; @@ -179,6 +185,10 @@ protected: std::map m_libraries; ///< Identifiers of libraries to be linked. std::map m_immutables; ///< Identifiers of immutables. + /// Map from a vector representing a path to a particular sub assembly to sub assembly id. + /// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value). + std::map, size_t> m_subPaths; + mutable LinkerObject m_assembledObject; mutable std::vector m_tagPositionsInBytecode; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 80d34f57c..e70e844d6 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -18,7 +18,10 @@ #include +#include + #include +#include #include #include @@ -170,7 +173,7 @@ string AssemblyItem::getJumpTypeAsString() const } } -string AssemblyItem::toAssemblyText() const +string AssemblyItem::toAssemblyText(Assembly const& _assembly) const { string text; switch (type()) @@ -208,11 +211,18 @@ string AssemblyItem::toAssemblyText() const text = string("data_") + util::toHex(data()); break; case PushSub: - text = string("dataOffset(sub_") + to_string(static_cast(data())) + ")"; - break; case PushSubSize: - text = string("dataSize(sub_") + to_string(static_cast(data())) + ")"; + { + vector subPathComponents; + for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast(data()))) + subPathComponents.emplace_back("sub_" + to_string(subPathComponentId)); + text = + (type() == PushSub ? "dataOffset"s : "dataSize"s) + + "(" + + solidity::util::joinHumanReadable(subPathComponents, ".") + + ")"; break; + } case PushProgramSize: text = string("bytecodeSize"); break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index dd65b109f..ac3a2c9d8 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -152,7 +152,7 @@ public: void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared(_value); } u256 const* pushedValue() const { return m_pushedValue.get(); } - std::string toAssemblyText() const; + std::string toAssemblyText(Assembly const& _assembly) const; size_t m_modifierDepth = 0; diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index e85ecd876..934cff0a4 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -72,7 +72,7 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, errors, _dialect, {}, - _object.dataNames() + _object.qualifiedDataNames() ).analyze(*_object.code); yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code."); return analysisInfo; diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index b8824740d..542de51d9 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -138,7 +138,7 @@ bool AssemblyStack::analyzeParsed(Object& _object) m_errorReporter, languageToDialect(m_language, m_evmVersion), {}, - _object.dataNames() + _object.qualifiedDataNames() ); bool success = analyzer.analyze(*_object.code); for (auto& subNode: _object.subObjects) diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 0d80a510f..2a99e3c79 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -39,7 +39,7 @@ map CompilabilityChecker::run( bool _optimizeStackAllocation ) { - if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) + if (auto const* evmDialect = dynamic_cast(&_dialect)) { NoOutputEVMDialect noOutputDialect(*evmDialect); @@ -48,8 +48,10 @@ map CompilabilityChecker::run( BuiltinContext builtinContext; builtinContext.currentObject = &_object; - for (auto name: _object.dataNames()) - builtinContext.subIDs[name] = 1; + if (!_object.name.empty()) + builtinContext.subIDs[_object.name] = 1; + for (auto const& subNode: _object.subObjects) + builtinContext.subIDs[subNode->name] = 1; NoOutputAssembly assembly; CodeTransform transform( assembly, diff --git a/libyul/Object.cpp b/libyul/Object.cpp index 1086e38cf..72206c4b6 100644 --- a/libyul/Object.cpp +++ b/libyul/Object.cpp @@ -24,9 +24,10 @@ #include #include -#include #include +#include +#include #include using namespace std; @@ -62,13 +63,54 @@ string Object::toString(Dialect const* _dialect) const return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}"; } -set Object::dataNames() const +set Object::qualifiedDataNames() const { - set names; - names.insert(name); - for (auto const& subObject: subIndexByName) - names.insert(subObject.first); - // The empty name is not valid - names.erase(YulString{}); - return names; + set qualifiedNames = name.empty() ? set{} : set{name}; + for (shared_ptr const& subObjectNode: subObjects) + { + yulAssert(qualifiedNames.count(subObjectNode->name) == 0, ""); + qualifiedNames.insert(subObjectNode->name); + if (auto const* subObject = dynamic_cast(subObjectNode.get())) + for (YulString const& subSubObj: subObject->qualifiedDataNames()) + if (subObject->name != subSubObj) + { + yulAssert(qualifiedNames.count(YulString{subObject->name.str() + "." + subSubObj.str()}) == 0, ""); + qualifiedNames.insert(YulString{subObject->name.str() + "." + subSubObj.str()}); + } + } + + yulAssert(qualifiedNames.count(YulString{}) == 0, ""); + qualifiedNames.erase(YulString{}); + return qualifiedNames; +} + +vector Object::pathToSubObject(YulString _qualifiedName) const +{ + yulAssert(_qualifiedName != name, ""); + yulAssert(subIndexByName.count(name) == 0, ""); + + if (boost::algorithm::starts_with(_qualifiedName.str(), name.str() + ".")) + _qualifiedName = YulString{_qualifiedName.str().substr(name.str().length() + 1)}; + yulAssert(!_qualifiedName.empty(), ""); + + vector subObjectPathComponents; + boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of(".")); + + vector path; + Object const* object = this; + for (string const& currentSubObjectName: subObjectPathComponents) + { + yulAssert(!currentSubObjectName.empty(), ""); + auto subIndexIt = object->subIndexByName.find(YulString{currentSubObjectName}); + yulAssert( + subIndexIt != object->subIndexByName.end(), + "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code." + ); + object = dynamic_cast(object->subObjects[subIndexIt->second].get()); + yulAssert(object, "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code."); + yulAssert(object->subId != numeric_limits::max(), ""); + path.push_back({object->subId}); + } + + return path; } diff --git a/libyul/Object.h b/libyul/Object.h index 3602e7177..deb52eb25 100644 --- a/libyul/Object.h +++ b/libyul/Object.h @@ -44,6 +44,8 @@ struct ObjectNode virtual std::string toString(Dialect const* _dialect) const = 0; std::string toString() { return toString(nullptr); } + /// Name of the object. + /// Can be empty since .yul files can also just contain code, without explicitly placing it in an object. YulString name; }; @@ -68,8 +70,23 @@ public: std::string toString(Dialect const* _dialect) const override; /// @returns the set of names of data objects accessible from within the code of - /// this object. - std::set dataNames() const; + /// this object, including the name of object itself + std::set qualifiedDataNames() const; + + /// @returns vector of subIDs if possible to reach subobject with @a _qualifiedName, throws otherwise + /// For "B.C" should return vector of two values if success (subId of B and subId of C in B). + /// In object "A" if called for "A.B" will return only one value (subId for B) + /// will return empty vector for @a _qualifiedName that equals to object name. + /// Example: + /// A1{ B2{ C3, D3 }, E2{ F3{ G4, K4, H4{ I5 } } } } + /// pathToSubObject("A1.E2.F3.H4") == {1, 0, 2} + /// pathToSubObject("E2.F3.H4") == {1, 0, 2} + /// pathToSubObject("A1.E2") == {1} + /// The path must not lead to a @a Data object (will throw in that case). + std::vector pathToSubObject(YulString _qualifiedName) const; + + /// sub id for object if it is subobject of another object, max value if it is not subobject + size_t subId = std::numeric_limits::max(); std::shared_ptr code; std::vector> subObjects; diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 9ff0c3846..a8327c346 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -102,9 +102,9 @@ public: /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. virtual std::pair, SubID> createSubAssembly() = 0; /// Appends the offset of the given sub-assembly or data. - virtual void appendDataOffset(SubID _sub) = 0; + virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. - virtual void appendDataSize(SubID _sub) = 0; + virtual void appendDataSize(std::vector const& _subPath) = 0; /// Appends the given data to the assembly and returns its ID. virtual SubID appendData(bytes const& _data) = 0; diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index c2204ab95..15d646755 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -147,22 +147,28 @@ pair, AbstractAssembly::SubID> EthAssemblyAdapter:: return {make_shared(*assembly), static_cast(sub.data())}; } -void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub) +void EthAssemblyAdapter::appendDataOffset(vector const& _subPath) { - auto it = m_dataHashBySubId.find(_sub); - if (it == m_dataHashBySubId.end()) - m_assembly.pushSubroutineOffset(_sub); - else + if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) + { + yulAssert(_subPath.size() == 1, ""); m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second); + return; + } + + m_assembly.pushSubroutineOffset(m_assembly.encodeSubPath(_subPath)); } -void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub) +void EthAssemblyAdapter::appendDataSize(vector const& _subPath) { - auto it = m_dataHashBySubId.find(_sub); - if (it == m_dataHashBySubId.end()) - m_assembly.pushSubroutineSize(static_cast(_sub)); - else + if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) + { + yulAssert(_subPath.size() == 1, ""); m_assembly << u256(m_assembly.data(h256(it->second)).size()); + return; + } + + m_assembly.pushSubroutineSize(m_assembly.encodeSubPath(_subPath)); } AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 3192a9133..ee56f7fcb 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -58,8 +58,8 @@ public: void appendReturnsub(int, int) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index a4a1d0a27..99945383e 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -202,12 +202,12 @@ pair, AbstractAssembly::SubID> EVMAssembly::createS return {}; } -void EVMAssembly::appendDataOffset(AbstractAssembly::SubID) +void EVMAssembly::appendDataOffset(vector const&) { yulAssert(false, "Data not implemented."); } -void EVMAssembly::appendDataSize(AbstractAssembly::SubID) +void EVMAssembly::appendDataSize(vector const&) { yulAssert(false, "Data not implemented."); } diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index f67aad8b8..6e6878476 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -80,8 +80,8 @@ public: /// Append the assembled size as a constant. void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 4b9acb0c7..c552b6e22 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -144,7 +144,7 @@ map createBuiltins(langutil::EVMVersion _evmVe FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - std::function + std::function const& ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -154,18 +154,19 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendAssemblySize(); else { - yulAssert( - _context.subIDs.count(dataName) != 0, - "Could not find assembly object <" + dataName.str() + ">." - ); - _assembly.appendDataSize(_context.subIDs.at(dataName)); + vector subIdPath = + _context.subIDs.count(dataName) == 0 ? + _context.currentObject->pathToSubObject(dataName) : + vector{_context.subIDs.at(dataName)}; + yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); + _assembly.appendDataSize(subIdPath); } })); builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - std::function + std::function const& ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -175,11 +176,12 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendConstant(0); else { - yulAssert( - _context.subIDs.count(dataName) != 0, - "Could not find assembly object <" + dataName.str() + ">." - ); - _assembly.appendDataOffset(_context.subIDs.at(dataName)); + vector subIdPath = + _context.subIDs.count(dataName) == 0 ? + _context.currentObject->pathToSubObject(dataName) : + vector{_context.subIDs.at(dataName)}; + yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); + _assembly.appendDataOffset(subIdPath); } })); builtins.emplace(createFunction( diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index 91f23b596..5eb83a6a8 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -26,6 +26,7 @@ #include #include +#include using namespace solidity::yul; using namespace std; @@ -41,11 +42,12 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) BuiltinContext context; context.currentObject = &_object; - for (auto& subNode: _object.subObjects) - if (Object* subObject = dynamic_cast(subNode.get())) + for (auto const& subNode: _object.subObjects) + if (auto* subObject = dynamic_cast(subNode.get())) { auto subAssemblyAndID = m_assembly.createSubAssembly(); context.subIDs[subObject->name] = subAssemblyAndID.second; + subObject->subId = subAssemblyAndID.second; compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize); } else diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 0d840ef66..733795523 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -132,12 +132,12 @@ pair, AbstractAssembly::SubID> NoOutputAssembly::cr return {}; } -void NoOutputAssembly::appendDataOffset(AbstractAssembly::SubID) +void NoOutputAssembly::appendDataOffset(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); } -void NoOutputAssembly::appendDataSize(AbstractAssembly::SubID) +void NoOutputAssembly::appendDataSize(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); } diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 9f853032b..b2153e42a 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -68,8 +68,8 @@ public: void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index abb898828..dc180b240 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -1249,7 +1249,7 @@ Object EVMToEwasmTranslator::run(Object const& _object) ErrorList errors; ErrorReporter errorReporter(errors); - AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames()); + AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.qualifiedDataNames()); if (!analyzer.analyze(*ret.code)) { string message = "Invalid code generated after EVM to wasm translation.\n"; diff --git a/test/cmdlineTests/standard_yul_object_invalid_sub/input.json b/test/cmdlineTests/standard_yul_object_invalid_sub/input.json new file mode 100644 index 000000000..ccc142d25 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_invalid_sub/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_object_invalid_sub/output.json b/test/cmdlineTests/standard_yul_object_invalid_sub/output.json new file mode 100644 index 000000000..5008d4916 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_invalid_sub/output.json @@ -0,0 +1,4 @@ +{"errors":[{"component":"general","formattedMessage":"A:1:40: TypeError: Unknown data object \"NamedObject.\". +object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } } + ^--------^ +","message":"Unknown data object \"NamedObject.\".","severity":"error","sourceLocation":{"end":49,"file":"A","start":39},"type":"TypeError"}]} diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index c2140ace6..c93d7b58d 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -29,6 +29,7 @@ #include #include #include +#include using namespace std; using namespace solidity::langutil; @@ -254,6 +255,24 @@ BOOST_AUTO_TEST_CASE(immutable) ); } +BOOST_AUTO_TEST_CASE(subobject_encode_decode) +{ + Assembly assembly; + + shared_ptr subAsmPtr = make_shared(); + shared_ptr subSubAsmPtr = make_shared(); + + assembly.appendSubroutine(subAsmPtr); + subAsmPtr->appendSubroutine(subSubAsmPtr); + + BOOST_CHECK(assembly.encodeSubPath({0}) == 0); + BOOST_REQUIRE_THROW(assembly.encodeSubPath({1}), solidity::evmasm::AssemblyException); + BOOST_REQUIRE_THROW(assembly.decodeSubPath(1), solidity::evmasm::AssemblyException); + + vector subPath{0, 0}; + BOOST_CHECK(assembly.decodeSubPath(assembly.encodeSubPath(subPath)) == subPath); +} + BOOST_AUTO_TEST_SUITE_END() } // end namespaces diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 57ee7ffa6..b0b07a8fb 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -90,7 +90,7 @@ pair, shared_ptr> yul::test::parse( if (!parserResult->code || errorReporter.hasErrors()) return {}; shared_ptr analysisInfo = make_shared(); - AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->dataNames()); + AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->qualifiedDataNames()); // TODO this should be done recursively. if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors()) return {}; diff --git a/test/libyul/objectCompiler/subObjectAccess.yul b/test/libyul/objectCompiler/subObjectAccess.yul new file mode 100644 index 000000000..4fddc2483 --- /dev/null +++ b/test/libyul/objectCompiler/subObjectAccess.yul @@ -0,0 +1,104 @@ +object "A" { + code { + pop(dataoffset("A")) + pop(datasize("A")) + pop(dataoffset("B")) + pop(datasize("B")) + pop(dataoffset("B.C")) + pop(datasize("B.C")) + pop(dataoffset("B.E")) + pop(datasize("B.E")) + pop(dataoffset("B.C.D")) + pop(datasize("B.C.D")) + } + + data "data1" "Hello, World!" + + object "B" { + code { + pop(dataoffset("C")) + pop(datasize("C")) + pop(dataoffset("E")) + pop(datasize("E")) + pop(dataoffset("C.D")) + pop(datasize("C.D")) + } + object "C" { + code { + pop(dataoffset("D")) + pop(datasize("D")) + } + object "D" { + code { + invalid() + } + } + } + object "E" { + code { + invalid() + } + } + } +} +// ---- +// Assembly: +// /* "source":26:46 */ +// pop(0x00) +// /* "source":51:69 */ +// pop(bytecodeSize) +// /* "source":74:94 */ +// pop(dataOffset(sub_0)) +// /* "source":99:117 */ +// pop(dataSize(sub_0)) +// /* "source":122:144 */ +// pop(dataOffset(sub_0.sub_0)) +// /* "source":149:169 */ +// pop(dataSize(sub_0.sub_0)) +// /* "source":174:196 */ +// pop(dataOffset(sub_0.sub_1)) +// /* "source":201:221 */ +// pop(dataSize(sub_0.sub_1)) +// /* "source":226:250 */ +// pop(dataOffset(sub_0.sub_0.sub_0)) +// /* "source":255:277 */ +// pop(dataSize(sub_0.sub_0.sub_0)) +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// +// sub_0: assembly { +// /* "source":347:367 */ +// pop(dataOffset(sub_0)) +// /* "source":374:392 */ +// pop(dataSize(sub_0)) +// /* "source":399:419 */ +// pop(dataOffset(sub_1)) +// /* "source":426:444 */ +// pop(dataSize(sub_1)) +// /* "source":451:473 */ +// pop(dataOffset(sub_0.sub_0)) +// /* "source":480:500 */ +// pop(dataSize(sub_0.sub_0)) +// stop +// +// sub_0: assembly { +// /* "source":545:565 */ +// pop(dataOffset(sub_0)) +// /* "source":574:592 */ +// pop(dataSize(sub_0)) +// stop +// +// sub_0: assembly { +// /* "source":645:654 */ +// invalid +// } +// } +// +// sub_1: assembly { +// /* "source":717:726 */ +// invalid +// } +// } +// Bytecode: 600050604650601f50601d50603e50600850603d50600150603c50600150fe601350600850601b50600150601c50600150fe600750600150fefefefefefe600750600150fefe +// Opcodes: PUSH1 0x0 POP PUSH1 0x46 POP PUSH1 0x1F POP PUSH1 0x1D POP PUSH1 0x3E POP PUSH1 0x8 POP PUSH1 0x3D POP PUSH1 0x1 POP PUSH1 0x3C POP PUSH1 0x1 POP INVALID PUSH1 0x13 POP PUSH1 0x8 POP PUSH1 0x1B POP PUSH1 0x1 POP PUSH1 0x1C POP PUSH1 0x1 POP INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID INVALID INVALID INVALID INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID +// SourceMappings: 26:20:0:-:0;;51:18;;74:20;;99:18;;122:22;;149:20;;174:22;;201:20;;226:24;;255:22;