mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add fuzzer for testing clean up of dirty bits via inline assembly
This commit is contained in:
parent
4e58c672bb
commit
7c91a31cce
@ -96,6 +96,7 @@ defaults:
|
||||
- test/tools/ossfuzz/strictasm_diff_ossfuzz
|
||||
- test/tools/ossfuzz/strictasm_opt_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_ossfuzz
|
||||
|
||||
# test result output directory
|
||||
|
@ -37,6 +37,8 @@ namespace solidity::langutil
|
||||
class Error;
|
||||
using ErrorList = std::vector<std::shared_ptr<Error const>>;
|
||||
|
||||
struct FuzzerError: virtual util::Exception {};
|
||||
struct StackTooDeepError: virtual util::Exception {};
|
||||
struct CompilerError: virtual util::Exception {};
|
||||
struct InternalCompilerError: virtual util::Exception {};
|
||||
struct FatalError: virtual util::Exception {};
|
||||
|
@ -219,8 +219,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
else
|
||||
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
||||
solAssert(
|
||||
assertThrow(
|
||||
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
// fetch target storage reference
|
||||
|
@ -405,7 +405,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
stackDiff -= 1;
|
||||
if (stackDiff < 1 || stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_identifier.location) <<
|
||||
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
|
||||
);
|
||||
|
@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory(
|
||||
// leave end_of_mem as dyn head pointer
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
dynPointers++;
|
||||
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables.");
|
||||
assertThrow(
|
||||
(argSize + dynPointers) < 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try using fewer variables."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -497,8 +501,9 @@ void CompilerUtils::encodeToMemory(
|
||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||
{
|
||||
// copy tail pointer (=mem_end - mem_start) to memory
|
||||
solAssert(
|
||||
assertThrow(
|
||||
(2 + dynPointers) <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
||||
);
|
||||
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
||||
@ -1241,7 +1246,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
||||
// move variable starting from its top end in the stack
|
||||
if (stackPosition - size + 1 > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_variable.location()) <<
|
||||
util::errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
@ -1251,7 +1256,11 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
||||
|
||||
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
|
||||
{
|
||||
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
|
||||
assertThrow(
|
||||
_stackDepth <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
for (unsigned i = 0; i < _itemSize; ++i)
|
||||
m_context << dupInstruction(_stackDepth);
|
||||
}
|
||||
@ -1273,14 +1282,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
|
||||
|
||||
void CompilerUtils::rotateStackUp(unsigned _items)
|
||||
{
|
||||
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
|
||||
assertThrow(
|
||||
_items - 1 <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
for (unsigned i = 1; i < _items; ++i)
|
||||
m_context << swapInstruction(_items - i);
|
||||
}
|
||||
|
||||
void CompilerUtils::rotateStackDown(unsigned _items)
|
||||
{
|
||||
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
|
||||
assertThrow(
|
||||
_items - 1 <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
for (unsigned i = 1; i < _items; ++i)
|
||||
m_context << swapInstruction(i);
|
||||
}
|
||||
|
@ -631,7 +631,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
|
||||
|
||||
if (stackLayout.size() > 17)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_function.location()) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
@ -795,7 +795,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
||||
if (stackDiff < 1 || stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
@ -828,7 +828,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1;
|
||||
if (stackDiff > 16 || stackDiff < 1)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
||||
);
|
||||
|
@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
|
||||
if (retSizeOnStack > 15)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_varDecl.location()) <<
|
||||
errinfo_comment("Stack too deep.")
|
||||
);
|
||||
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
{
|
||||
if (itemSize + lvalueSize > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_assignment.location()) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
|
@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
|
||||
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
|
||||
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_location) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
@ -61,7 +61,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
|
||||
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
|
||||
if (stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_location) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
|
@ -218,10 +218,10 @@ void CodeGenerator::assemble(
|
||||
}
|
||||
catch (StackTooDeepError const& _e)
|
||||
{
|
||||
yulAssert(
|
||||
assertThrow(
|
||||
false,
|
||||
"Stack too deep when compiling inline assembly" +
|
||||
(_e.comment() ? ": " + *_e.comment() : ".")
|
||||
langutil::StackTooDeepError,
|
||||
""
|
||||
);
|
||||
}
|
||||
yulAssert(transform.stackErrors().empty(), "Stack errors present but not thrown.");
|
||||
|
@ -10,7 +10,12 @@ add_dependencies(ossfuzz
|
||||
|
||||
if (OSSFUZZ)
|
||||
add_custom_target(ossfuzz_proto)
|
||||
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz)
|
||||
add_dependencies(ossfuzz_proto
|
||||
yul_proto_ossfuzz
|
||||
yul_proto_diff_ossfuzz
|
||||
yul_proto_diff_custom_mutate_ossfuzz
|
||||
sol_arith_proto_ossfuzz
|
||||
)
|
||||
|
||||
add_custom_target(ossfuzz_abiv2)
|
||||
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
|
||||
@ -60,6 +65,22 @@ if (OSSFUZZ)
|
||||
)
|
||||
set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
|
||||
add_executable(yul_proto_diff_custom_mutate_ossfuzz
|
||||
yulProto_diff_ossfuzz.cpp
|
||||
yulFuzzerCommon.cpp
|
||||
protoToYul.cpp
|
||||
yulProto.pb.cc
|
||||
protomutators/YulProtoMutator.cpp
|
||||
)
|
||||
target_include_directories(yul_proto_diff_custom_mutate_ossfuzz PRIVATE /usr/include/libprotobuf-mutator)
|
||||
target_link_libraries(yul_proto_diff_custom_mutate_ossfuzz PRIVATE yul
|
||||
yulInterpreter
|
||||
protobuf-mutator-libfuzzer.a
|
||||
protobuf-mutator.a
|
||||
protobuf.a
|
||||
)
|
||||
set_target_properties(yul_proto_diff_custom_mutate_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
|
||||
add_executable(abiv2_proto_ossfuzz
|
||||
../../EVMHost.cpp
|
||||
abiV2ProtoFuzzer.cpp
|
||||
@ -78,6 +99,52 @@ if (OSSFUZZ)
|
||||
protobuf.a
|
||||
)
|
||||
set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
|
||||
add_executable(sol_arith_proto_ossfuzz
|
||||
solArithProtoFuzzer.cpp
|
||||
solarithprotoToSol.cpp
|
||||
solArith.pb.cc
|
||||
abiV2FuzzerCommon.cpp
|
||||
../../EVMHost.cpp
|
||||
protoToYul.cpp
|
||||
yulProto.pb.cc
|
||||
yulFuzzerCommon.cpp
|
||||
)
|
||||
target_include_directories(sol_arith_proto_ossfuzz PRIVATE
|
||||
/usr/include/libprotobuf-mutator
|
||||
)
|
||||
target_link_libraries(sol_arith_proto_ossfuzz PRIVATE solidity libsolc
|
||||
yulInterpreter
|
||||
evmc
|
||||
evmone-standalone
|
||||
protobuf-mutator-libfuzzer.a
|
||||
protobuf-mutator.a
|
||||
protobuf.a
|
||||
)
|
||||
set_target_properties(sol_arith_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
|
||||
# add_executable(sol_proto_ossfuzz
|
||||
# solProtoFuzzer.cpp
|
||||
# protoToSol.cpp
|
||||
# solProto.pb.cc
|
||||
# abiV2FuzzerCommon.cpp
|
||||
# ../../EVMHost.cpp
|
||||
# SolProtoAdaptor.cpp
|
||||
# )
|
||||
# target_include_directories(sol_proto_ossfuzz PRIVATE
|
||||
# /usr/include/libprotobuf-mutator
|
||||
# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include
|
||||
# /home/bhargava/work/github/solidity/deps/evmone/include
|
||||
# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator
|
||||
# )
|
||||
# target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc
|
||||
# evmc
|
||||
# evmone-standalone
|
||||
# protobuf-mutator-libfuzzer.a
|
||||
# protobuf-mutator.a
|
||||
# protobuf.a
|
||||
# )
|
||||
# set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
else()
|
||||
add_library(solc_opt_ossfuzz
|
||||
solc_opt_ossfuzz.cpp
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -38,10 +38,14 @@ namespace solidity::yul::test::yul_fuzzer
|
||||
class ProtoConverter
|
||||
{
|
||||
public:
|
||||
ProtoConverter()
|
||||
ProtoConverter(unsigned _numGlobalVars)
|
||||
{
|
||||
m_funcVars = std::vector<std::vector<std::vector<std::string>>>{};
|
||||
m_globalVars = std::vector<std::vector<std::string>>{};
|
||||
if (_numGlobalVars > 0)
|
||||
m_globalVars.push_back({});
|
||||
for (unsigned i = 0; i < _numGlobalVars; i++)
|
||||
m_globalVars.back().push_back("v" + std::to_string(i));
|
||||
m_inForBodyScope = false;
|
||||
m_inForInitScope = false;
|
||||
m_inForCond = false;
|
||||
@ -50,20 +54,12 @@ public:
|
||||
m_counter = 0;
|
||||
m_inputSize = 0;
|
||||
m_inFunctionDef = false;
|
||||
m_objectId = 0;
|
||||
m_isObject = false;
|
||||
m_forInitScopeExtEnabled = true;
|
||||
}
|
||||
ProtoConverter(ProtoConverter const&) = delete;
|
||||
ProtoConverter(ProtoConverter&&) = delete;
|
||||
std::string programToString(Program const& _input);
|
||||
|
||||
/// Returns evm version
|
||||
solidity::langutil::EVMVersion version()
|
||||
{
|
||||
return m_evmVersion;
|
||||
}
|
||||
|
||||
private:
|
||||
void visit(BinaryOp const&);
|
||||
|
||||
@ -75,10 +71,8 @@ private:
|
||||
|
||||
std::string visit(Literal const&);
|
||||
void visit(VarRef const&);
|
||||
void visit(FunctionExpr const& _x);
|
||||
void visit(Expression const&);
|
||||
void visit(VarDecl const&);
|
||||
void visit(MultiVarDecl const&);
|
||||
void visit(TypedVarDecl const&);
|
||||
void visit(UnaryOp const&);
|
||||
void visit(AssignmentStatement const&);
|
||||
void visit(IfStmt const&);
|
||||
@ -90,23 +84,10 @@ private:
|
||||
void visit(SwitchStmt const&);
|
||||
void visit(TernaryOp const&);
|
||||
void visit(NullaryOp const&);
|
||||
void visit(LogFunc const&);
|
||||
void visit(CopyFunc const&);
|
||||
void visit(ExtCodeCopy const&);
|
||||
void visit(StopInvalidStmt const&);
|
||||
void visit(RetRevStmt const&);
|
||||
void visit(SelfDestructStmt const&);
|
||||
void visit(TerminatingStmt const&);
|
||||
void visit(FunctionCall const&);
|
||||
void visit(FunctionDef const&);
|
||||
void visit(PopStmt const&);
|
||||
void visit(LeaveStmt 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 block scope.
|
||||
@ -140,8 +121,10 @@ private:
|
||||
Multiple
|
||||
};
|
||||
|
||||
void visitFunctionInputParams(FunctionCall const&, unsigned);
|
||||
template <typename T>
|
||||
void visitFunctionInputParams(T const&, unsigned);
|
||||
void createFunctionDefAndCall(FunctionDef const&, unsigned, unsigned);
|
||||
void storeGlobals();
|
||||
|
||||
/// Convert function type to a string to be used while naming a
|
||||
/// function that is created by a function declaration statement.
|
||||
@ -169,46 +152,19 @@ private:
|
||||
/// in scope
|
||||
bool varDeclAvailable();
|
||||
|
||||
/// Return true if a function call cannot be made, false otherwise.
|
||||
/// @param _type is an enum denoting the type of function call. It
|
||||
/// can be one of NONE, SINGLE, MULTIDECL, MULTIASSIGN.
|
||||
/// NONE -> Function call does not return a value
|
||||
/// SINGLE -> Function call returns a single value
|
||||
/// MULTIDECL -> Function call returns more than one value
|
||||
/// and it is used to create a multi declaration
|
||||
/// statement
|
||||
/// MULTIASSIGN -> Function call returns more than one value
|
||||
/// and it is used to create a multi assignment
|
||||
/// statement
|
||||
/// @return True if the function call cannot be created for one of the
|
||||
/// following reasons
|
||||
// - It is a SINGLE function call (we reserve SINGLE functions for
|
||||
// expressions)
|
||||
// - It is a MULTIASSIGN function call and we do not have any
|
||||
// variables available for assignment.
|
||||
bool functionCallNotPossible(FunctionCall_Returns _type);
|
||||
|
||||
/// Checks if function call of type @a _type returns the correct number
|
||||
/// of values.
|
||||
/// @param _type Function call type of the function being checked
|
||||
/// @param _numOutParams Number of values returned by the function
|
||||
/// being checked
|
||||
/// @return true if the function returns the correct number of values,
|
||||
/// false otherwise
|
||||
bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams);
|
||||
|
||||
/// Converts protobuf function call to a Yul function call and appends
|
||||
/// Converts protobuf function call to a yul function call and appends
|
||||
/// it to output stream.
|
||||
/// @param _x Protobuf function call
|
||||
/// @param _name Function name
|
||||
/// @param _numInParams Number of input arguments accepted by function
|
||||
/// @param _newLine Flag that prints a new line to the output stream if
|
||||
/// true. Default value for the flag is true.
|
||||
/// @param _newline Boolean flag that is true if new line to be printed after
|
||||
/// function call, false otherwise
|
||||
template <typename T>
|
||||
void convertFunctionCall(
|
||||
FunctionCall const& _x,
|
||||
T const& _x,
|
||||
std::string _name,
|
||||
unsigned _numInParams,
|
||||
bool _newLine = true
|
||||
bool _newline = false
|
||||
);
|
||||
|
||||
/// Prints a Yul formatted variable declaration statement to the output
|
||||
@ -270,11 +226,6 @@ private:
|
||||
/// Removes entry from m_functionMap and m_functionName
|
||||
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.
|
||||
/// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not
|
||||
/// @return Dictionary token at the index computed using a
|
||||
@ -284,9 +235,9 @@ private:
|
||||
/// dictionarySize is the total number of entries in the dictionary.
|
||||
std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add);
|
||||
|
||||
/// Returns an EVMVersion object corresponding to the protobuf
|
||||
/// enum of type Program_Version
|
||||
solidity::langutil::EVMVersion evmVersionMapping(Program_Version const& _x);
|
||||
/// Return variable reference.
|
||||
/// @param _index: Index of variable to be referenced
|
||||
std::string varRef(unsigned _index);
|
||||
|
||||
/// Returns a monotonically increasing counter that starts from zero.
|
||||
unsigned counter()
|
||||
@ -302,29 +253,7 @@ private:
|
||||
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(unsigned _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 util::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::string dummyExpression();
|
||||
|
||||
std::ostringstream m_output;
|
||||
/// Variables in all function definitions
|
||||
@ -349,13 +278,9 @@ private:
|
||||
std::stack<std::set<u256>> m_switchLiteralSetPerScope;
|
||||
// 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;
|
||||
/// 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.
|
||||
static unsigned constexpr s_modInputParams = 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 false, break/continue
|
||||
/// statements can not be created.
|
||||
bool m_inForBodyScope;
|
||||
@ -368,24 +293,15 @@ private:
|
||||
/// Predicate to keep track of for loop init scope. If true, variable
|
||||
/// or function declarations can not be created.
|
||||
bool m_inForInitScope;
|
||||
/// Flag that is true while converting for loop condition,
|
||||
/// false otherwise.
|
||||
bool m_inForCond;
|
||||
/// Monotonically increasing counter
|
||||
unsigned m_counter;
|
||||
/// Size of protobuf input
|
||||
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;
|
||||
/// Flag to track whether scope extension of variables defined in for-init
|
||||
/// block is enabled.
|
||||
bool m_forInitScopeExtEnabled;
|
||||
/// Object that holds the targeted evm version specified by protobuf input
|
||||
solidity::langutil::EVMVersion m_evmVersion;
|
||||
bool m_inForCond;
|
||||
};
|
||||
}
|
||||
|
49
test/tools/ossfuzz/solArith.proto
Normal file
49
test/tools/ossfuzz/solArith.proto
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
import "yulProto.proto";
|
||||
|
||||
message Type {
|
||||
enum Sign {
|
||||
SIGNED = 0;
|
||||
UNSIGNED = 1;
|
||||
}
|
||||
required Sign s = 1;
|
||||
required uint32 bytewidth = 2;
|
||||
}
|
||||
|
||||
message VarDecl {
|
||||
required Type t = 1;
|
||||
}
|
||||
|
||||
message Assembly {
|
||||
required solidity.yul.test.yul_fuzzer.Program p = 1;
|
||||
}
|
||||
|
||||
message Block {
|
||||
repeated VarDecl v = 1;
|
||||
required Assembly a = 2;
|
||||
}
|
||||
|
||||
message Program {
|
||||
required Block b = 1;
|
||||
required uint64 seed = 2;
|
||||
}
|
||||
|
||||
package solidity.test.solarithfuzzer;
|
268
test/tools/ossfuzz/solArithProtoFuzzer.cpp
Normal file
268
test/tools/ossfuzz/solArithProtoFuzzer.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <test/tools/ossfuzz/solarithprotoToSol.h>
|
||||
#include <test/tools/ossfuzz/solArith.pb.h>
|
||||
#include <test/tools/ossfuzz/abiV2FuzzerCommon.h>
|
||||
#include <test/EVMHost.h>
|
||||
|
||||
#include <evmone/evmone.h>
|
||||
#include <src/libfuzzer/libfuzzer_macro.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
|
||||
|
||||
using namespace solidity::test::abiv2fuzzer;
|
||||
using namespace solidity::test::solarithfuzzer;
|
||||
using namespace solidity;
|
||||
using namespace solidity::test;
|
||||
using namespace solidity::util;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Test function returns a uint256 value
|
||||
//static size_t const expectedOutputLength = 32;
|
||||
///// Expected output value is decimal 0
|
||||
//static uint8_t const expectedOutput[expectedOutputLength] = {
|
||||
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
//};
|
||||
|
||||
/// Compares the contents of the memory address pointed to
|
||||
/// by `_result` of `_length` bytes to the expected output.
|
||||
/// Returns true if `_result` matches expected output, false
|
||||
/// otherwise.
|
||||
//bool isOutputExpected(evmc::result const& _run)
|
||||
//{
|
||||
// if (_run.output_size != expectedOutputLength)
|
||||
// return false;
|
||||
//
|
||||
// return (memcmp(_run.output_data, expectedOutput, expectedOutputLength) == 0);
|
||||
//}
|
||||
|
||||
bool compareRuns(evmc::result const& _run1, evmc::result const& _run2)
|
||||
{
|
||||
if (_run1.output_size != _run2.output_size)
|
||||
return false;
|
||||
return memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0;
|
||||
}
|
||||
|
||||
/// Accepts a reference to a user-specified input and returns an
|
||||
/// evmc_message with all of its fields zero initialized except
|
||||
/// gas and input fields.
|
||||
/// The gas field is set to the maximum permissible value so that we
|
||||
/// don't run into out of gas errors. The input field is copied from
|
||||
/// user input.
|
||||
evmc_message initializeMessage(bytes const& _input)
|
||||
{
|
||||
// Zero initialize all message fields
|
||||
evmc_message msg = {};
|
||||
// Gas available (value of type int64_t) is set to its maximum
|
||||
// value.
|
||||
msg.gas = std::numeric_limits<int64_t>::max();
|
||||
msg.input_data = _input.data();
|
||||
msg.input_size = _input.size();
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// Accepts host context implementation, and keccak256 hash of the function
|
||||
/// to be called at a specified address in the simulated blockchain as
|
||||
/// input and returns the result of the execution of the called function.
|
||||
evmc::result executeContract(
|
||||
EVMHost& _hostContext,
|
||||
bytes const& _functionHash,
|
||||
evmc_address _deployedAddress
|
||||
)
|
||||
{
|
||||
evmc_message message = initializeMessage(_functionHash);
|
||||
message.destination = _deployedAddress;
|
||||
message.kind = EVMC_CALL;
|
||||
return _hostContext.call(message);
|
||||
}
|
||||
|
||||
/// Accepts a reference to host context implementation and byte code
|
||||
/// as input and deploys it on the simulated blockchain. Returns the
|
||||
/// result of deployment.
|
||||
evmc::result deployContract(EVMHost& _hostContext, bytes const& _code)
|
||||
{
|
||||
evmc_message message = initializeMessage(_code);
|
||||
message.kind = EVMC_CREATE;
|
||||
return _hostContext.call(message);
|
||||
}
|
||||
|
||||
std::pair<bytes, Json::Value> compileContract(
|
||||
std::string _sourceCode,
|
||||
std::string _contractName,
|
||||
std::map<std::string, solidity::util::h160> const& _libraryAddresses = {},
|
||||
frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal()
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Compile contract generated by the proto fuzzer
|
||||
SolidityCompilationFramework solCompilationFramework;
|
||||
return std::make_pair(
|
||||
solCompilationFramework.compileContract(_sourceCode, _contractName, _libraryAddresses, _optimization),
|
||||
solCompilationFramework.getMethodIdentifiers()
|
||||
);
|
||||
}
|
||||
// Ignore stack too deep errors during compilation
|
||||
catch (langutil::StackTooDeepError const&)
|
||||
{
|
||||
throw langutil::FuzzerError();
|
||||
}
|
||||
}
|
||||
|
||||
evmc::result deployAndExecute(EVMHost& _hostContext, bytes _byteCode, std::string _hexEncodedInput)
|
||||
{
|
||||
// Deploy contract and signal failure if deploy failed
|
||||
evmc::result createResult = deployContract(_hostContext, _byteCode);
|
||||
solAssert(
|
||||
createResult.status_code == EVMC_SUCCESS,
|
||||
"Proto solc fuzzer: Contract creation failed"
|
||||
);
|
||||
|
||||
// Execute test function and signal failure if EVM reverted or
|
||||
// did not return expected output on successful execution.
|
||||
evmc::result callResult = executeContract(
|
||||
_hostContext,
|
||||
fromHex(_hexEncodedInput),
|
||||
createResult.create_address
|
||||
);
|
||||
|
||||
// We don't care about EVM One failures other than EVMC_REVERT
|
||||
solAssert(callResult.status_code != EVMC_REVERT, "Proto solc fuzzer: EVM One reverted");
|
||||
return callResult;
|
||||
}
|
||||
|
||||
evmc::result compileDeployAndExecute(
|
||||
std::string _sourceCode,
|
||||
std::string _contractName,
|
||||
std::string _methodName,
|
||||
frontend::OptimiserSettings _optimization,
|
||||
std::string _libraryName = {}
|
||||
)
|
||||
{
|
||||
bytes libraryBytecode;
|
||||
Json::Value libIds;
|
||||
// We target the default EVM which is the latest
|
||||
langutil::EVMVersion version = {};
|
||||
EVMHost hostContext(version, evmone);
|
||||
std::map<std::string, solidity::util::h160> _libraryAddressMap;
|
||||
|
||||
// First deploy library
|
||||
if (!_libraryName.empty())
|
||||
{
|
||||
tie(libraryBytecode, libIds) = compileContract(
|
||||
_sourceCode,
|
||||
_libraryName,
|
||||
{},
|
||||
_optimization
|
||||
);
|
||||
// Deploy contract and signal failure if deploy failed
|
||||
evmc::result createResult = deployContract(hostContext, libraryBytecode);
|
||||
solAssert(
|
||||
createResult.status_code == EVMC_SUCCESS,
|
||||
"Proto solc fuzzer: Library deployment failed"
|
||||
);
|
||||
_libraryAddressMap[_libraryName] = EVMHost::convertFromEVMC(createResult.create_address);
|
||||
}
|
||||
|
||||
auto [bytecode, ids] = compileContract(
|
||||
_sourceCode,
|
||||
_contractName,
|
||||
_libraryAddressMap,
|
||||
_optimization
|
||||
);
|
||||
|
||||
return deployAndExecute(
|
||||
hostContext,
|
||||
bytecode,
|
||||
ids[_methodName].asString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
{
|
||||
ProtoConverter converter;
|
||||
string sol_source{};
|
||||
try {
|
||||
sol_source = converter.programToString(_input);
|
||||
}
|
||||
catch (langutil::FuzzerError const&)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
|
||||
{
|
||||
// With libFuzzer binary run this to generate a YUL source file x.yul:
|
||||
// PROTO_FUZZER_DUMP_PATH=x.yul ./a.out proto-input
|
||||
ofstream of(dump_path);
|
||||
of.write(sol_source.data(), sol_source.size());
|
||||
}
|
||||
|
||||
if (const char* dump_path = getenv("SOL_DEBUG_FILE"))
|
||||
{
|
||||
sol_source.clear();
|
||||
// With libFuzzer binary run this to generate a YUL source file x.yul:
|
||||
// PROTO_FUZZER_LOAD_PATH=x.yul ./a.out proto-input
|
||||
ifstream ifstr(dump_path);
|
||||
sol_source = {
|
||||
std::istreambuf_iterator<char>(ifstr),
|
||||
std::istreambuf_iterator<char>()
|
||||
};
|
||||
std::cout << sol_source << std::endl;
|
||||
}
|
||||
|
||||
try {
|
||||
auto minimalResult = compileDeployAndExecute(
|
||||
sol_source,
|
||||
":C",
|
||||
"test()",
|
||||
frontend::OptimiserSettings::minimal(),
|
||||
""
|
||||
);
|
||||
bool successState = minimalResult.status_code == EVMC_SUCCESS;
|
||||
|
||||
auto optResult = compileDeployAndExecute(
|
||||
sol_source,
|
||||
":C",
|
||||
"test()",
|
||||
frontend::OptimiserSettings::standard(),
|
||||
""
|
||||
);
|
||||
if (successState)
|
||||
{
|
||||
solAssert(
|
||||
optResult.status_code == EVMC_SUCCESS,
|
||||
"Sol arith fuzzer: Optimal code failed"
|
||||
);
|
||||
solAssert(
|
||||
compareRuns(minimalResult, optResult),
|
||||
"Sol arith fuzzer: Runs produced different result"
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (langutil::FuzzerError const&)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
180
test/tools/ossfuzz/solarithprotoToSol.cpp
Normal file
180
test/tools/ossfuzz/solarithprotoToSol.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include <test/tools/ossfuzz/solarithprotoToSol.h>
|
||||
#include <test/tools/ossfuzz/protoToYul.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
using namespace solidity::test::solarithfuzzer;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace std;
|
||||
|
||||
string ProtoConverter::programToString(Program const& _program)
|
||||
{
|
||||
m_rand = make_unique<SolRandomNumGenerator>(
|
||||
SolRandomNumGenerator(_program.seed())
|
||||
);
|
||||
return visit(_program);
|
||||
}
|
||||
|
||||
string ProtoConverter::visit(Program const& _program)
|
||||
{
|
||||
Whiskers p(R"(pragma solidity >= 0.0.0;)");
|
||||
Whiskers c(R"(contract C {)");
|
||||
Whiskers t(R"(function test() public returns (uint)<body>)");
|
||||
t("body", visit(_program.b()));
|
||||
return p.render()
|
||||
+ '\n'
|
||||
+ c.render()
|
||||
+ '\n'
|
||||
+ '\t'
|
||||
+ t.render()
|
||||
+ '\n'
|
||||
+ '}';
|
||||
}
|
||||
|
||||
string ProtoConverter::visit(Block const& _block)
|
||||
{
|
||||
ostringstream blockStr;
|
||||
blockStr << '\n'
|
||||
<< '\t'
|
||||
<< '{'
|
||||
<< '\n';
|
||||
for (auto const& v: _block.v())
|
||||
blockStr << visit(v);
|
||||
blockStr << visit(_block.a());
|
||||
ostringstream trace;
|
||||
if (m_yulAssembly.empty())
|
||||
blockStr << "\t\treturn 0;";
|
||||
else
|
||||
blockStr << addChecks(m_yulProgram, langutil::EVMVersion::berlin(), trace);
|
||||
blockStr << '\n'
|
||||
<< '\t'
|
||||
<< '}';
|
||||
return blockStr.str();
|
||||
}
|
||||
|
||||
string ProtoConverter::addChecks(
|
||||
string const& _yulSource,
|
||||
langutil::EVMVersion _version,
|
||||
ostringstream& _os
|
||||
)
|
||||
{
|
||||
ostringstream out;
|
||||
unsigned error = 1;
|
||||
auto memoryDump = interpretYul(_yulSource, _version, _os);
|
||||
unsigned index = 0;
|
||||
for (auto const& v: m_varTypeMap)
|
||||
{
|
||||
Whiskers check(R"(<ind>if (<var> != <type>(0x<value>)) return <error>;<endl>)");
|
||||
check("ind", "\t\t");
|
||||
check("var", v.first);
|
||||
check("type", get<1>(v.second));
|
||||
u256 memIdx = index * 0x20;
|
||||
string val{};
|
||||
if (memoryDump.count(memIdx))
|
||||
{
|
||||
unsigned byteWidth = get<2>(v.second);
|
||||
val = extractBytes(memoryDump.at(memIdx), byteWidth);
|
||||
// Avoid interpretation of 20 byte literals as address literals
|
||||
// by prepending 00.
|
||||
if (byteWidth == 20)
|
||||
val = "00" + val;
|
||||
}
|
||||
else
|
||||
val = "0";
|
||||
check("value", val);
|
||||
check("error", to_string(error++));
|
||||
check("endl", "\n");
|
||||
out << check.render();
|
||||
index++;
|
||||
}
|
||||
out << "\t\treturn 0;\n";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
map<u256, string> ProtoConverter::interpretYul(
|
||||
string const& _yulSource,
|
||||
langutil::EVMVersion _version,
|
||||
ostringstream& _os
|
||||
)
|
||||
{
|
||||
using namespace yul;
|
||||
using namespace solidity::yul::test::yul_fuzzer;
|
||||
YulStringRepository::reset();
|
||||
|
||||
// AssemblyStack entry point
|
||||
AssemblyStack stack(
|
||||
_version,
|
||||
AssemblyStack::Language::StrictAssembly,
|
||||
solidity::frontend::OptimiserSettings::full()
|
||||
);
|
||||
|
||||
// Parse protobuf mutated YUL code
|
||||
if (!stack.parseAndAnalyze("source", _yulSource) || !stack.parserResult()->code ||
|
||||
!stack.parserResult()->analysisInfo)
|
||||
{
|
||||
std::cout << _yulSource << std::endl;
|
||||
yulAssert(false, "Proto fuzzer generated malformed program");
|
||||
}
|
||||
stack.optimize();
|
||||
yulFuzzerUtil i;
|
||||
|
||||
auto r = i.interpret(
|
||||
_os,
|
||||
stack.parserResult()->code,
|
||||
EVMDialect::strictAssemblyForEVMObjects(_version)
|
||||
);
|
||||
if (r != yulFuzzerUtil::TerminationReason::StepLimitReached && r != yulFuzzerUtil::TerminationReason::TraceLimitReached)
|
||||
return i.memoryDump();
|
||||
else
|
||||
throw langutil::FuzzerError();
|
||||
}
|
||||
|
||||
string ProtoConverter::visit(Assembly const& _as)
|
||||
{
|
||||
using namespace solidity::yul::test;
|
||||
ostringstream assemblyStr;
|
||||
assemblyStr << "\t\t" << "assembly {\n";
|
||||
// Yul converter
|
||||
auto c = yul_fuzzer::ProtoConverter{m_varCounter};
|
||||
m_yulAssembly = c.programToString(_as.p());
|
||||
m_yulProgram = Whiskers("{<endl><init><assembly>}")
|
||||
("endl", "\n")
|
||||
("init", m_yulInitCode.str())
|
||||
("assembly", m_yulAssembly)
|
||||
.render();
|
||||
assemblyStr << m_yulAssembly;
|
||||
assemblyStr << "\t\t}\n";
|
||||
return assemblyStr.str();
|
||||
}
|
||||
|
||||
string ProtoConverter::visit(VarDecl const& _vardecl)
|
||||
{
|
||||
Whiskers v(R"(<type> <varName> = <type>(<value>);)");
|
||||
string type = visit(_vardecl.t());
|
||||
string varName = newVarName();
|
||||
unsigned byteWidth = widthUnsigned(_vardecl.t().bytewidth());
|
||||
m_varTypeMap.emplace(varName, tuple(typeSign(_vardecl.t()), type, byteWidth));
|
||||
v("type", type);
|
||||
v("varName", varName);
|
||||
string value = maskUnsignedToHex(64);
|
||||
v("value", value);
|
||||
Whiskers i(R"(let <varName> := <value><endl>)");
|
||||
i("varName", varName);
|
||||
i("value", "0x" + extractBytes(value, byteWidth));
|
||||
i("endl", "\n");
|
||||
m_yulInitCode << i.render();
|
||||
incrementVarCounter();
|
||||
return "\t\t" + v.render() + '\n';
|
||||
}
|
||||
|
||||
string ProtoConverter::visit(Type const& _type)
|
||||
{
|
||||
return signString(_type.s()) + widthString(_type.bytewidth());
|
||||
}
|
121
test/tools/ossfuzz/solarithprotoToSol.h
Normal file
121
test/tools/ossfuzz/solarithprotoToSol.h
Normal file
@ -0,0 +1,121 @@
|
||||
#include <test/tools/ossfuzz/solArith.pb.h>
|
||||
|
||||
#include "yulFuzzerCommon.h"
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/FixedHash.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <random>
|
||||
|
||||
namespace solidity::test::solarithfuzzer
|
||||
{
|
||||
/// Random number generator that is seeded with a fuzzer
|
||||
/// supplied unsigned integer.
|
||||
struct SolRandomNumGenerator
|
||||
{
|
||||
using RandomEngine = std::mt19937_64;
|
||||
|
||||
explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {}
|
||||
|
||||
/// @returns a pseudo random unsigned integer
|
||||
unsigned operator()()
|
||||
{
|
||||
return m_random();
|
||||
}
|
||||
|
||||
RandomEngine m_random;
|
||||
};
|
||||
|
||||
enum class Sign
|
||||
{
|
||||
Signed,
|
||||
Unsigned
|
||||
};
|
||||
|
||||
class ProtoConverter
|
||||
{
|
||||
public:
|
||||
ProtoConverter()
|
||||
{
|
||||
|
||||
}
|
||||
ProtoConverter(ProtoConverter const&) = delete;
|
||||
ProtoConverter(ProtoConverter&&) = delete;
|
||||
std::string programToString(Program const& _input);
|
||||
private:
|
||||
std::string visit(Type const& _type);
|
||||
std::string visit(Assembly const& _a);
|
||||
std::string visit(VarDecl const& _decl);
|
||||
std::string visit(Block const& _block);
|
||||
std::string visit(Program const& _program);
|
||||
|
||||
std::map<u256, std::string> interpretYul(
|
||||
std::string const& _yulSource,
|
||||
langutil::EVMVersion _version,
|
||||
std::ostringstream& _os
|
||||
);
|
||||
std::string addChecks(
|
||||
std::string const& _yulSource,
|
||||
langutil::EVMVersion _version,
|
||||
std::ostringstream& _os
|
||||
);
|
||||
|
||||
std::string newVarName()
|
||||
{
|
||||
return "v" + std::to_string(m_varCounter);
|
||||
}
|
||||
void incrementVarCounter()
|
||||
{
|
||||
m_varCounter++;
|
||||
}
|
||||
static std::string signString(Type::Sign _sign)
|
||||
{
|
||||
return _sign == Type::Sign::Type_Sign_SIGNED ? "int" : "uint";
|
||||
}
|
||||
static std::string signString(Sign _sign)
|
||||
{
|
||||
return _sign == Sign::Signed ? "int" : "uint";
|
||||
}
|
||||
static std::string widthString(unsigned _width)
|
||||
{
|
||||
return std::to_string((_width % 32 + 1) * 8);
|
||||
}
|
||||
static unsigned widthUnsigned(unsigned _width)
|
||||
{
|
||||
return _width % 32 + 1;
|
||||
}
|
||||
bool varAvailable()
|
||||
{
|
||||
return m_varCounter > 0;
|
||||
}
|
||||
Sign typeSign(Type const& _ts)
|
||||
{
|
||||
return _ts.s() == Type::SIGNED ? Sign::Signed : Sign::Unsigned;
|
||||
}
|
||||
std::string maskUnsignedToHex(unsigned _numMaskNibbles)
|
||||
{
|
||||
return toHex(maskUnsignedInt(_numMaskNibbles), util::HexPrefix::Add);
|
||||
}
|
||||
|
||||
// Convert _counter to string and return its keccak256 hash
|
||||
solidity::u256 hashUnsignedInt()
|
||||
{
|
||||
return util::keccak256(util::h256((*m_rand)()));
|
||||
}
|
||||
u256 maskUnsignedInt(unsigned _numMaskNibbles)
|
||||
{
|
||||
return hashUnsignedInt() & u256("0x" + std::string(_numMaskNibbles, 'f'));
|
||||
}
|
||||
static std::string extractBytes(std::string _value, unsigned _numBytes)
|
||||
{
|
||||
return _value.substr(_value.size() - (_numBytes * 2));
|
||||
}
|
||||
|
||||
unsigned m_varCounter = 0;
|
||||
std::shared_ptr<SolRandomNumGenerator> m_rand;
|
||||
std::map<std::string, std::tuple<Sign, std::string, unsigned>> m_varTypeMap;
|
||||
std::string m_yulAssembly;
|
||||
std::string m_yulProgram;
|
||||
std::ostringstream m_yulInitCode;
|
||||
};
|
||||
}
|
@ -29,12 +29,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
|
||||
size_t _maxTraceSize
|
||||
)
|
||||
{
|
||||
InterpreterState state;
|
||||
state.maxTraceSize = _maxTraceSize;
|
||||
state.maxSteps = _maxSteps;
|
||||
m_state.maxTraceSize = _maxTraceSize;
|
||||
m_state.maxSteps = _maxSteps;
|
||||
// Add 64 bytes of pseudo-randomly generated calldata so that
|
||||
// calldata opcodes perform non trivial work.
|
||||
state.calldata = {
|
||||
m_state.calldata = {
|
||||
0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d,
|
||||
0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8,
|
||||
0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e,
|
||||
@ -44,7 +43,24 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
|
||||
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
|
||||
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
|
||||
};
|
||||
Interpreter interpreter(state, _dialect);
|
||||
m_state.returndata = {
|
||||
0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d,
|
||||
0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8,
|
||||
0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e,
|
||||
0x67, 0xce, 0x90, 0x44, 0x2e, 0x92, 0xdb, 0x88,
|
||||
0xb8, 0x43, 0x9c, 0x41, 0x42, 0x08, 0xf1, 0xd7,
|
||||
0x65, 0xe9, 0x7f, 0xeb, 0x7b, 0xb9, 0x56, 0x9f,
|
||||
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
|
||||
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
|
||||
};
|
||||
// for (unsigned i = 0; i < 64; i++)
|
||||
// {
|
||||
// m_state.memory[i] = m_state.calldata[i];
|
||||
// if (i % 32 == 0)
|
||||
// m_state.storage[util::h256(i / 32)] = util::h256(m_state.calldata[i]);
|
||||
// }
|
||||
|
||||
Interpreter interpreter(m_state, _dialect);
|
||||
|
||||
TerminationReason reason = TerminationReason::None;
|
||||
try
|
||||
@ -64,6 +80,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
|
||||
reason = TerminationReason::ExplicitlyTerminated;
|
||||
}
|
||||
|
||||
state.dumpTraceAndState(_os);
|
||||
m_state.dumpTraceAndState(_os);
|
||||
return reason;
|
||||
}
|
||||
|
||||
std::map<u256, std::string> yulFuzzerUtil::memoryDump()
|
||||
{
|
||||
return m_state.dumpMemory();
|
||||
}
|
||||
|
@ -30,13 +30,15 @@ struct yulFuzzerUtil
|
||||
None
|
||||
};
|
||||
|
||||
static TerminationReason interpret(
|
||||
TerminationReason interpret(
|
||||
std::ostream& _os,
|
||||
std::shared_ptr<yul::Block> _ast,
|
||||
Dialect const& _dialect,
|
||||
size_t _maxSteps = maxSteps,
|
||||
size_t _maxTraceSize = maxTraceSize
|
||||
);
|
||||
std::map<u256, std::string> memoryDump();
|
||||
InterpreterState m_state;
|
||||
static size_t constexpr maxSteps = 100;
|
||||
static size_t constexpr maxTraceSize = 75;
|
||||
};
|
||||
|
@ -17,82 +17,17 @@
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
message VarDecl {
|
||||
required Expression expr = 1;
|
||||
}
|
||||
|
||||
message MultiVarDecl {
|
||||
required uint32 num_vars = 1;
|
||||
}
|
||||
|
||||
message LowLevelCall {
|
||||
enum Type {
|
||||
CALL = 0;
|
||||
CALLCODE = 1;
|
||||
DELEGATECALL = 2;
|
||||
STATICCALL = 3;
|
||||
}
|
||||
required Type callty = 1;
|
||||
required Expression gas = 2;
|
||||
required Expression addr = 3;
|
||||
// Valid for call and callcode only
|
||||
required Expression wei = 4;
|
||||
required Expression in = 5;
|
||||
required Expression insize = 6;
|
||||
required Expression out = 7;
|
||||
required Expression outsize = 8;
|
||||
}
|
||||
|
||||
message Create {
|
||||
enum Type {
|
||||
CREATE = 0;
|
||||
CREATE2 = 1;
|
||||
}
|
||||
required Type createty = 1;
|
||||
required Expression wei = 2;
|
||||
required Expression position = 3;
|
||||
required Expression size = 4;
|
||||
// Valid for create2 only
|
||||
required Expression value = 5;
|
||||
}
|
||||
|
||||
message FunctionCall {
|
||||
enum Returns {
|
||||
ZERO = 1;
|
||||
SINGLE = 2;
|
||||
MULTIDECL = 3;
|
||||
MULTIASSIGN = 4;
|
||||
}
|
||||
required Returns ret = 1;
|
||||
// Indexes an existing function
|
||||
required uint32 func_index = 2;
|
||||
required Expression in_param1 = 3;
|
||||
required Expression in_param2 = 4;
|
||||
required Expression in_param3 = 5;
|
||||
required Expression in_param4 = 6;
|
||||
required VarRef out_param1 = 7;
|
||||
required VarRef out_param2 = 8;
|
||||
required VarRef out_param3 = 9;
|
||||
required VarRef out_param4 = 10;
|
||||
}
|
||||
|
||||
message TypedVarDecl {
|
||||
enum TypeName {
|
||||
BOOL = 1;
|
||||
U8 = 2;
|
||||
U32 = 3;
|
||||
U64 = 4;
|
||||
U128 = 5;
|
||||
U256 = 6;
|
||||
S8 = 7;
|
||||
S32 = 8;
|
||||
S64 = 9;
|
||||
S128 = 10;
|
||||
S256 = 11;
|
||||
};
|
||||
required int32 id = 1;
|
||||
required TypeName type = 2;
|
||||
required Expression expr = 3;
|
||||
required uint32 func_index = 1;
|
||||
required Expression in_param1 = 2;
|
||||
required Expression in_param2 = 3;
|
||||
required Expression in_param3 = 4;
|
||||
required Expression in_param4 = 5;
|
||||
required VarRef out_param1 = 6;
|
||||
required VarRef out_param2 = 7;
|
||||
required VarRef out_param3 = 8;
|
||||
required VarRef out_param4 = 9;
|
||||
}
|
||||
|
||||
message VarRef {
|
||||
@ -108,24 +43,6 @@ message Literal {
|
||||
}
|
||||
}
|
||||
|
||||
message TypedLiteral {
|
||||
enum TypeName {
|
||||
BOOL = 1;
|
||||
U8 = 2;
|
||||
U32 = 3;
|
||||
U64 = 4;
|
||||
U128 = 5;
|
||||
U256 = 6;
|
||||
S8 = 7;
|
||||
S32 = 8;
|
||||
S64 = 9;
|
||||
S128 = 10;
|
||||
S256 = 11;
|
||||
};
|
||||
required int32 val = 1;
|
||||
required TypeName type = 2;
|
||||
}
|
||||
|
||||
message BinaryOp {
|
||||
enum BOp {
|
||||
ADD = 0;
|
||||
@ -163,24 +80,11 @@ message UnaryOp {
|
||||
SLOAD = 2;
|
||||
ISZERO = 3;
|
||||
CALLDATALOAD = 4;
|
||||
EXTCODESIZE = 5;
|
||||
EXTCODEHASH = 6;
|
||||
BALANCE = 7;
|
||||
BLOCKHASH = 8;
|
||||
}
|
||||
required UOp op = 1;
|
||||
required Expression operand = 2;
|
||||
}
|
||||
|
||||
message UnaryOpData {
|
||||
enum UOpData {
|
||||
SIZE = 1;
|
||||
OFFSET = 2;
|
||||
}
|
||||
required UOpData op = 1;
|
||||
required uint64 identifier = 2;
|
||||
}
|
||||
|
||||
message TernaryOp {
|
||||
enum TOp {
|
||||
ADDM = 0;
|
||||
@ -192,33 +96,9 @@ message TernaryOp {
|
||||
required Expression arg3 = 4;
|
||||
}
|
||||
|
||||
message CopyFunc {
|
||||
enum CopyType {
|
||||
CALLDATA = 0;
|
||||
CODE = 1;
|
||||
RETURNDATA = 2;
|
||||
DATA = 3;
|
||||
}
|
||||
required CopyType ct = 1;
|
||||
required Expression target = 2;
|
||||
required Expression source = 3;
|
||||
required Expression size = 4;
|
||||
}
|
||||
|
||||
message ExtCodeCopy {
|
||||
required Expression addr = 1;
|
||||
required Expression target = 2;
|
||||
required Expression source = 3;
|
||||
required Expression size = 4;
|
||||
}
|
||||
|
||||
message NullaryOp {
|
||||
enum NOp {
|
||||
PC = 1;
|
||||
MSIZE = 2;
|
||||
GAS = 3;
|
||||
CALLDATASIZE = 4;
|
||||
CODESIZE = 5;
|
||||
RETURNDATASIZE = 6;
|
||||
ADDRESS = 7;
|
||||
ORIGIN = 8;
|
||||
@ -226,7 +106,6 @@ message NullaryOp {
|
||||
CALLVALUE = 10;
|
||||
GASPRICE = 11;
|
||||
COINBASE = 12;
|
||||
TIMESTAMP = 13;
|
||||
NUMBER = 14;
|
||||
DIFFICULTY = 15;
|
||||
GASLIMIT = 16;
|
||||
@ -247,21 +126,12 @@ message StoreFunc {
|
||||
required Storage st = 3;
|
||||
}
|
||||
|
||||
message LogFunc {
|
||||
enum NumTopics {
|
||||
ZERO = 0;
|
||||
ONE = 1;
|
||||
TWO = 2;
|
||||
THREE = 3;
|
||||
FOUR = 4;
|
||||
}
|
||||
required Expression pos = 1;
|
||||
required Expression size = 2;
|
||||
required NumTopics num_topics = 3;
|
||||
required Expression t1 = 4;
|
||||
required Expression t2 = 5;
|
||||
required Expression t3 = 6;
|
||||
required Expression t4 = 7;
|
||||
message FunctionExpr {
|
||||
required uint64 index = 1;
|
||||
required Expression in_param1 = 2;
|
||||
required Expression in_param2 = 3;
|
||||
required Expression in_param3 = 4;
|
||||
required Expression in_param4 = 5;
|
||||
}
|
||||
|
||||
message Expression {
|
||||
@ -272,10 +142,7 @@ message Expression {
|
||||
UnaryOp unop = 4;
|
||||
TernaryOp top = 5;
|
||||
NullaryOp nop = 6;
|
||||
FunctionCall func_expr = 7;
|
||||
LowLevelCall lowcall = 8;
|
||||
Create create = 9;
|
||||
UnaryOpData unopdata = 10;
|
||||
FunctionExpr funcexpr = 10;
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,15 +153,15 @@ message AssignmentStatement {
|
||||
|
||||
message IfStmt {
|
||||
required Expression cond = 1;
|
||||
required Block if_body = 2;
|
||||
required Block block = 2;
|
||||
}
|
||||
|
||||
message BoundedForStmt {
|
||||
required Block for_body = 1;
|
||||
required Block block = 1;
|
||||
}
|
||||
|
||||
message ForStmt {
|
||||
required Block for_body = 1;
|
||||
required Block block = 1;
|
||||
required Block for_init = 2;
|
||||
required Block for_post = 3;
|
||||
required Expression for_cond = 4;
|
||||
@ -302,48 +169,18 @@ message ForStmt {
|
||||
|
||||
message CaseStmt {
|
||||
required Literal case_lit = 1;
|
||||
required Block case_block = 2;
|
||||
required Block block = 2;
|
||||
}
|
||||
|
||||
message SwitchStmt {
|
||||
required Expression switch_expr = 1;
|
||||
repeated CaseStmt case_stmt = 2;
|
||||
optional Block default_block = 3;
|
||||
optional Block block = 3;
|
||||
}
|
||||
|
||||
message BreakStmt {}
|
||||
message ContinueStmt {}
|
||||
|
||||
message StopInvalidStmt {
|
||||
enum Type {
|
||||
STOP = 0;
|
||||
INVALID = 1;
|
||||
}
|
||||
required Type stmt = 1;
|
||||
}
|
||||
|
||||
message RetRevStmt {
|
||||
enum Type {
|
||||
RETURN = 0;
|
||||
REVERT = 1;
|
||||
}
|
||||
required Type stmt = 1;
|
||||
required Expression pos = 2;
|
||||
required Expression size = 3;
|
||||
}
|
||||
|
||||
message SelfDestructStmt {
|
||||
required Expression addr = 1;
|
||||
}
|
||||
|
||||
message TerminatingStmt {
|
||||
oneof term_oneof {
|
||||
StopInvalidStmt stop_invalid = 1;
|
||||
RetRevStmt ret_rev = 2;
|
||||
SelfDestructStmt self_des = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message FunctionDef {
|
||||
required uint32 num_input_params = 1;
|
||||
required uint32 num_output_params = 2;
|
||||
@ -359,7 +196,6 @@ message LeaveStmt {}
|
||||
|
||||
message Statement {
|
||||
oneof stmt_oneof {
|
||||
VarDecl decl = 1;
|
||||
AssignmentStatement assignment = 2;
|
||||
IfStmt ifstmt = 3;
|
||||
StoreFunc storage_func = 4;
|
||||
@ -368,16 +204,11 @@ message Statement {
|
||||
SwitchStmt switchstmt = 7;
|
||||
BreakStmt breakstmt = 8;
|
||||
ContinueStmt contstmt = 9;
|
||||
LogFunc log_func = 10;
|
||||
CopyFunc copy_func = 11;
|
||||
ExtCodeCopy extcode_copy = 12;
|
||||
TerminatingStmt terminatestmt = 13;
|
||||
FunctionCall functioncall = 14;
|
||||
BoundedForStmt boundedforstmt = 15;
|
||||
FunctionDef funcdef = 16;
|
||||
PopStmt pop = 17;
|
||||
LeaveStmt leave = 18;
|
||||
MultiVarDecl multidecl = 19;
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,36 +216,8 @@ message Block {
|
||||
repeated Statement statements = 1;
|
||||
}
|
||||
|
||||
message Object {
|
||||
required Code code = 1;
|
||||
optional Data data = 2;
|
||||
optional Object sub_obj = 3;
|
||||
}
|
||||
|
||||
message Code {
|
||||
message Program {
|
||||
required Block block = 1;
|
||||
}
|
||||
|
||||
message Data {
|
||||
required string hex = 1;
|
||||
}
|
||||
|
||||
message Program {
|
||||
enum Version {
|
||||
HOMESTEAD = 0;
|
||||
TANGERINE = 1;
|
||||
SPURIOUS = 2;
|
||||
BYZANTIUM = 3;
|
||||
CONSTANTINOPLE = 4;
|
||||
PETERSBURG = 5;
|
||||
ISTANBUL = 6;
|
||||
BERLIN = 7;
|
||||
}
|
||||
oneof program_oneof {
|
||||
Block block = 1;
|
||||
Object obj = 2;
|
||||
}
|
||||
required Version ver = 3;
|
||||
}
|
||||
|
||||
package solidity.yul.test.yul_fuzzer;
|
||||
package solidity.yul.test.yul_fuzzer;
|
@ -96,7 +96,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
return;
|
||||
|
||||
stack.optimize();
|
||||
termReason = yulFuzzerUtil::interpret(
|
||||
yulFuzzerUtil::interpret(
|
||||
os2,
|
||||
stack.parserResult()->code,
|
||||
EVMDialect::strictAssemblyForEVMObjects(version),
|
||||
|
@ -46,6 +46,18 @@ using namespace solidity::yul::test;
|
||||
|
||||
using solidity::util::h256;
|
||||
|
||||
std::map<u256, std::string> InterpreterState::dumpMemory() const
|
||||
{
|
||||
std::map<u256, std::string> out;
|
||||
map<u256, u256> words;
|
||||
for (auto const& [offset, value]: memory)
|
||||
words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * size_t(offset % 0x20));
|
||||
for (auto const& [offset, value]: words)
|
||||
if (value != 0)
|
||||
out.emplace(offset, h256(value).hex());
|
||||
return out;
|
||||
}
|
||||
|
||||
void InterpreterState::dumpTraceAndState(ostream& _out) const
|
||||
{
|
||||
_out << "Trace:" << endl;
|
||||
|
@ -94,6 +94,7 @@ struct InterpreterState
|
||||
ControlFlowState controlFlowState = ControlFlowState::Default;
|
||||
|
||||
void dumpTraceAndState(std::ostream& _out) const;
|
||||
std::map<u256, std::string> dumpMemory() const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user