[yul] Adding support for accessing subobjects via .

Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
This commit is contained in:
Djordje Mijovic 2020-06-17 11:17:35 +02:00
parent 952101996c
commit 6f97e6153c
24 changed files with 357 additions and 76 deletions

View File

@ -92,8 +92,8 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const&
class Functionalizer class Functionalizer
{ {
public: public:
Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes): Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes, Assembly const& _assembly):
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes) m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly)
{} {}
void feed(AssemblyItem const& _item) void feed(AssemblyItem const& _item)
@ -104,6 +104,9 @@ public:
m_location = _item.location(); m_location = _item.location();
printLocation(); printLocation();
} }
string expression = _item.toAssemblyText(m_assembly);
if (!( if (!(
_item.canBeFunctional() && _item.canBeFunctional() &&
_item.returnValues() <= 1 && _item.returnValues() <= 1 &&
@ -111,10 +114,9 @@ public:
)) ))
{ {
flush(); flush();
m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl; m_out << m_prefix << (_item.type() == Tag ? "" : " ") << expression << endl;
return; return;
} }
string expression = _item.toAssemblyText();
if (_item.arguments() > 0) if (_item.arguments() > 0)
{ {
expression += "("; expression += "(";
@ -160,13 +162,14 @@ private:
ostream& m_out; ostream& m_out;
string const& m_prefix; string const& m_prefix;
StringMap const& m_sourceCodes; StringMap const& m_sourceCodes;
Assembly const& m_assembly;
}; };
} }
void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const 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) for (auto const& i: m_items)
f.feed(i); f.feed(i);
@ -639,7 +642,7 @@ LinkerObject const& Assembly::assemble() const
case PushSubSize: case PushSubSize:
{ {
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, ""); assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
auto s = m_subs.at(static_cast<size_t>(i.data()))->assemble().bytecode.size(); auto s = subAssemblyById(static_cast<size_t>(i.data()))->assemble().bytecode.size();
i.setPushedValue(u256(s)); i.setPushedValue(u256(s));
uint8_t b = max<unsigned>(1, util::bytesRequired(s)); uint8_t b = max<unsigned>(1, util::bytesRequired(s));
ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b); 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. // Append an INVALID here to help tests find miscompilation.
ret.bytecode.push_back(uint8_t(Instruction::INVALID)); 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); bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef);
if (references.first == references.second) toBigEndian(ret.bytecode.size(), r);
continue; ret.append(subAssemblyById(subIdPath)->assemble());
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());
} }
for (auto const& i: tagRef) for (auto const& i: tagRef)
{ {
size_t subId; size_t subId;
size_t tagId; size_t tagId;
tie(subId, tagId) = i.second; tie(subId, tagId) = i.second;
assertThrow(subId == numeric_limits<size_t>::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id"); assertThrow(subId == numeric_limits<size_t>::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id");
std::vector<size_t> const& tagPositions = vector<size_t> const& tagPositions =
subId == numeric_limits<size_t>::max() ? subId == numeric_limits<size_t>::max() ?
m_tagPositionsInBytecode : m_tagPositionsInBytecode :
m_subs[subId]->m_tagPositionsInBytecode; m_subs[subId]->m_tagPositionsInBytecode;
@ -758,3 +756,51 @@ LinkerObject const& Assembly::assemble() const
} }
return ret; return ret;
} }
vector<size_t> 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<size_t> 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<size_t>::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<size_t> 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;
}

View File

@ -146,6 +146,9 @@ public:
/// Mark this assembly as invalid. Calling ``assemble`` on it will throw. /// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
void markAsInvalid() { m_invalid = true; } void markAsInvalid() { m_invalid = true; }
std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
size_t encodeSubPath(std::vector<size_t> const& _subPath);
protected: protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and /// 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 /// 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); static std::string toStringInHex(u256 _value);
bool m_invalid = false; bool m_invalid = false;
Assembly const* subAssemblyById(size_t _subId) const;
protected: protected:
/// 0 is reserved for exception /// 0 is reserved for exception
unsigned m_usedTags = 1; unsigned m_usedTags = 1;
@ -179,6 +185,10 @@ protected:
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked. std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables. std::map<util::h256, std::string> 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<std::vector<size_t>, size_t> m_subPaths;
mutable LinkerObject m_assembledObject; mutable LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode; mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -18,7 +18,10 @@
#include <libevmasm/AssemblyItem.h> #include <libevmasm/AssemblyItem.h>
#include <libevmasm/Assembly.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
@ -170,7 +173,7 @@ string AssemblyItem::getJumpTypeAsString() const
} }
} }
string AssemblyItem::toAssemblyText() const string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
{ {
string text; string text;
switch (type()) switch (type())
@ -208,11 +211,18 @@ string AssemblyItem::toAssemblyText() const
text = string("data_") + util::toHex(data()); text = string("data_") + util::toHex(data());
break; break;
case PushSub: case PushSub:
text = string("dataOffset(sub_") + to_string(static_cast<size_t>(data())) + ")";
break;
case PushSubSize: case PushSubSize:
text = string("dataSize(sub_") + to_string(static_cast<size_t>(data())) + ")"; {
vector<string> subPathComponents;
for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast<size_t>(data())))
subPathComponents.emplace_back("sub_" + to_string(subPathComponentId));
text =
(type() == PushSub ? "dataOffset"s : "dataSize"s) +
"(" +
solidity::util::joinHumanReadable(subPathComponents, ".") +
")";
break; break;
}
case PushProgramSize: case PushProgramSize:
text = string("bytecodeSize"); text = string("bytecodeSize");
break; break;

