mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7236 from ethereum/proto-add-object-access
Experimental support for object access builtins
This commit is contained in:
commit
a064e0fc97
@ -54,6 +54,10 @@ string ProtoConverter::createHex(string const& _hexBytes)
|
|||||||
// Use a dictionary token.
|
// Use a dictionary token.
|
||||||
if (tmp.empty())
|
if (tmp.empty())
|
||||||
tmp = dictionaryToken(HexPrefix::DontAdd);
|
tmp = dictionaryToken(HexPrefix::DontAdd);
|
||||||
|
// Hex literals must have even number of digits
|
||||||
|
if (tmp.size() % 2)
|
||||||
|
tmp.insert(0, "0");
|
||||||
|
|
||||||
yulAssert(tmp.size() <= 64, "Proto Fuzzer: Dictionary token too large");
|
yulAssert(tmp.size() <= 64, "Proto Fuzzer: Dictionary token too large");
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
@ -158,6 +162,12 @@ void ProtoConverter::visit(Expression const& _x)
|
|||||||
case Expression::kCreate:
|
case Expression::kCreate:
|
||||||
visit(_x.create());
|
visit(_x.create());
|
||||||
break;
|
break;
|
||||||
|
case Expression::kUnopdata:
|
||||||
|
if (m_isObject)
|
||||||
|
visit(_x.unopdata());
|
||||||
|
else
|
||||||
|
m_output << dictionaryToken();
|
||||||
|
break;
|
||||||
case Expression::EXPR_ONEOF_NOT_SET:
|
case Expression::EXPR_ONEOF_NOT_SET:
|
||||||
m_output << dictionaryToken();
|
m_output << dictionaryToken();
|
||||||
break;
|
break;
|
||||||
@ -432,7 +442,14 @@ void ProtoConverter::visit(NullaryOp const& _x)
|
|||||||
|
|
||||||
void ProtoConverter::visit(CopyFunc const& _x)
|
void ProtoConverter::visit(CopyFunc const& _x)
|
||||||
{
|
{
|
||||||
switch (_x.ct())
|
CopyFunc_CopyType type = _x.ct();
|
||||||
|
|
||||||
|
// datacopy() is valid only if we are inside
|
||||||
|
// a yul object.
|
||||||
|
if (type == CopyFunc::DATA && !m_isObject)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
{
|
{
|
||||||
case CopyFunc::CALLDATA:
|
case CopyFunc::CALLDATA:
|
||||||
m_output << "calldatacopy";
|
m_output << "calldatacopy";
|
||||||
@ -443,6 +460,9 @@ void ProtoConverter::visit(CopyFunc const& _x)
|
|||||||
case CopyFunc::RETURNDATA:
|
case CopyFunc::RETURNDATA:
|
||||||
m_output << "returndatacopy";
|
m_output << "returndatacopy";
|
||||||
break;
|
break;
|
||||||
|
case CopyFunc::DATA:
|
||||||
|
m_output << "datacopy";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
m_output << "(";
|
m_output << "(";
|
||||||
visit(_x.target());
|
visit(_x.target());
|
||||||
@ -988,6 +1008,23 @@ void ProtoConverter::visit(TerminatingStmt const& _x)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProtoConverter::visit(UnaryOpData const& _x)
|
||||||
|
{
|
||||||
|
switch (_x.op())
|
||||||
|
{
|
||||||
|
case UnaryOpData::SIZE:
|
||||||
|
m_output << Whiskers(R"(datasize("<id>"))")
|
||||||
|
("id", getObjectIdentifier(_x.identifier()))
|
||||||
|
.render();
|
||||||
|
break;
|
||||||
|
case UnaryOpData::OFFSET:
|
||||||
|
m_output << Whiskers(R"(dataoffset("<id>"))")
|
||||||
|
("id", getObjectIdentifier(_x.identifier()))
|
||||||
|
.render();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ProtoConverter::visit(Statement const& _x)
|
void ProtoConverter::visit(Statement const& _x)
|
||||||
{
|
{
|
||||||
switch (_x.stmt_oneof_case())
|
switch (_x.stmt_oneof_case())
|
||||||
@ -1347,21 +1384,89 @@ void ProtoConverter::visit(PopStmt const& _x)
|
|||||||
m_output << ")\n";
|
m_output << ")\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string ProtoConverter::getObjectIdentifier(ObjectId const& _x)
|
||||||
|
{
|
||||||
|
unsigned currentId = currentObjectId();
|
||||||
|
yulAssert(m_objectScopeTree.size() > currentId, "Proto fuzzer: Error referencing object");
|
||||||
|
std::vector<std::string> objectIdsInScope = m_objectScopeTree[currentId];
|
||||||
|
return objectIdsInScope[_x.id() % objectIdsInScope.size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConverter::visit(Code const& _x)
|
||||||
|
{
|
||||||
|
m_output << "code {\n";
|
||||||
|
visit(_x.block());
|
||||||
|
m_output << "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConverter::visit(Data const& _x)
|
||||||
|
{
|
||||||
|
// TODO: Generate random data block identifier
|
||||||
|
m_output << "data \"" << s_dataIdentifier << "\" hex\"" << createHex(_x.hex()) << "\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConverter::visit(Object const& _x)
|
||||||
|
{
|
||||||
|
// object "object<n>" {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
m_output << "object " << newObjectId() << " {\n";
|
||||||
|
visit(_x.code());
|
||||||
|
if (_x.has_data())
|
||||||
|
visit(_x.data());
|
||||||
|
if (_x.has_sub_obj())
|
||||||
|
visit(_x.sub_obj());
|
||||||
|
m_output << "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtoConverter::buildObjectScopeTree(Object const& _x)
|
||||||
|
{
|
||||||
|
// Identifies object being visited
|
||||||
|
string objectId = newObjectId(false);
|
||||||
|
vector<string> node{objectId};
|
||||||
|
if (_x.has_data())
|
||||||
|
node.push_back(s_dataIdentifier);
|
||||||
|
if (_x.has_sub_obj())
|
||||||
|
{
|
||||||
|
// Identifies sub object whose numeric suffix is
|
||||||
|
// m_objectId
|
||||||
|
string subObjectId = "object" + to_string(m_objectId);
|
||||||
|
node.push_back(subObjectId);
|
||||||
|
// TODO: Add sub-object to object's ancestors once
|
||||||
|
// nested access is implemented.
|
||||||
|
m_objectScopeTree.push_back(node);
|
||||||
|
buildObjectScopeTree(_x.sub_obj());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_objectScopeTree.push_back(node);
|
||||||
|
}
|
||||||
|
|
||||||
void ProtoConverter::visit(Program const& _x)
|
void ProtoConverter::visit(Program const& _x)
|
||||||
{
|
{
|
||||||
// Initialize input size
|
// Initialize input size
|
||||||
m_inputSize = _x.ByteSizeLong();
|
m_inputSize = _x.ByteSizeLong();
|
||||||
|
|
||||||
/* Program template is as follows
|
// Program is either a yul object or a block of
|
||||||
* Zero or more statements. If function definition is present, it is
|
// statements.
|
||||||
* called post definition.
|
switch (_x.program_oneof_case())
|
||||||
* Example: function foo(x_0) -> x_1 {}
|
{
|
||||||
* x_2 := foo(calldataload(0))
|
case Program::kBlock:
|
||||||
* sstore(0, x_2)
|
m_output << "{\n";
|
||||||
*/
|
visit(_x.block());
|
||||||
m_output << "{\n";
|
m_output << "}\n";
|
||||||
visit(_x.block());
|
break;
|
||||||
m_output << "}\n";
|
case Program::kObj:
|
||||||
|
m_isObject = true;
|
||||||
|
buildObjectScopeTree(_x.obj());
|
||||||
|
// Reset object id counter
|
||||||
|
m_objectId = 0;
|
||||||
|
visit(_x.obj());
|
||||||
|
break;
|
||||||
|
case Program::PROGRAM_ONEOF_NOT_SET:
|
||||||
|
// {} is a trivial yul program
|
||||||
|
m_output << "{}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string ProtoConverter::programToString(Program const& _input)
|
string ProtoConverter::programToString(Program const& _input)
|
||||||
|
@ -26,8 +26,10 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include <test/tools/ossfuzz/yulProto.pb.h>
|
#include <test/tools/ossfuzz/yulProto.pb.h>
|
||||||
|
|
||||||
#include <libdevcore/Common.h>
|
#include <libdevcore/Common.h>
|
||||||
#include <libdevcore/FixedHash.h>
|
#include <libdevcore/FixedHash.h>
|
||||||
|
#include <libdevcore/Whiskers.h>
|
||||||
|
|
||||||
namespace yul
|
namespace yul
|
||||||
{
|
{
|
||||||
@ -46,6 +48,8 @@ public:
|
|||||||
m_counter = 0;
|
m_counter = 0;
|
||||||
m_inputSize = 0;
|
m_inputSize = 0;
|
||||||
m_inFunctionDef = false;
|
m_inFunctionDef = false;
|
||||||
|
m_objectId = 0;
|
||||||
|
m_isObject = false;
|
||||||
}
|
}
|
||||||
ProtoConverter(ProtoConverter const&) = delete;
|
ProtoConverter(ProtoConverter const&) = delete;
|
||||||
ProtoConverter(ProtoConverter&&) = delete;
|
ProtoConverter(ProtoConverter&&) = delete;
|
||||||
@ -88,6 +92,10 @@ private:
|
|||||||
void visit(PopStmt const&);
|
void visit(PopStmt const&);
|
||||||
void visit(LowLevelCall const&);
|
void visit(LowLevelCall const&);
|
||||||
void visit(Create const&);
|
void visit(Create const&);
|
||||||
|
void visit(UnaryOpData const&);
|
||||||
|
void visit(Object const&);
|
||||||
|
void visit(Data const&);
|
||||||
|
void visit(Code const&);
|
||||||
void visit(Program const&);
|
void visit(Program const&);
|
||||||
|
|
||||||
/// Creates a new scope, and adds @a _funcParams to it if it
|
/// Creates a new scope, and adds @a _funcParams to it if it
|
||||||
@ -109,6 +117,7 @@ private:
|
|||||||
/// Accepts an arbitrary string, removes all characters that are neither
|
/// Accepts an arbitrary string, removes all characters that are neither
|
||||||
/// alphabets nor digits from it and returns the said string.
|
/// alphabets nor digits from it and returns the said string.
|
||||||
std::string createAlphaNum(std::string const& _strBytes);
|
std::string createAlphaNum(std::string const& _strBytes);
|
||||||
|
|
||||||
enum class NumFunctionReturns
|
enum class NumFunctionReturns
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@ -233,6 +242,11 @@ private:
|
|||||||
/// Removes entry from m_functionMap and m_functionName
|
/// Removes entry from m_functionMap and m_functionName
|
||||||
void updateFunctionMaps(std::string const& _x);
|
void updateFunctionMaps(std::string const& _x);
|
||||||
|
|
||||||
|
/// Build a tree of objects that contains the object/data
|
||||||
|
/// identifiers that are in scope in a given object.
|
||||||
|
/// @param _x root object of the yul protobuf specification.
|
||||||
|
void buildObjectScopeTree(Object const& _x);
|
||||||
|
|
||||||
/// Returns a pseudo-random dictionary token.
|
/// Returns a pseudo-random dictionary token.
|
||||||
/// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not
|
/// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not
|
||||||
/// @return Dictionary token at the index computed using a
|
/// @return Dictionary token at the index computed using a
|
||||||
@ -256,6 +270,30 @@ private:
|
|||||||
return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter());
|
return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a pseudo-randomly chosen object identifier that is in the
|
||||||
|
/// scope of the Yul object being visited.
|
||||||
|
std::string getObjectIdentifier(ObjectId const& _x);
|
||||||
|
|
||||||
|
/// Return new object identifier as string. Identifier string
|
||||||
|
/// is a template of the form "\"object<n>\"" where <n> is
|
||||||
|
/// a monotonically increasing object ID counter.
|
||||||
|
/// @param _decorate If true (default value), object ID is
|
||||||
|
/// enclosed within double quotes.
|
||||||
|
std::string newObjectId(bool _decorate = true)
|
||||||
|
{
|
||||||
|
return dev::Whiskers(R"(<?decorate>"</decorate>object<id><?decorate>"</decorate>)")
|
||||||
|
("decorate", _decorate)
|
||||||
|
("id", std::to_string(m_objectId++))
|
||||||
|
.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the object counter value corresponding to the object
|
||||||
|
/// being visited.
|
||||||
|
unsigned currentObjectId()
|
||||||
|
{
|
||||||
|
return m_objectId - 1;
|
||||||
|
}
|
||||||
|
|
||||||
std::ostringstream m_output;
|
std::ostringstream m_output;
|
||||||
/// Variables in current scope
|
/// Variables in current scope
|
||||||
std::stack<std::vector<std::string>> m_scopeVars;
|
std::stack<std::vector<std::string>> m_scopeVars;
|
||||||
@ -271,9 +309,13 @@ private:
|
|||||||
std::stack<std::set<dev::u256>> m_switchLiteralSetPerScope;
|
std::stack<std::set<dev::u256>> m_switchLiteralSetPerScope;
|
||||||
// Look-up table per function type that holds the number of input (output) function parameters
|
// Look-up table per function type that holds the number of input (output) function parameters
|
||||||
std::map<std::string, std::pair<unsigned, unsigned>> m_functionSigMap;
|
std::map<std::string, std::pair<unsigned, unsigned>> m_functionSigMap;
|
||||||
|
/// Tree of objects and their scopes
|
||||||
|
std::vector<std::vector<std::string>> m_objectScopeTree;
|
||||||
// mod input/output parameters impose an upper bound on the number of input/output parameters a function may have.
|
// mod input/output parameters impose an upper bound on the number of input/output parameters a function may have.
|
||||||
static unsigned constexpr s_modInputParams = 5;
|
static unsigned constexpr s_modInputParams = 5;
|
||||||
static unsigned constexpr s_modOutputParams = 5;
|
static unsigned constexpr s_modOutputParams = 5;
|
||||||
|
/// Hard-coded identifier for a Yul object's data block
|
||||||
|
static auto constexpr s_dataIdentifier = "datablock";
|
||||||
/// Predicate to keep track of for body scope. If true, break/continue
|
/// Predicate to keep track of for body scope. If true, break/continue
|
||||||
/// statements can not be created.
|
/// statements can not be created.
|
||||||
bool m_inForBodyScope;
|
bool m_inForBodyScope;
|
||||||
@ -288,6 +330,11 @@ private:
|
|||||||
unsigned m_inputSize;
|
unsigned m_inputSize;
|
||||||
/// Predicate that is true if inside function definition, false otherwise
|
/// Predicate that is true if inside function definition, false otherwise
|
||||||
bool m_inFunctionDef;
|
bool m_inFunctionDef;
|
||||||
|
/// Index used for naming objects
|
||||||
|
unsigned m_objectId;
|
||||||
|
/// Flag to track whether program is an object (true) or a statement block
|
||||||
|
/// (false: default value)
|
||||||
|
bool m_isObject;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,15 @@ message UnaryOp {
|
|||||||
required Expression operand = 2;
|
required Expression operand = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UnaryOpData {
|
||||||
|
enum UOpData {
|
||||||
|
SIZE = 1;
|
||||||
|
OFFSET = 2;
|
||||||
|
}
|
||||||
|
required UOpData op = 1;
|
||||||
|
required ObjectId identifier = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message TernaryOp {
|
message TernaryOp {
|
||||||
enum TOp {
|
enum TOp {
|
||||||
ADDM = 0;
|
ADDM = 0;
|
||||||
@ -183,6 +192,7 @@ message CopyFunc {
|
|||||||
CALLDATA = 0;
|
CALLDATA = 0;
|
||||||
CODE = 1;
|
CODE = 1;
|
||||||
RETURNDATA = 2;
|
RETURNDATA = 2;
|
||||||
|
DATA = 3;
|
||||||
}
|
}
|
||||||
required CopyType ct = 1;
|
required CopyType ct = 1;
|
||||||
required Expression target = 2;
|
required Expression target = 2;
|
||||||
@ -197,6 +207,18 @@ message ExtCodeCopy {
|
|||||||
required Expression size = 4;
|
required Expression size = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ObjectId {
|
||||||
|
required uint64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DataSize {
|
||||||
|
required ObjectId identifier = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DataOffset {
|
||||||
|
required ObjectId identifier = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message NullaryOp {
|
message NullaryOp {
|
||||||
enum NOp {
|
enum NOp {
|
||||||
PC = 1;
|
PC = 1;
|
||||||
@ -258,6 +280,7 @@ message Expression {
|
|||||||
FunctionCall func_expr = 7;
|
FunctionCall func_expr = 7;
|
||||||
LowLevelCall lowcall = 8;
|
LowLevelCall lowcall = 8;
|
||||||
Create create = 9;
|
Create create = 9;
|
||||||
|
UnaryOpData unopdata = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,8 +385,25 @@ message Block {
|
|||||||
repeated Statement statements = 1;
|
repeated Statement statements = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Program {
|
message Object {
|
||||||
|
required Code code = 1;
|
||||||
|
optional Data data = 2;
|
||||||
|
optional Object sub_obj = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Code {
|
||||||
required Block block = 1;
|
required Block block = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Data {
|
||||||
|
required string hex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Program {
|
||||||
|
oneof program_oneof {
|
||||||
|
Block block = 1;
|
||||||
|
Object obj = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
package yul.test.yul_fuzzer;
|
package yul.test.yul_fuzzer;
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
#include <libyul/AssemblyStack.h>
|
#include <libyul/AssemblyStack.h>
|
||||||
#include <libyul/backends/evm/EVMDialect.h>
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
#include <libyul/Exceptions.h>
|
#include <libyul/Exceptions.h>
|
||||||
|
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
#include <liblangutil/SourceReferenceFormatter.h>
|
||||||
|
|
||||||
#include <test/tools/ossfuzz/yulFuzzerCommon.h>
|
#include <test/tools/ossfuzz/yulFuzzerCommon.h>
|
||||||
|
|
||||||
@ -37,6 +39,20 @@ using namespace langutil;
|
|||||||
using namespace dev;
|
using namespace dev;
|
||||||
using namespace yul::test;
|
using namespace yul::test;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void printErrors(ostream& _stream, ErrorList const& _errors)
|
||||||
|
{
|
||||||
|
SourceReferenceFormatter formatter(_stream);
|
||||||
|
|
||||||
|
for (auto const& error: _errors)
|
||||||
|
formatter.printExceptionInformation(
|
||||||
|
*error,
|
||||||
|
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_PROTO_FUZZER(Program const& _input)
|
DEFINE_PROTO_FUZZER(Program const& _input)
|
||||||
{
|
{
|
||||||
ProtoConverter converter;
|
ProtoConverter converter;
|
||||||
@ -67,7 +83,10 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
|||||||
// Parse protobuf mutated YUL code
|
// Parse protobuf mutated YUL code
|
||||||
if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code ||
|
if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code ||
|
||||||
!stack.parserResult()->analysisInfo)
|
!stack.parserResult()->analysisInfo)
|
||||||
|
{
|
||||||
|
printErrors(std::cout, stack.errors());
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception const&)
|
catch (Exception const&)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user