Merge pull request #9235 from ethereum/yulSubObjectAccessWithDot

Yul sub object access with dot
This commit is contained in:
chriseth 2020-07-22 09:50:41 +02:00 committed by GitHub
commit cb1f7a8410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 357 additions and 77 deletions

View File

@ -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<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));
uint8_t b = max<unsigned>(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);
bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef);
toBigEndian(ret.bytecode.size(), r);
ret.append(subAssemblyById(subIdPath)->assemble());
}
ret.append(m_subs[i]->assemble());
}
for (auto const& i: tagRef)
{
size_t subId;
size_t tagId;
tie(subId, tagId) = i.second;
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() ?
m_tagPositionsInBytecode :
m_subs[subId]->m_tagPositionsInBytecode;
@ -758,3 +756,51 @@ LinkerObject const& Assembly::assemble() const
}
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.
void markAsInvalid() { m_invalid = true; }
std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
size_t encodeSubPath(std::vector<size_t> 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<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
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 std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -18,7 +18,10 @@
#include <libevmasm/AssemblyItem.h>
#include <libevmasm/Assembly.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/FixedHash.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;
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<size_t>(data())) + ")";
break;
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;
}
case PushProgramSize:
text = string("bytecodeSize");
break;

View File

@ -152,7 +152,7 @@ public:
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_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;

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ map<YulString, int> CompilabilityChecker::run(
bool _optimizeStackAllocation
)
{
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
if (auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
{
NoOutputEVMDialect noOutputDialect(*evmDialect);
@ -48,8 +48,10 @@ map<YulString, int> 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,

View File

@ -24,9 +24,10 @@
#include <libyul/AsmPrinter.h>
#include <libyul/Exceptions.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/replace.hpp>
using namespace std;
@ -62,13 +63,54 @@ string Object::toString(Dialect const* _dialect) const
return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
}
set<YulString> Object::dataNames() const
set<YulString> Object::qualifiedDataNames() const
{
set<YulString> 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<YulString> qualifiedNames = name.empty() ? set<YulString>{} : set<YulString>{name};
for (shared_ptr<ObjectNode> const& subObjectNode: subObjects)
{
yulAssert(qualifiedNames.count(subObjectNode->name) == 0, "");
qualifiedNames.insert(subObjectNode->name);
if (auto const* subObject = dynamic_cast<Object const*>(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<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;
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<YulString> dataNames() const;
/// this object, including the name of object itself
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::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.
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() = 0;
/// 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.
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.
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())};
}
void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub)
void EthAssemblyAdapter::appendDataOffset(vector<AbstractAssembly::SubID> 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<AbstractAssembly::SubID> const& _subPath)
{
auto it = m_dataHashBySubId.find(_sub);
if (it == m_dataHashBySubId.end())
m_assembly.pushSubroutineSize(static_cast<size_t>(_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)

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@
#include <libyul/Object.h>
#include <libyul/Exceptions.h>
#include <libevmasm/Assembly.h>
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<Object*>(subNode.get()))
for (auto const& subNode: _object.subObjects)
if (auto* subObject = dynamic_cast<Object*>(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

View File

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

View File

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

View File

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

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 <tuple>
#include <memory>
#include <libyul/Exceptions.h>
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<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()
} // end namespaces

View File

@ -90,7 +90,7 @@ pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(
if (!parserResult->code || errorReporter.hasErrors())
return {};
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.
if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors())
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;