View File

@ -152,7 +152,7 @@ public:
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); } void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
u256 const* pushedValue() const { return m_pushedValue.get(); } u256 const* pushedValue() const { return m_pushedValue.get(); }
std::string toAssemblyText() const; std::string toAssemblyText(Assembly const& _assembly) const;
size_t m_modifierDepth = 0; size_t m_modifierDepth = 0;

View File

@ -72,7 +72,7 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
errors, errors,
_dialect, _dialect,
{}, {},
_object.dataNames() _object.qualifiedDataNames()
).analyze(*_object.code); ).analyze(*_object.code);
yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code."); yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code.");
return analysisInfo; return analysisInfo;

View File

@ -138,7 +138,7 @@ bool AssemblyStack::analyzeParsed(Object& _object)
m_errorReporter, m_errorReporter,
languageToDialect(m_language, m_evmVersion), languageToDialect(m_language, m_evmVersion),
{}, {},
_object.dataNames() _object.qualifiedDataNames()
); );
bool success = analyzer.analyze(*_object.code); bool success = analyzer.analyze(*_object.code);
for (auto& subNode: _object.subObjects) for (auto& subNode: _object.subObjects)

View File

@ -39,7 +39,7 @@ map<YulString, int> CompilabilityChecker::run(
bool _optimizeStackAllocation bool _optimizeStackAllocation
) )
{ {
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect)) if (auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
{ {
NoOutputEVMDialect noOutputDialect(*evmDialect); NoOutputEVMDialect noOutputDialect(*evmDialect);
@ -48,8 +48,10 @@ map<YulString, int> CompilabilityChecker::run(
BuiltinContext builtinContext; BuiltinContext builtinContext;
builtinContext.currentObject = &_object; builtinContext.currentObject = &_object;
for (auto name: _object.dataNames()) if (!_object.name.empty())
builtinContext.subIDs[name] = 1; builtinContext.subIDs[_object.name] = 1;
for (auto const& subNode: _object.subObjects)
builtinContext.subIDs[subNode->name] = 1;
NoOutputAssembly assembly; NoOutputAssembly assembly;
CodeTransform transform( CodeTransform transform(
assembly, assembly,

View File

@ -24,9 +24,10 @@
#include <libyul/AsmPrinter.h> #include <libyul/AsmPrinter.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
using namespace std; using namespace std;
@ -62,13 +63,54 @@ string Object::toString(Dialect const* _dialect) const
return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}"; return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
} }
set<YulString> Object::dataNames() const set<YulString> Object::qualifiedDataNames() const
{ {
set<YulString> names; set<YulString> qualifiedNames = name.empty() ? set<YulString>{} : set<YulString>{name};
names.insert(name); for (shared_ptr<ObjectNode> const& subObjectNode: subObjects)
for (auto const& subObject: subIndexByName) {
names.insert(subObject.first); yulAssert(qualifiedNames.count(subObjectNode->name) == 0, "");
// The empty name is not valid qualifiedNames.insert(subObjectNode->name);
names.erase(YulString{}); if (auto const* subObject = dynamic_cast<Object const*>(subObjectNode.get()))
return names; 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<size_t> 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<string> subObjectPathComponents;
boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of("."));
vector<size_t> 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 const*>(object->subObjects[subIndexIt->second].get());
yulAssert(object, "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code.");
yulAssert(object->subId != numeric_limits<size_t>::max(), "");
path.push_back({object->subId});
}
return path;
} }

View File

@ -44,6 +44,8 @@ struct ObjectNode
virtual std::string toString(Dialect const* _dialect) const = 0; virtual std::string toString(Dialect const* _dialect) const = 0;
std::string toString() { return toString(nullptr); } 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; YulString name;
}; };
@ -68,8 +70,23 @@ public:
std::string toString(Dialect const* _dialect) const override; std::string toString(Dialect const* _dialect) const override;
/// @returns the set of names of data objects accessible from within the code of /// @returns the set of names of data objects accessible from within the code of
/// this object. /// this object, including the name of object itself
std::set<YulString> dataNames() const; std::set<YulString> 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<size_t> 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<size_t>::max();
std::shared_ptr<Block> code; std::shared_ptr<Block> code;
std::vector<std::shared_ptr<ObjectNode>> subObjects; std::vector<std::shared_ptr<ObjectNode>> subObjects;

