Add object access builtin functions

This commit is contained in:
Bhargava Shastry 2019-05-27 11:18:05 +02:00
parent 5063e53730
commit d677a15507
4 changed files with 174 additions and 12 deletions

View File

@ -21,6 +21,7 @@
#include <libyul/Exceptions.h>
#include <libdevcore/StringUtils.h>
#include <libdevcore/Whiskers.h>
#include <boost/algorithm/cxx11/all_of.hpp>
#include <boost/algorithm/string/classification.hpp>
@ -54,6 +55,10 @@ string ProtoConverter::createHex(string const& _hexBytes)
// Use a dictionary token.
if (tmp.empty())
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");
return tmp;
}
@ -158,6 +163,12 @@ void ProtoConverter::visit(Expression const& _x)
case Expression::kCreate:
visit(_x.create());
break;
case Expression::kUnopdata:
if (m_isObject)
visit(_x.unopdata());
else
m_output << dictionaryToken();
break;
case Expression::EXPR_ONEOF_NOT_SET:
m_output << dictionaryToken();
break;
@ -432,7 +443,14 @@ void ProtoConverter::visit(NullaryOp 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:
m_output << "calldatacopy";
@ -443,6 +461,9 @@ void ProtoConverter::visit(CopyFunc const& _x)
case CopyFunc::RETURNDATA:
m_output << "returndatacopy";
break;
case CopyFunc::DATA:
m_output << "datacopy";
break;
}
m_output << "(";
visit(_x.target());
@ -988,6 +1009,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)
{
switch (_x.stmt_oneof_case())
@ -1347,21 +1385,55 @@ void ProtoConverter::visit(PopStmt const& _x)
m_output << ")\n";
}
void ProtoConverter::visit(Code const& _x)
{
m_output << "code {\n";
visit(_x.block());
m_output << "}\n";
}
void ProtoConverter::visit(Data const& _x)
{
m_output << "data \"datablock\" 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_obj())
visit(_x.obj());
m_output << "}\n";
}
void ProtoConverter::visit(Program const& _x)
{
// Initialize input size
m_inputSize = _x.ByteSizeLong();
/* Program template is as follows
* Zero or more statements. If function definition is present, it is
* called post definition.
* Example: function foo(x_0) -> x_1 {}
* x_2 := foo(calldataload(0))
* sstore(0, x_2)
*/
m_output << "{\n";
visit(_x.block());
m_output << "}\n";
// Program is either a yul object or a block of
// statements.
switch (_x.program_oneof_case())
{
case Program::kBlock:
m_output << "{\n";
visit(_x.block());
m_output << "}\n";
break;
case Program::kObj:
m_isObject = true;
visit(_x.obj());
break;
case Program::PROGRAM_ONEOF_NOT_SET:
// {} is a trivial yul program
m_output << "{}";
break;
}
}
string ProtoConverter::programToString(Program const& _input)

View File

@ -46,6 +46,8 @@ public:
m_counter = 0;
m_inputSize = 0;
m_inFunctionDef = false;
m_objectId = 0;
m_isObject = false;
}
ProtoConverter(ProtoConverter const&) = delete;
ProtoConverter(ProtoConverter&&) = delete;
@ -88,6 +90,10 @@ private:
void visit(PopStmt const&);
void visit(LowLevelCall 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&);
/// Creates a new scope, and adds @a _funcParams to it if it
@ -109,6 +115,7 @@ private:
/// Accepts an arbitrary string, removes all characters that are neither
/// alphabets nor digits from it and returns the said string.
std::string createAlphaNum(std::string const& _strBytes);
enum class NumFunctionReturns
{
None,
@ -256,6 +263,25 @@ private:
return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter());
}
/// Returns current object identifier as string. Input parameter
/// is ignored.
std::string getObjectIdentifier(ObjectId const&)
{
// TODO: Return a pseudo randomly chosen object identifier
// that is in scope as string.
// At the moment, we simply return the identifier that
// corresponds to the currently visited object.
return "object" + std::to_string(m_objectId - 1);
}
/// 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.
std::string newObjectId()
{
return "\"object" + std::to_string(m_objectId++) + "\"";
}
std::ostringstream m_output;
/// Variables in current scope
std::stack<std::vector<std::string>> m_scopeVars;
@ -288,6 +314,11 @@ private:
unsigned m_inputSize;
/// Predicate that is true if inside function definition, false otherwise
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;
};
}
}

View File

@ -167,6 +167,15 @@ message UnaryOp {
required Expression operand = 2;
}
message UnaryOpData {
enum UOpData {
SIZE = 1;
OFFSET = 2;
}
required UOpData op = 1;
required ObjectId identifier = 2;
}
message TernaryOp {
enum TOp {
ADDM = 0;
@ -183,6 +192,7 @@ message CopyFunc {
CALLDATA = 0;
CODE = 1;
RETURNDATA = 2;
DATA = 3;
}
required CopyType ct = 1;
required Expression target = 2;
@ -197,6 +207,18 @@ message ExtCodeCopy {
required Expression size = 4;
}
/// TODO: Add a field that may be used for referencing
/// a pseudo random object identifier at run time.
message ObjectId {}
message DataSize {
required ObjectId identifier = 1;
}
message DataOffset {
required ObjectId identifier = 1;
}
message NullaryOp {
enum NOp {
PC = 1;
@ -258,6 +280,7 @@ message Expression {
FunctionCall func_expr = 7;
LowLevelCall lowcall = 8;
Create create = 9;
UnaryOpData unopdata = 10;
}
}
@ -362,8 +385,25 @@ message Block {
repeated Statement statements = 1;
}
message Program {
message Object {
required Code code = 1;
optional Data data = 2;
optional Object obj = 3;
}
message Code {
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;

View File

@ -25,7 +25,9 @@
#include <libyul/AssemblyStack.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/Exceptions.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <test/tools/ossfuzz/yulFuzzerCommon.h>
@ -37,6 +39,20 @@ using namespace langutil;
using namespace dev;
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)
{
ProtoConverter converter;
@ -67,7 +83,10 @@ DEFINE_PROTO_FUZZER(Program const& _input)
// Parse protobuf mutated YUL code
if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code ||
!stack.parserResult()->analysisInfo)
{
printErrors(std::cout, stack.errors());
return;
}
}
catch (Exception const&)
{