View File

@ -102,9 +102,9 @@ public:
/// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset.
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() = 0; virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() = 0;
/// Appends the offset of the given sub-assembly or data. /// Appends the offset of the given sub-assembly or data.
virtual void appendDataOffset(SubID _sub) = 0; virtual void appendDataOffset(std::vector<SubID> const& _subPath) = 0;
/// Appends the size of the given sub-assembly or data. /// Appends the size of the given sub-assembly or data.
virtual void appendDataSize(SubID _sub) = 0; virtual void appendDataSize(std::vector<SubID> const& _subPath) = 0;
/// Appends the given data to the assembly and returns its ID. /// Appends the given data to the assembly and returns its ID.
virtual SubID appendData(bytes const& _data) = 0; virtual SubID appendData(bytes const& _data) = 0;

View File

@ -147,22 +147,28 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::
return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())}; return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())};
} }
void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub) void EthAssemblyAdapter::appendDataOffset(vector<AbstractAssembly::SubID> const& _subPath)
{ {
auto it = m_dataHashBySubId.find(_sub); if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
if (it == m_dataHashBySubId.end()) {
m_assembly.pushSubroutineOffset(_sub); yulAssert(_subPath.size() == 1, "");
else
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second); 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<AbstractAssembly::SubID> const& _subPath)
{ {
auto it = m_dataHashBySubId.find(_sub); if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
if (it == m_dataHashBySubId.end()) {
m_assembly.pushSubroutineSize(static_cast<size_t>(_sub)); yulAssert(_subPath.size() == 1, "");
else
m_assembly << u256(m_assembly.data(h256(it->second)).size()); 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) AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data)

View File

@ -58,8 +58,8 @@ public:
void appendReturnsub(int, int) override; void appendReturnsub(int, int) override;
void appendAssemblySize() override; void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override; void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(SubID _sub) override; void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override; void appendImmutable(std::string const& _identifier) override;

View File

@ -202,12 +202,12 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EVMAssembly::createS
return {}; return {};
} }
void EVMAssembly::appendDataOffset(AbstractAssembly::SubID) void EVMAssembly::appendDataOffset(vector<AbstractAssembly::SubID> const&)
{ {
yulAssert(false, "Data not implemented."); yulAssert(false, "Data not implemented.");
} }
void EVMAssembly::appendDataSize(AbstractAssembly::SubID) void EVMAssembly::appendDataSize(vector<AbstractAssembly::SubID> const&)
{ {
yulAssert(false, "Data not implemented."); yulAssert(false, "Data not implemented.");
} }

View File

@ -80,8 +80,8 @@ public:
/// Append the assembled size as a constant. /// Append the assembled size as a constant.
void appendAssemblySize() override; void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override; void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(SubID _sub) override; void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override; void appendImmutable(std::string const& _identifier) override;

View File

@ -144,7 +144,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
std::function<void(Expression const&)> std::function<void(Expression const&)> const&
) { ) {
yulAssert(_context.currentObject, "No object available."); yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, ""); yulAssert(_call.arguments.size() == 1, "");
@ -154,18 +154,19 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendAssemblySize(); _assembly.appendAssemblySize();
else else
{ {
yulAssert( vector<size_t> subIdPath =
_context.subIDs.count(dataName) != 0, _context.subIDs.count(dataName) == 0 ?
"Could not find assembly object <" + dataName.str() + ">." _context.currentObject->pathToSubObject(dataName) :
); vector<size_t>{_context.subIDs.at(dataName)};
_assembly.appendDataSize(_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}, []( builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
std::function<void(Expression const&)> std::function<void(Expression const&)> const&
) { ) {
yulAssert(_context.currentObject, "No object available."); yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, ""); yulAssert(_call.arguments.size() == 1, "");
@ -175,11 +176,12 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendConstant(0); _assembly.appendConstant(0);
else else
{ {
yulAssert( vector<size_t> subIdPath =
_context.subIDs.count(dataName) != 0, _context.subIDs.count(dataName) == 0 ?
"Could not find assembly object <" + dataName.str() + ">." _context.currentObject->pathToSubObject(dataName) :
); vector<size_t>{_context.subIDs.at(dataName)};
_assembly.appendDataOffset(_context.subIDs.at(dataName)); yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">.");
_assembly.appendDataOffset(subIdPath);
} }
})); }));
builtins.emplace(createFunction( builtins.emplace(createFunction(

View File

@ -26,6 +26,7 @@
#include <libyul/Object.h> #include <libyul/Object.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libevmasm/Assembly.h>
using namespace solidity::yul; using namespace solidity::yul;
using namespace std; using namespace std;
@ -41,11 +42,12 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
BuiltinContext context; BuiltinContext context;
context.currentObject = &_object; context.currentObject = &_object;
for (auto& subNode: _object.subObjects) for (auto const& subNode: _object.subObjects)
if (Object* subObject = dynamic_cast<Object*>(subNode.get())) if (auto* subObject = dynamic_cast<Object*>(subNode.get()))
{ {
auto subAssemblyAndID = m_assembly.createSubAssembly(); auto subAssemblyAndID = m_assembly.createSubAssembly();
context.subIDs[subObject->name] = subAssemblyAndID.second; context.subIDs[subObject->name] = subAssemblyAndID.second;
subObject->subId = subAssemblyAndID.second;
compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize); compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize);
} }
else else

View File

@ -132,12 +132,12 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::cr
return {}; return {};
} }
void NoOutputAssembly::appendDataOffset(AbstractAssembly::SubID) void NoOutputAssembly::appendDataOffset(std::vector<AbstractAssembly::SubID> const&)
{ {
appendInstruction(evmasm::Instruction::PUSH1); appendInstruction(evmasm::Instruction::PUSH1);
} }
void NoOutputAssembly::appendDataSize(AbstractAssembly::SubID) void NoOutputAssembly::appendDataSize(std::vector<AbstractAssembly::SubID> const&)
{ {
appendInstruction(evmasm::Instruction::PUSH1); appendInstruction(evmasm::Instruction::PUSH1);
} }

View File

@ -68,8 +68,8 @@ public:
void appendAssemblySize() override; void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override; void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(SubID _sub) override; void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override; void appendImmutable(std::string const& _identifier) override;

View File

@ -1249,7 +1249,7 @@ Object EVMToEwasmTranslator::run(Object const& _object)
ErrorList errors; ErrorList errors;
ErrorReporter errorReporter(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)) if (!analyzer.analyze(*ret.code))
{ {
string message = "Invalid code generated after EVM to wasm translation.\n"; string message = "Invalid code generated after EVM to wasm translation.\n";

View File

@ -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":
{
"*": { "*": ["*"], "": [ "*" ] }
}
}
}

View File

@ -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"}]}

View File

@ -29,6 +29,7 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <memory> #include <memory>
#include <libyul/Exceptions.h>
using namespace std; using namespace std;
using namespace solidity::langutil; using namespace solidity::langutil;
@ -254,6 +255,24 @@ BOOST_AUTO_TEST_CASE(immutable)
); );
} }
BOOST_AUTO_TEST_CASE(subobject_encode_decode)
{
Assembly assembly;
shared_ptr<Assembly> subAsmPtr = make_shared<Assembly>();
shared_ptr<Assembly> subSubAsmPtr = make_shared<Assembly>();
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<size_t> subPath{0, 0};
BOOST_CHECK(assembly.decodeSubPath(assembly.encodeSubPath(subPath)) == subPath);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} // end namespaces } // end namespaces

View File

@ -90,7 +90,7 @@ pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(
if (!parserResult->code || errorReporter.hasErrors()) if (!parserResult->code || errorReporter.hasErrors())
return {}; return {};
shared_ptr<AsmAnalysisInfo> analysisInfo = make_shared<AsmAnalysisInfo>(); shared_ptr<AsmAnalysisInfo> analysisInfo = make_shared<AsmAnalysisInfo>();
AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->dataNames()); AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->qualifiedDataNames());
// TODO this should be done recursively. // TODO this should be done recursively.
if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors()) if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors())
return {}; return {};

View File

@ -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;