From 3cfeca33c19fbf1a9a09907f0c5b1454b1618431 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry <bhargava.shastry@ethereum.org> Date: Mon, 23 Sep 2019 17:46:46 +0200 Subject: [PATCH 1/3] Abiv2 proto fuzzer: Refactor and add support for structs --- test/tools/ossfuzz/abiV2Proto.proto | 39 +- test/tools/ossfuzz/protoToAbiV2.cpp | 1550 ++++++++++++++------------- test/tools/ossfuzz/protoToAbiV2.h | 714 ++++++++---- 3 files changed, 1339 insertions(+), 964 deletions(-) diff --git a/test/tools/ossfuzz/abiV2Proto.proto b/test/tools/ossfuzz/abiV2Proto.proto index bf5f782f9..c634e545c 100644 --- a/test/tools/ossfuzz/abiV2Proto.proto +++ b/test/tools/ossfuzz/abiV2Proto.proto @@ -17,30 +17,6 @@ syntax = "proto2"; -// Flattened specification of array dimension -// If is_static is false, and this array dimension is contained -// inside another dimension e.g., x[][2] ([2] being the outer dimension) -// then `length` for this dimension is the length of the first dynamically -// sized array. The other (n-1) lengths are unspecified -message ArrayDimensionInfo { - required uint32 length = 1; - required bool is_static = 2; -} - -// TODO: Add more base types -// See https://github.com/ethereum/solidity/issues/6749 -message ArrayType { - oneof base_type_oneof { - IntegerType inty = 1; - FixedByteType byty = 2; - AddressType adty = 3; - StructType stty = 4; - BoolType boolty = 5; - DynamicByteArrayType dynbytesty = 6; - } - repeated ArrayDimensionInfo info = 7; -} - // bool message BoolType {} @@ -78,6 +54,16 @@ message DynamicByteArrayType { required DType type = 1; } +message ArrayType { + required Type t = 1; + required uint32 length = 2; + required bool is_static = 3; +} + +message StructType { + repeated Type t = 1; +} + message NonValueType { oneof nonvalue_type_oneof { DynamicByteArrayType dynbytearray = 1; @@ -86,6 +72,8 @@ message NonValueType { } } +// TODO: Add more types +// See https://github.com/ethereum/solidity/issues/6749 message Type { oneof type_oneof { ValueType vtype = 1; @@ -93,9 +81,6 @@ message Type { } } -// TODO: This must not reference itself either directly or indirectly -message StructType {} - message VarDecl { required Type type = 1; } diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index b20fca810..aa35c80c8 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -1,732 +1,224 @@ -#include <regex> -#include <numeric> -#include <boost/range/adaptor/reversed.hpp> #include <test/tools/ossfuzz/protoToAbiV2.h> -#include <libdevcore/StringUtils.h> -#include <libdevcore/Whiskers.h> -#include <liblangutil/Exceptions.h> using namespace std; using namespace dev; using namespace dev::test::abiv2fuzzer; -// Create a new variable declaration and append said variable to function parameter lists -// of coder functions. -// Declared name is x_<i>; parameterized name is c_<i> -// where <i> is a monotonically increasing integer. -void ProtoConverter::createDeclAndParamList( - std::string const& _type, - DataType _dataType, - std::string& _varName, - std::string& _paramName -) -{ - auto varNames = newVarNames(getNextVarCounter()); - _varName = varNames.first; - _paramName = varNames.second; - - // Declare array - appendVarDeclToOutput(_type, _varName, getQualifier(_dataType)); - - // Add typed params for calling public and external functions with said type - appendTypedParams( - CalleeType::PUBLIC, - isValueType(_dataType), - _type, - _paramName, - ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) - ); - appendTypedParams( - CalleeType::EXTERNAL, - isValueType(_dataType), - _type, - _paramName, - ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) - ); -} - -void ProtoConverter::visitArrayType(std::string const& _baseType, ArrayType const& _x) -{ - std::string type = arrayTypeAsString(_baseType, _x); - std::string varName, paramName; - createDeclAndParamList(type, DataType::ARRAY, varName, paramName); - // Resize-initialize array and add checks - resizeInitArray(_x, _baseType, varName, paramName); -} - -void ProtoConverter::visitType( - DataType _dataType, - std::string const& _type, - std::string const& _value -) -{ - std::string varName, paramName; - createDeclAndParamList(_type, _dataType, varName, paramName); - addCheckedVarDef(_dataType, varName, paramName, _value); -} - -void ProtoConverter::appendVarDeclToOutput( - std::string const& _type, - std::string const& _varName, - std::string const& _qualifier +string ProtoConverter::appendVarDeclToOutput( + string const& _type, + string const& _varName, + string const& _qualifier ) { // One level of indentation for state variable declarations // Two levels of indentation for local variable declarations - m_output << Whiskers(R"( + return Whiskers(R"( <?isLocalVar> </isLocalVar><type><?qual> <qualifier></qual> <varName>;)" - ) + ) ("isLocalVar", !m_isStateVar) ("type", _type) ("qual", !_qualifier.empty()) ("qualifier", _qualifier) ("varName", _varName) - .render(); + .render() + + "\n"; } -void ProtoConverter::appendChecks( - DataType _type, - std::string const& _varName, - std::string const& _rhs +pair<string, string> ProtoConverter::visit(Type const& _type) +{ + switch (_type.type_oneof_case()) + { + case Type::kVtype: + return visit(_type.vtype()); + case Type::kNvtype: + return visit(_type.nvtype()); + case Type::TYPE_ONEOF_NOT_SET: + return make_pair("", ""); + } +} + +pair<string, string> ProtoConverter::visit(ValueType const& _type) +{ + switch (_type.value_type_oneof_case()) + { + case ValueType::kBoolty: + return visit(_type.boolty()); + case ValueType::kInty: + return visit(_type.inty()); + case ValueType::kByty: + return visit(_type.byty()); + case ValueType::kAdty: + return visit(_type.adty()); + case ValueType::VALUE_TYPE_ONEOF_NOT_SET: + return make_pair("", ""); + } +} + +pair<string, string> ProtoConverter::visit(NonValueType const& _type) +{ + switch (_type.nonvalue_type_oneof_case()) + { + case NonValueType::kDynbytearray: + return visit(_type.dynbytearray()); + case NonValueType::kArrtype: + if (ValidityVisitor().visit(_type.arrtype())) + return visit(_type.arrtype()); + else + return make_pair("", ""); + case NonValueType::kStype: + if (ValidityVisitor().visit(_type.stype())) + return visit(_type.stype()); + else + return make_pair("", ""); + case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET: + return make_pair("", ""); + } +} + +pair<string, string> ProtoConverter::visit(BoolType const& _type) +{ + return processType(_type, true); +} + +pair<string, string> ProtoConverter::visit(IntegerType const& _type) +{ + return processType(_type, true); +} + +pair<string, string> ProtoConverter::visit(FixedByteType const& _type) +{ + return processType(_type, true); +} + +pair<string, string> ProtoConverter::visit(AddressType const& _type) +{ + return processType(_type, true); +} + +pair<string, string> ProtoConverter::visit(DynamicByteArrayType const& _type) +{ + return processType(_type, false); +} + +pair<string, string> ProtoConverter::visit(ArrayType const& _type) +{ + return processType(_type, false); +} + +pair<string, string> ProtoConverter::visit(StructType const& _type) +{ + return processType(_type, false); +} + +template <typename T> +pair<string, string> ProtoConverter::processType(T const& _type, bool _isValueType) +{ + ostringstream local, global; + auto varNames = newVarNames(getNextVarCounter()); + string varName = varNames.first; + string paramName = varNames.second; + string location{}; + if (!m_isStateVar && !_isValueType) + location = "memory"; + + auto varDeclBuffers = varDecl( + varName, + paramName, + _type, + _isValueType, + location + ); + global << varDeclBuffers.first; + local << varDeclBuffers.second; + auto assignCheckBuffers = assignChecker(varName, paramName, _type); + global << assignCheckBuffers.first; + local << assignCheckBuffers.second; + + m_structCounter += m_numStructsAdded; + return make_pair(global.str(), local.str()); +} + +template <typename T> +pair<string, string> ProtoConverter::varDecl( + string const& _varName, + string const& _paramName, + T _type, + bool _isValueType, + string const& _location ) { - std::string check = {}; - switch (_type) - { - case DataType::STRING: - check = Whiskers(R"(!bytesCompare(bytes(<varName>), <value>))") - ("varName", _varName) - ("value", _rhs) - .render(); - break; - case DataType::BYTES: - check = Whiskers(R"(!bytesCompare(<varName>, <value>))") - ("varName", _varName) - ("value", _rhs) - .render(); - break; - case DataType::VALUE: - check = Whiskers(R"(<varName> != <value>)") - ("varName", _varName) - ("value", _rhs) - .render(); - break; - case DataType::ARRAY: - solUnimplemented("Proto ABIv2 fuzzer: Invalid data type."); - } + ostringstream local, global; - // Each (failing) check returns a unique value to simplify debugging. - m_checks << Whiskers(R"( - if (<check>) return <returnVal>;)" - ) - ("check", check) - ("returnVal", std::to_string(m_returnValue++)) - .render(); + TypeVisitor tVisitor(m_structCounter); + string typeStr = tVisitor.visit(_type); + if (typeStr.empty()) + return make_pair("", ""); + + // Append struct defs + global << tVisitor.structDef(); + m_numStructsAdded = tVisitor.numStructs(); + + // variable declaration + if (m_isStateVar) + global << appendVarDeclToOutput(typeStr, _varName, _location); + else + local << appendVarDeclToOutput(typeStr, _varName, _location); + + // Add typed params for calling public and external functions with said type + appendTypedParams( + CalleeType::PUBLIC, + _isValueType, + typeStr, + _paramName, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); + appendTypedParams( + CalleeType::EXTERNAL, + _isValueType, + typeStr, + _paramName, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); + + // Update dyn param only if necessary + if (tVisitor.isLastDynParamRightPadded()) + m_isLastDynParamRightPadded = true; + + return make_pair(global.str(), local.str()); } -void ProtoConverter::addVarDef(std::string const& _varName, std::string const& _rhs) +template <typename T> +pair<string, string> ProtoConverter::assignChecker( + string const& _varName, + string const& _paramName, + T _type +) { - std::string varDefString = Whiskers(R"( - <varName> = <rhs>;)" - ) - ("varName", _varName) - ("rhs", _rhs) - .render(); + ostringstream local; + AssignCheckVisitor acVisitor( + _varName, + _paramName, + m_returnValue, + m_isStateVar, + m_counter, + m_structCounter + ); + pair<string, string> assignCheckStrPair = acVisitor.visit(_type); + m_returnValue += acVisitor.errorStmts(); + m_counter += acVisitor.counted(); + + m_checks << assignCheckStrPair.second; // State variables cannot be assigned in contract-scope // Therefore, we buffer their assignments and // render them in function scope later. - if (m_isStateVar) - m_statebuffer << varDefString; - else - m_output << varDefString; + local << assignCheckStrPair.first; + return make_pair("", local.str()); } -void ProtoConverter::addCheckedVarDef( - DataType _type, - std::string const& _varName, - std::string const& _paramName, - std::string const& _rhs) +pair<string, string> ProtoConverter::visit(VarDecl const& _x) { - addVarDef(_varName, _rhs); - appendChecks(_type, _paramName, _rhs); -} - -// Runtime check for array length. -void ProtoConverter::checkResizeOp(std::string const& _paramName, unsigned _len) -{ - appendChecks(DataType::VALUE, _paramName + ".length", std::to_string(_len)); -} - -std::string ProtoConverter::boolValueAsString(unsigned _counter) -{ - return ((_counter % 2) ? "true" : "false"); -} - -/* Input(s) - * - Unsigned integer to be hashed - * - Width of desired uint value - * Processing - * - Take hash of first parameter and mask it with the max unsigned value for given bit width - * Output - * - string representation of uint value - */ -std::string ProtoConverter::uintValueAsString(unsigned _width, unsigned _counter) -{ - solAssert( - (_width % 8 == 0), - "Proto ABIv2 Fuzzer: Unsigned integer width is not a multiple of 8" - ); - return maskUnsignedIntToHex(_counter, _width/4); -} - -/* Input(s) - * - counter to be hashed to derive a value for Integer type - * - Width of desired int value - * Processing - * - Take hash of first parameter and mask it with the max signed value for given bit width - * Output - * - string representation of int value - */ -std::string ProtoConverter::intValueAsString(unsigned _width, unsigned _counter) -{ - solAssert( - (_width % 8 == 0), - "Proto ABIv2 Fuzzer: Signed integer width is not a multiple of 8" - ); - return maskUnsignedIntToHex(_counter, ((_width/4) - 1)); -} - -std::string ProtoConverter::addressValueAsString(unsigned _counter) -{ - return Whiskers(R"(address(<value>))") - ("value", uintValueAsString(160, _counter)) - .render(); -} - -std::string ProtoConverter::croppedString( - unsigned _numBytes, - unsigned _counter, - bool _isHexLiteral -) -{ - // _numBytes can not be zero or exceed 32 bytes - solAssert( - _numBytes > 0 && _numBytes <= 32, - "Proto ABIv2 fuzzer: Too short or too long a cropped string" - ); - - // Number of masked nibbles is twice the number of bytes for a - // hex literal of _numBytes bytes. For a string literal, each nibble - // is treated as a character. - unsigned numMaskNibbles = _isHexLiteral ? _numBytes * 2 : _numBytes; - - // Start position of substring equals totalHexStringLength - numMaskNibbles - // totalHexStringLength = 64 + 2 = 66 - // e.g., 0x12345678901234567890123456789012 is a total of 66 characters - // |---------------------^-----------| - // <--- start position---><--numMask-> - // <-----------total length ---------> - // Note: This assumes that maskUnsignedIntToHex() invokes toHex(..., HexPrefix::Add) - unsigned startPos = 66 - numMaskNibbles; - // Extracts the least significant numMaskNibbles from the result - // of maskUnsignedIntToHex(). - return maskUnsignedIntToHex( - _counter, - numMaskNibbles - ).substr(startPos, numMaskNibbles); -} - -std::string ProtoConverter::hexValueAsString( - unsigned _numBytes, - unsigned _counter, - bool _isHexLiteral, - bool _decorate -) -{ - solAssert(_numBytes > 0 && _numBytes <= 32, - "Proto ABIv2 fuzzer: Invalid hex length" - ); - - // If _decorate is set, then we return a hex"" or a "" string. - if (_numBytes == 0) - return Whiskers(R"(<?decorate><?isHex>hex</isHex>""</decorate>)") - ("decorate", _decorate) - ("isHex", _isHexLiteral) - .render(); - - // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address - // payable type. - return Whiskers(R"(<?decorate><?isHex>hex</isHex>"</decorate><value><?decorate>"</decorate>)") - ("decorate", _decorate) - ("isHex", _isHexLiteral) - ("value", croppedString(_numBytes, _counter, _isHexLiteral)) - .render(); -} - -std::string ProtoConverter::variableLengthValueAsString( - unsigned _numBytes, - unsigned _counter, - bool _isHexLiteral -) -{ - solAssert(_numBytes >= 0 && _numBytes <= s_maxDynArrayLength, - "Proto ABIv2 fuzzer: Invalid hex length" - ); - if (_numBytes == 0) - return Whiskers(R"(<?isHex>hex</isHex>"")") - ("isHex", _isHexLiteral) - .render(); - - unsigned numBytesRemaining = _numBytes; - // Stores the literal - string output{}; - // If requested value is shorter than or exactly 32 bytes, - // the literal is the return value of hexValueAsString. - if (numBytesRemaining <= 32) - output = hexValueAsString( - numBytesRemaining, - _counter, - _isHexLiteral, - /*decorate=*/false - ); - // If requested value is longer than 32 bytes, the literal - // is obtained by duplicating the return value of hexValueAsString - // until we reach a value of the requested size. - else - { - // Create a 32-byte value to be duplicated and - // update number of bytes to be appended. - // Stores the cached literal that saves us - // (expensive) calls to keccak256. - string cachedString = hexValueAsString( - /*numBytes=*/32, - _counter, - _isHexLiteral, - /*decorate=*/false - ); - output = cachedString; - numBytesRemaining -= 32; - - // Append bytes from cachedString until - // we create a value of desired length. - unsigned numAppendedBytes; - while (numBytesRemaining > 0) - { - // We append at most 32 bytes at a time - numAppendedBytes = numBytesRemaining >= 32 ? 32 : numBytesRemaining; - output += cachedString.substr( - 0, - // Double the substring length for hex literals since each - // character is actually half a byte (or a nibble). - _isHexLiteral ? numAppendedBytes * 2 : numAppendedBytes - ); - numBytesRemaining -= numAppendedBytes; - } - solAssert( - numBytesRemaining == 0, - "Proto ABIv2 fuzzer: Logic flaw in variable literal creation" - ); - } - - if (_isHexLiteral) - solAssert( - output.size() == 2 * _numBytes, - "Proto ABIv2 fuzzer: Generated hex literal is of incorrect length" - ); - else - solAssert( - output.size() == _numBytes, - "Proto ABIv2 fuzzer: Generated string literal is of incorrect length" - ); - - // Decorate output - return Whiskers(R"(<?isHexLiteral>hex</isHexLiteral>"<value>")") - ("isHexLiteral", _isHexLiteral) - ("value", output) - .render(); -} - -std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) -{ - solAssert( - (_width >= 1 && _width <= 32), - "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" - ); - return hexValueAsString(_width, _counter, /*isHexLiteral=*/true); -} - -std::string ProtoConverter::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) -{ - if (_sign) - return intValueAsString(_width, _counter); - else - return uintValueAsString(_width, _counter); -} - -std::string ProtoConverter::bytesArrayTypeAsString(DynamicByteArrayType const& _x) -{ - switch (_x.type()) - { - case DynamicByteArrayType::BYTES: - return "bytes"; - case DynamicByteArrayType::STRING: - return "string"; - } -} - -std::string ProtoConverter::structTypeAsString(StructType const&) -{ - // TODO: Implement this - return {}; -} - -void ProtoConverter::visit(BoolType const&) -{ - visitType( - DataType::VALUE, - getBoolTypeAsString(), - boolValueAsString(getNextCounter()) - ); -} - -void ProtoConverter::visit(IntegerType const& _x) -{ - visitType( - DataType::VALUE, - getIntTypeAsString(_x), - integerValueAsString(isIntSigned(_x), getIntWidth(_x), getNextCounter()) - ); -} - -void ProtoConverter::visit(AddressType const& _x) -{ - visitType( - DataType::VALUE, - getAddressTypeAsString(_x), - addressValueAsString(getNextCounter()) - ); -} - -void ProtoConverter::visit(FixedByteType const& _x) -{ - visitType( - DataType::VALUE, - getFixedByteTypeAsString(_x), - fixedByteValueAsString(getFixedByteWidth(_x), getNextCounter()) - ); -} - -void ProtoConverter::visit(ValueType const& _x) -{ - switch (_x.value_type_oneof_case()) - { - case ValueType::kInty: - visit(_x.inty()); - break; - case ValueType::kByty: - visit(_x.byty()); - break; - case ValueType::kAdty: - visit(_x.adty()); - break; - case ValueType::kBoolty: - visit(_x.boolty()); - break; - case ValueType::VALUE_TYPE_ONEOF_NOT_SET: - break; - } -} - -void ProtoConverter::visit(DynamicByteArrayType const& _x) -{ - bool isBytes = _x.type() == DynamicByteArrayType::BYTES; - visitType( - isBytes ? DataType::BYTES : DataType::STRING, - bytesArrayTypeAsString(_x), - bytesArrayValueAsString( - getNextCounter(), - isBytes - ) - ); - // Update right padding of type - m_isLastDynParamRightPadded = true; -} - -// TODO: Implement struct visitor -void ProtoConverter::visit(StructType const&) -{ -} - -std::string ProtoConverter::arrayDimInfoAsString(ArrayDimensionInfo const& _x) -{ - return Whiskers(R"([<?isStatic><length></isStatic>])") - ("isStatic", _x.is_static()) - ("length", std::to_string(getStaticArrayLengthFromFuzz(_x.length()))) - .render(); -} - -void ProtoConverter::arrayDimensionsAsStringVector( - ArrayType const& _x, - std::vector<std::string>& _vecOfStr) -{ - solAssert(_x.info_size() > 0, "Proto ABIv2 Fuzzer: Array dimensions empty."); - for (auto const& dim: _x.info()) - _vecOfStr.push_back(arrayDimInfoAsString(dim)); -} - -ProtoConverter::VecOfBoolUnsigned ProtoConverter::arrayDimensionsAsPairVector( - ArrayType const& _x -) -{ - VecOfBoolUnsigned arrayDimsPairVector = {}; - for (auto const& dim: _x.info()) - arrayDimsPairVector.push_back(arrayDimInfoAsPair(dim)); - solAssert(!arrayDimsPairVector.empty(), "Proto ABIv2 Fuzzer: Array dimensions empty."); - return arrayDimsPairVector; -} - -std::string ProtoConverter::getValueByBaseType(ArrayType const& _x) -{ - switch (_x.base_type_oneof_case()) - { - case ArrayType::kInty: - return integerValueAsString(isIntSigned(_x.inty()), getIntWidth(_x.inty()), getNextCounter()); - case ArrayType::kByty: - return fixedByteValueAsString(getFixedByteWidth(_x.byty()), getNextCounter()); - case ArrayType::kAdty: - return addressValueAsString(getNextCounter()); - case ArrayType::kBoolty: - return boolValueAsString(getNextCounter()); - case ArrayType::kDynbytesty: - return bytesArrayValueAsString( - getNextCounter(), - _x.dynbytesty().type() == DynamicByteArrayType::BYTES - ); - // TODO: Implement structs. - case ArrayType::kStty: - case ArrayType::BASE_TYPE_ONEOF_NOT_SET: - solAssert(false, "Proto ABIv2 fuzzer: Invalid array base type"); - } -} - -ProtoConverter::DataType ProtoConverter::getDataTypeByBaseType(ArrayType const& _x) -{ - switch (_x.base_type_oneof_case()) - { - case ArrayType::kInty: - case ArrayType::kByty: - case ArrayType::kAdty: - case ArrayType::kBoolty: - return DataType::VALUE; - case ArrayType::kDynbytesty: - return getDataTypeOfDynBytesType(_x.dynbytesty()); - case ArrayType::kStty: - case ArrayType::BASE_TYPE_ONEOF_NOT_SET: - solUnimplemented("Proto ABIv2 fuzzer: Invalid array base type"); - } -} - -// Adds a resize operation for a given dimension of type `_type` and expression referenced -// by `_var`. `_isStatic` is true for statically sized dimensions, false otherwise. -// `_arrayLen` is equal to length of statically sized array dimension. For dynamically -// sized dimension, we use `getDynArrayLengthFromFuzz()` and a monotonically increasing -// counter to obtain actual length. Function returns dimension length. -unsigned ProtoConverter::resizeDimension( - bool _isStatic, - unsigned _arrayLen, - std::string const& _var, - std::string const& _param, - std::string const& _type -) -{ - unsigned length; - if (_isStatic) - length = _arrayLen; - else - { - length = getDynArrayLengthFromFuzz(_arrayLen, getNextCounter()); - - // If local var, new T(l); - // Else, l; - std::string lhs, rhs; - if (m_isStateVar) - { - lhs = _var + ".length"; - rhs = Whiskers(R"(<length>)") - ("length", std::to_string(length)) - .render(); - } - else - { - lhs = _var; - rhs = Whiskers(R"(new <type>(<length>))") - ("type", _type) - ("length", std::to_string(length)) - .render(); - } - // If local var, x = new T(l); - // Else, x.length = l; - addVarDef(lhs, rhs); - } - - // if (c.length != l) - checkResizeOp(_param, length); - return length; -} - -void ProtoConverter::resizeHelper( - ArrayType const& _x, - std::vector<std::string> _arrStrVec, - VecOfBoolUnsigned _arrInfoVec, - std::string const& _varName, - std::string const& _paramName -) -{ - // Initialize value expressions if we have arrived at leaf node, - // (depth-first) recurse otherwise. - if (_arrInfoVec.empty()) - { - // expression name is _var - // value is a value of base type - std::string value = getValueByBaseType(_x); - // add assignment and check - DataType dataType = getDataTypeByBaseType(_x); - addCheckedVarDef(dataType, _varName, _paramName, value); - } - else - { - auto& dim = _arrInfoVec.back(); - - std::string type = std::accumulate( - _arrStrVec.begin(), - _arrStrVec.end(), - std::string("") - ); - unsigned length = resizeDimension(dim.first, dim.second, _varName, _paramName, type); - // Recurse one level dimension down. - _arrStrVec.pop_back(); - _arrInfoVec.pop_back(); - for (unsigned i = 0; i < length; i++) - resizeHelper( - _x, - _arrStrVec, - _arrInfoVec, - _varName + "[" + std::to_string(i) + "]", - _paramName + "[" + std::to_string(i) + "]" - ); - } -} - -// This function takes care of properly resizing and initializing ArrayType. -// In parallel, it adds runtime checks on array bound and values. -void ProtoConverter::resizeInitArray( - ArrayType const& _x, - std::string const& _baseType, - std::string const& _varName, - std::string const& _paramName -) -{ - VecOfBoolUnsigned arrInfoVec = arrayDimensionsAsPairVector(_x); - std::vector<std::string> arrStrVec = {_baseType}; - arrayDimensionsAsStringVector(_x, arrStrVec); - resizeHelper(_x, arrStrVec, arrInfoVec, _varName, _paramName); -} - -// Returns array type from it's base type (e.g., int8) and array dimensions info contained in -// ArrayType. -std::string ProtoConverter::arrayTypeAsString(std::string const& _baseType, ArrayType const& _x) -{ - std::vector<std::string> typeStringVec = {_baseType}; - arrayDimensionsAsStringVector(_x, typeStringVec); - - return std::accumulate( - typeStringVec.begin(), - typeStringVec.end(), - std::string("") - ); -} - -void ProtoConverter::visit(ArrayType const& _x) -{ - // Bail out if input contains too few or too many dimensions. - if (_x.info_size() == 0 || _x.info_size() > (int)s_maxArrayDimensions) - return; - - // Array type is dynamically encoded if one of the following is true - // - array base type is "bytes" or "string" - // - at least one array dimension is dynamically sized. - if (_x.base_type_oneof_case() == ArrayType::kDynbytesty) - m_isLastDynParamRightPadded = true; - else - for (auto const& dim: _x.info()) - if (!dim.is_static()) - { - m_isLastDynParamRightPadded = true; - break; - } - - string baseType = {}; - switch (_x.base_type_oneof_case()) - { - case ArrayType::kInty: - baseType = getIntTypeAsString(_x.inty()); - break; - case ArrayType::kByty: - baseType = getFixedByteTypeAsString(_x.byty()); - break; - case ArrayType::kAdty: - baseType = getAddressTypeAsString(_x.adty()); - break; - case ArrayType::kBoolty: - baseType = getBoolTypeAsString(); - break; - case ArrayType::kDynbytesty: - baseType = bytesArrayTypeAsString(_x.dynbytesty()); - break; - case ArrayType::kStty: - case ArrayType::BASE_TYPE_ONEOF_NOT_SET: - return; - } - visitArrayType(baseType, _x); -} - -void ProtoConverter::visit(NonValueType const& _x) -{ - switch (_x.nonvalue_type_oneof_case()) - { - case NonValueType::kDynbytearray: - visit(_x.dynbytearray()); - break; - case NonValueType::kArrtype: - visit(_x.arrtype()); - break; - case NonValueType::kStype: - visit(_x.stype()); - break; - case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET: - break; - } -} - -void ProtoConverter::visit(Type const& _x) -{ - switch (_x.type_oneof_case()) - { - case Type::kVtype: - visit(_x.vtype()); - break; - case Type::kNvtype: - visit(_x.nvtype()); - break; - case Type::TYPE_ONEOF_NOT_SET: - break; - } -} - -void ProtoConverter::visit(VarDecl const& _x) -{ - visit(_x.type()); + return visit(_x.type()); } std::string ProtoConverter::equalityChecksAsString() @@ -821,20 +313,34 @@ std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType) } /// Test function to be called externally. -void ProtoConverter::visit(TestFunction const& _x) +string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs) { - m_output << R"( - - function test() public returns (uint) { - )"; - - // Define state variables in function scope - m_output << m_statebuffer.str(); - // TODO: Support more than one but less than N local variables - visit(_x.local_vars()); + auto localVarBuffers = visit(_x.local_vars()); - m_output << Whiskers(R"( + string structTypeDecl = localVarBuffers.first; + string localVarDefs = localVarBuffers.second; + + ostringstream testBuffer; + string functionDecl = "function test() public returns (uint)"; + testBuffer << Whiskers(R"(<structTypeDecl> + <functionDecl> { +<storageVarDefs> +<localVarDefs> +<testCode> + })") + ("structTypeDecl", structTypeDecl) + ("functionDecl", functionDecl) + ("storageVarDefs", _storageVarDefs) + ("localVarDefs", localVarDefs) + ("testCode", testCode(_x.invalid_encoding_length())) + .render(); + return testBuffer.str(); +} + +string ProtoConverter::testCode(unsigned _invalidLength) +{ + return Whiskers(R"( uint returnVal = this.coder_public(<parameterNames>); if (returnVal != 0) return returnVal; @@ -865,18 +371,18 @@ void ProtoConverter::visit(TestFunction const& _x) return uint(200000) + returnVal; </atLeastOneVar> return 0; - } - )") - ("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) - ("invalidLengthFuzz", std::to_string(_x.invalid_encoding_length())) - ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") - ("atLeastOneVar", m_varCounter > 0) - .render(); + )") + ("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) + ("invalidLengthFuzz", std::to_string(_invalidLength)) + ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") + ("atLeastOneVar", m_varCounter > 0) + .render(); } -void ProtoConverter::writeHelperFunctions() +string ProtoConverter::helperFunctions() { - m_output << R"( + stringstream helperFuncs; + helperFuncs << R"( function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { if(a.length != b.length) return false; @@ -954,14 +460,14 @@ void ProtoConverter::writeHelperFunctions() // These are callee functions that encode from storage, decode to // memory/calldata and check if decoded value matches storage value // return true on successful match, false otherwise - m_output << Whiskers(R"( + helperFuncs << Whiskers(R"( function coder_public(<parameters_memory>) public pure returns (uint) { - <equality_checks> +<equality_checks> return 0; } function coder_external(<parameters_calldata>) external pure returns (uint) { - <equality_checks> +<equality_checks> return 0; } )") @@ -969,26 +475,610 @@ void ProtoConverter::writeHelperFunctions() ("equality_checks", equalityChecksAsString()) ("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL)) .render(); + return helperFuncs.str(); } void ProtoConverter::visit(Contract const& _x) { - m_output << R"(pragma solidity >=0.0; -pragma experimental ABIEncoderV2; + string pragmas = R"(pragma solidity >=0.0; +pragma experimental ABIEncoderV2;)"; -contract C { -)"; // TODO: Support more than one but less than N state variables - visit(_x.state_vars()); + auto storageBuffers = visit(_x.state_vars()); + string storageVarDecls = storageBuffers.first; + string storageVarDefs = storageBuffers.second; m_isStateVar = false; - // Test function - visit(_x.testfunction()); - writeHelperFunctions(); - m_output << "\n}"; + string testFunction = visit(_x.testfunction(), storageVarDefs); + /* Structure of contract body + * - Storage variable declarations + * - Struct type declarations + * - Test function + * - Storage variable definitions + * - Local variable definitions + * - Test code proper (calls public and external functions) + * - Helper functions + */ + ostringstream contractBody; + contractBody << storageVarDecls + << testFunction + << helperFunctions(); + m_output << Whiskers(R"(<pragmas> +<contractStart> +<contractBody> +<contractEnd>)") + ("pragmas", pragmas) + ("contractStart", "contract C {") + ("contractBody", contractBody.str()) + ("contractEnd", "}") + .render(); } string ProtoConverter::contractToString(Contract const& _input) { visit(_input); return m_output.str(); +} + +/// Type visitor +string TypeVisitor::visit(BoolType const&) +{ + m_baseType = "bool"; + return m_baseType; +} + +string TypeVisitor::visit(IntegerType const& _type) +{ + m_baseType = getIntTypeAsString(_type); + return m_baseType; +} + +string TypeVisitor::visit(FixedByteType const& _type) +{ + m_baseType = getFixedByteTypeAsString(_type); + return m_baseType; +} + +string TypeVisitor::visit(AddressType const& _type) +{ + m_baseType = getAddressTypeAsString(_type); + return m_baseType; +} + +string TypeVisitor::visit(ArrayType const& _type) +{ + if (!ValidityVisitor().visit(_type)) + return ""; + string baseType = visit(_type.t()); + solAssert(!baseType.empty(), ""); + string arrayBraces = _type.is_static() ? + string("[") + + to_string(getStaticArrayLengthFromFuzz(_type.length())) + + string("]") : + string("[]"); + m_baseType += arrayBraces; + if (!_type.is_static()) + m_isLastDynParamRightPadded = true; + return baseType + arrayBraces; +} + +string TypeVisitor::visit(DynamicByteArrayType const& _type) +{ + m_isLastDynParamRightPadded = true; + m_baseType = bytesArrayTypeAsString(_type); + return m_baseType; +} + +void TypeVisitor::structDefinition(StructType const& _type) +{ + // Return an empty string if struct is empty + solAssert(ValidityVisitor().visit(_type), ""); + + // Reset field counter and indentation + unsigned wasFieldCounter = m_structFieldCounter; + unsigned wasIndentation = m_indentation; + + m_indentation = 1; + m_structFieldCounter = 0; + + // Commence struct declaration + string structDef = lineString( + "struct " + + string(s_structNamePrefix) + + to_string(m_structCounter) + + " {" + ); + + // Increase indentation for struct fields + m_indentation++; + for (auto const& t: _type.t()) + { + string type{}; + + if (!ValidityVisitor().visit(t)) + continue; + + TypeVisitor tVisitor(m_structCounter + 1); + type = tVisitor.visit(t); + m_structCounter += tVisitor.numStructs(); + m_structDef << tVisitor.structDef(); + + solAssert(!type.empty(), ""); + + structDef += lineString( + Whiskers(R"(<type> <member>;)") + ("type", type) + ("member", "m" + to_string(m_structFieldCounter++)) + .render() + ); + } + m_indentation--; + structDef += lineString("}"); + m_structCounter++; + m_structDef << structDef; + m_indentation = wasIndentation; + m_structFieldCounter = wasFieldCounter; +} + +string TypeVisitor::visit(StructType const& _type) +{ + if (ValidityVisitor().visit(_type)) + { + // Add struct definition + structDefinition(_type); + // Set last dyn param if struct contains a dyn param e.g., bytes, array etc. + m_isLastDynParamRightPadded = DynParamVisitor().visit(_type); + // If top-level struct is a non-emtpy struct, assign the name S<suffix> + m_baseType = s_structTypeName + to_string(m_structStartCounter); + } + else + m_baseType = {}; + + return m_baseType; +} + +/// AssignCheckVisitor implementation +pair<string, string> AssignCheckVisitor::visit(BoolType const& _type) +{ + string value = ValueGetterVisitor(counter()).visit(_type); + return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); +} + +pair<string, string> AssignCheckVisitor::visit(IntegerType const& _type) +{ + string value = ValueGetterVisitor(counter()).visit(_type); + return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); +} + +pair<string, string> AssignCheckVisitor::visit(FixedByteType const& _type) +{ + string value = ValueGetterVisitor(counter()).visit(_type); + return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); +} + +pair<string, string> AssignCheckVisitor::visit(AddressType const& _type) +{ + string value = ValueGetterVisitor(counter()).visit(_type); + return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); +} + +pair<string, string> AssignCheckVisitor::visit(DynamicByteArrayType const& _type) +{ + string value = ValueGetterVisitor(counter()).visit(_type); + DataType dataType = _type.type() == DynamicByteArrayType::BYTES ? DataType::BYTES : DataType::STRING; + return assignAndCheckStringPair(m_varName, m_paramName, value, value, dataType); +} + +pair<string, string> AssignCheckVisitor::visit(ArrayType const& _type) +{ + if (!ValidityVisitor().visit(_type)) + return make_pair("", ""); + + // Obtain type of array to be resized and initialized + string typeStr{}; + + unsigned wasStructCounter = m_structCounter; + TypeVisitor tVisitor(m_structCounter); + typeStr = tVisitor.visit(_type); + + pair<string, string> resizeBuffer; + string lengthStr; + unsigned length; + + // Resize dynamic arrays + if (!_type.is_static()) + { + length = getDynArrayLengthFromFuzz(_type.length(), counter()); + lengthStr = to_string(length); + if (m_stateVar) + resizeBuffer = assignAndCheckStringPair( + m_varName + ".length", + m_paramName + ".length", + lengthStr, + lengthStr, + DataType::VALUE + ); + else + { + // Resizing memory arrays via the new operator + string resizeOp = Whiskers(R"(new <fullTypeStr>(<length>))") + ("fullTypeStr", typeStr) + ("length", lengthStr) + .render(); + resizeBuffer = assignAndCheckStringPair( + m_varName, + m_paramName + ".length", + resizeOp, + lengthStr, + DataType::VALUE + ); + } + } + else + { + length = getStaticArrayLengthFromFuzz(_type.length()); + lengthStr = to_string(length); + // Add check on length + resizeBuffer.second = checkString(m_paramName + ".length", lengthStr, DataType::VALUE); + } + + // Add assignCheckBuffer and check statements + pair<string, string> assignCheckBuffer; + string wasVarName = m_varName; + string wasParamName = m_paramName; + for (unsigned i = 0; i < length; i++) + { + m_varName = wasVarName + "[" + to_string(i) + "]"; + m_paramName = wasParamName + "[" + to_string(i) + "]"; + pair<string, string> assign = visit(_type.t()); + assignCheckBuffer.first += assign.first; + assignCheckBuffer.second += assign.second; + if (i < length - 1) + m_structCounter = wasStructCounter; + } + + // Since struct visitor won't be called for zero-length + // arrays, struct counter will not get incremented. Therefore, + // we need to manually force a recursive struct visit. + if (length == 0 && TypeVisitor().arrayOfStruct(_type)) + visit(_type.t()); + + m_varName = wasVarName; + m_paramName = wasParamName; + + // Compose resize and initialization assignment and check + return make_pair( + resizeBuffer.first + assignCheckBuffer.first, + resizeBuffer.second + assignCheckBuffer.second + ); +} + +pair<string, string> AssignCheckVisitor::visit(StructType const& _type) +{ + if (!ValidityVisitor().visit(_type)) + return make_pair("", ""); + + pair<string, string> assignCheckBuffer; + unsigned i = 0; + + // Increment struct counter + m_structCounter++; + + string wasVarName = m_varName; + string wasParamName = m_paramName; + + for (auto const& t: _type.t()) + { + m_varName = wasVarName + ".m" + to_string(i); + m_paramName = wasParamName + ".m" + to_string(i); + pair<string, string> assign = visit(t); + // If type is not well formed continue without + // updating state. + if (assign.first.empty() && assign.second.empty()) + continue; + assignCheckBuffer.first += assign.first; + assignCheckBuffer.second += assign.second; + i++; + } + m_varName = wasVarName; + m_paramName = wasParamName; + return assignCheckBuffer; +} + +pair<string, string> AssignCheckVisitor::assignAndCheckStringPair( + string const& _varRef, + string const& _checkRef, + string const& _assignValue, + string const& _checkValue, + DataType _type +) +{ + return make_pair(assignString(_varRef, _assignValue), checkString(_checkRef, _checkValue, _type)); +} + +string AssignCheckVisitor::assignString(string const& _ref, string const& _value) +{ + string assignStmt = Whiskers(R"(<ref> = <value>;)") + ("ref", _ref) + ("value", _value) + .render(); + return indentation() + assignStmt + "\n"; +} + +string AssignCheckVisitor::checkString(string const& _ref, string const& _value, DataType _type) +{ + string checkPred; + switch (_type) + { + case DataType::STRING: + checkPred = Whiskers(R"(!bytesCompare(bytes(<varName>), <value>))") + ("varName", _ref) + ("value", _value) + .render(); + break; + case DataType::BYTES: + checkPred = Whiskers(R"(!bytesCompare(<varName>, <value>))") + ("varName", _ref) + ("value", _value) + .render(); + break; + case DataType::VALUE: + checkPred = Whiskers(R"(<varName> != <value>)") + ("varName", _ref) + ("value", _value) + .render(); + break; + case DataType::ARRAY: + solUnimplemented("Proto ABIv2 fuzzer: Invalid data type."); + } + string checkStmt = Whiskers(R"(if (<checkPred>) return <errCode>;)") + ("checkPred", checkPred) + ("errCode", to_string(m_errorCode++)) + .render(); + return indentation() + checkStmt + "\n"; +} + +/// ValueGetterVisitor +string ValueGetterVisitor::visit(BoolType const&) +{ + return counter() % 2 ? "true" : "false"; +} + +string ValueGetterVisitor::visit(IntegerType const& _type) +{ + return integerValueAsString( + _type.is_signed(), + getIntWidth(_type), + counter() + ); +} + +string ValueGetterVisitor::visit(FixedByteType const& _type) +{ + return fixedByteValueAsString( + getFixedByteWidth(_type), + counter() + ); +} + +string ValueGetterVisitor::visit(AddressType const&) +{ + return addressValueAsString(counter()); +} + +string ValueGetterVisitor::visit(DynamicByteArrayType const& _type) +{ + return bytesArrayValueAsString( + counter(), + getDataTypeOfDynBytesType(_type) == DataType::BYTES + ); +} + +std::string ValueGetterVisitor::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) +{ + if (_sign) + return intValueAsString(_width, _counter); + else + return uintValueAsString(_width, _counter); +} + +/* Input(s) + * - Unsigned integer to be hashed + * - Width of desired uint value + * Processing + * - Take hash of first parameter and mask it with the max unsigned value for given bit width + * Output + * - string representation of uint value + */ +std::string ValueGetterVisitor::uintValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width % 8 == 0), + "Proto ABIv2 Fuzzer: Unsigned integer width is not a multiple of 8" + ); + return maskUnsignedIntToHex(_counter, _width/4); +} + +/* Input(s) + * - counter to be hashed to derive a value for Integer type + * - Width of desired int value + * Processing + * - Take hash of first parameter and mask it with the max signed value for given bit width + * Output + * - string representation of int value + */ +std::string ValueGetterVisitor::intValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width % 8 == 0), + "Proto ABIv2 Fuzzer: Signed integer width is not a multiple of 8" + ); + return maskUnsignedIntToHex(_counter, ((_width/4) - 1)); +} + +std::string ValueGetterVisitor::croppedString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral +) +{ + solAssert( + _numBytes > 0 && _numBytes <= 32, + "Proto ABIv2 fuzzer: Too short or too long a cropped string" + ); + + // Number of masked nibbles is twice the number of bytes for a + // hex literal of _numBytes bytes. For a string literal, each nibble + // is treated as a character. + unsigned numMaskNibbles = _isHexLiteral ? _numBytes * 2 : _numBytes; + + // Start position of substring equals totalHexStringLength - numMaskNibbles + // totalHexStringLength = 64 + 2 = 66 + // e.g., 0x12345678901234567890123456789012 is a total of 66 characters + // |---------------------^-----------| + // <--- start position---><--numMask-> + // <-----------total length ---------> + // Note: This assumes that maskUnsignedIntToHex() invokes toHex(..., HexPrefix::Add) + unsigned startPos = 66 - numMaskNibbles; + // Extracts the least significant numMaskNibbles from the result + // of maskUnsignedIntToHex(). + return maskUnsignedIntToHex( + _counter, + numMaskNibbles + ).substr(startPos, numMaskNibbles); +} + +std::string ValueGetterVisitor::hexValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral, + bool _decorate +) +{ + solAssert(_numBytes > 0 && _numBytes <= 32, + "Proto ABIv2 fuzzer: Invalid hex length" + ); + + // If _decorate is set, then we return a hex"" or a "" string. + if (_numBytes == 0) + return Whiskers(R"(<?decorate><?isHex>hex</isHex>""</decorate>)") + ("decorate", _decorate) + ("isHex", _isHexLiteral) + .render(); + + // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address + // payable type. + return Whiskers(R"(<?decorate><?isHex>hex</isHex>"</decorate><value><?decorate>"</decorate>)") + ("decorate", _decorate) + ("isHex", _isHexLiteral) + ("value", croppedString(_numBytes, _counter, _isHexLiteral)) + .render(); +} + +std::string ValueGetterVisitor::fixedByteValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width >= 1 && _width <= 32), + "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" + ); + return hexValueAsString(_width, _counter, /*isHexLiteral=*/true); +} + +std::string ValueGetterVisitor::addressValueAsString(unsigned _counter) +{ + return Whiskers(R"(address(<value>))") + ("value", uintValueAsString(160, _counter)) + .render(); +} + +std::string ValueGetterVisitor::variableLengthValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral +) +{ + // TODO: Move this to caller +// solAssert(_numBytes >= 0 && _numBytes <= s_maxDynArrayLength, +// "Proto ABIv2 fuzzer: Invalid hex length" +// ); + if (_numBytes == 0) + return Whiskers(R"(<?isHex>hex</isHex>"")") + ("isHex", _isHexLiteral) + .render(); + + unsigned numBytesRemaining = _numBytes; + // Stores the literal + string output{}; + // If requested value is shorter than or exactly 32 bytes, + // the literal is the return value of hexValueAsString. + if (numBytesRemaining <= 32) + output = hexValueAsString( + numBytesRemaining, + _counter, + _isHexLiteral, + /*decorate=*/false + ); + // If requested value is longer than 32 bytes, the literal + // is obtained by duplicating the return value of hexValueAsString + // until we reach a value of the requested size. + else + { + // Create a 32-byte value to be duplicated and + // update number of bytes to be appended. + // Stores the cached literal that saves us + // (expensive) calls to keccak256. + string cachedString = hexValueAsString( + /*numBytes=*/32, + _counter, + _isHexLiteral, + /*decorate=*/false + ); + output = cachedString; + numBytesRemaining -= 32; + + // Append bytes from cachedString until + // we create a value of desired length. + unsigned numAppendedBytes; + while (numBytesRemaining > 0) + { + // We append at most 32 bytes at a time + numAppendedBytes = numBytesRemaining >= 32 ? 32 : numBytesRemaining; + output += cachedString.substr( + 0, + // Double the substring length for hex literals since each + // character is actually half a byte (or a nibble). + _isHexLiteral ? numAppendedBytes * 2 : numAppendedBytes + ); + numBytesRemaining -= numAppendedBytes; + } + solAssert( + numBytesRemaining == 0, + "Proto ABIv2 fuzzer: Logic flaw in variable literal creation" + ); + } + + if (_isHexLiteral) + solAssert( + output.size() == 2 * _numBytes, + "Proto ABIv2 fuzzer: Generated hex literal is of incorrect length" + ); + else + solAssert( + output.size() == _numBytes, + "Proto ABIv2 fuzzer: Generated string literal is of incorrect length" + ); + + // Decorate output + return Whiskers(R"(<?isHexLiteral>hex</isHexLiteral>"<value>")") + ("isHexLiteral", _isHexLiteral) + ("value", output) + .render(); +} + +string ValueGetterVisitor::bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) +{ + return variableLengthValueAsString( + getVarLength(_counter), + _counter, + _isHexLiteral + ); } \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index 401867364..a04e36af2 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -1,10 +1,18 @@ #pragma once +#include <test/tools/ossfuzz/abiV2Proto.pb.h> + #include <libdevcore/FixedHash.h> #include <libdevcore/Keccak256.h> +#include <libdevcore/StringUtils.h> #include <libdevcore/Whiskers.h> -#include <test/tools/ossfuzz/abiV2Proto.pb.h> + +#include <liblangutil/Exceptions.h> + +#include <boost/variant/static_visitor.hpp> #include <boost/algorithm/string.hpp> +#include <boost/variant.hpp> + #include <ostream> #include <sstream> @@ -89,32 +97,29 @@ */ -namespace dev -{ -namespace test -{ -namespace abiv2fuzzer -{ +namespace dev { +namespace test { +namespace abiv2fuzzer { + +/// Converts a protobuf input into a Solidity program that tests +/// abi coding. class ProtoConverter { public: - ProtoConverter(): + ProtoConverter() : m_isStateVar(true), m_counter(0), m_varCounter(0), m_returnValue(1), - m_isLastDynParamRightPadded(false) + m_isLastDynParamRightPadded(false), + m_structCounter(0), + m_numStructsAdded(0) {} ProtoConverter(ProtoConverter const&) = delete; - ProtoConverter(ProtoConverter&&) = delete; - std::string contractToString(Contract const& _input); - private: - using VecOfBoolUnsigned = std::vector<std::pair<bool, unsigned>>; - enum class Delimiter { ADD, @@ -125,79 +130,40 @@ private: PUBLIC, EXTERNAL }; - enum class DataType - { - BYTES, - STRING, - VALUE, - ARRAY - }; - - void visit(BoolType const&); - - void visit(IntegerType const&); - - void visit(FixedByteType const&); - - void visit(AddressType const&); - - void visit(ArrayType const&); - - void visit(DynamicByteArrayType const&); - - void visit(StructType const&); - - void visit(ValueType const&); - - void visit(NonValueType const&); - - void visit(Type const&); - - void visit(VarDecl const&); - - void visit(TestFunction const&); - + std::pair<std::string, std::string> visit(VarDecl const&); + std::string visit(TestFunction const&, std::string const&); void visit(Contract const&); - std::string getValueByBaseType(ArrayType const&); + template <typename T> + std::pair<std::string, std::string> processType(T const& _type, bool _isValueType); - DataType getDataTypeByBaseType(ArrayType const& _x); - - void resizeInitArray( - ArrayType const& _x, - std::string const& _baseType, - std::string const& _varName, - std::string const& _paramName - ); - - unsigned resizeDimension( - bool _isStatic, - unsigned _arrayLen, - std::string const& _var, - std::string const& _param, - std::string const& _type - ); - - void resizeHelper( - ArrayType const& _x, - std::vector<std::string> _arrStrVec, - VecOfBoolUnsigned _arrInfoVec, - std::string const& _varName, - std::string const& _paramName - ); - - // Utility functions - void appendChecks(DataType _type, std::string const& _varName, std::string const& _rhs); - - void addVarDef(std::string const& _varName, std::string const& _rhs); - - void addCheckedVarDef( - DataType _type, + template <typename T> + std::pair<std::string, std::string> assignChecker( std::string const& _varName, std::string const& _paramName, - std::string const& _rhs + T _type ); + template <typename T> + std::pair<std::string, std::string> varDecl( + std::string const& _varName, + std::string const& _paramName, + T _type, + bool _isValueType, + std::string const& _location + ); + std::pair<std::string, std::string> visit(Type const&); + std::pair<std::string, std::string> visit(ValueType const&); + std::pair<std::string, std::string> visit(NonValueType const&); + std::pair<std::string, std::string> visit(BoolType const&); + std::pair<std::string, std::string> visit(IntegerType const&); + std::pair<std::string, std::string> visit(FixedByteType const&); + std::pair<std::string, std::string> visit(AddressType const&); + std::pair<std::string, std::string> visit(DynamicByteArrayType const&); + std::pair<std::string, std::string> visit(ArrayType const&); + std::pair<std::string, std::string> visit(StructType const&); + + // Utility functions void appendTypedParams( CalleeType _calleeType, bool _isValueType, @@ -220,38 +186,23 @@ private: Delimiter _delimiter = Delimiter::ADD ); - void appendVarDeclToOutput( + std::string appendVarDeclToOutput( std::string const& _type, std::string const& _varName, std::string const& _qualifier ); - void checkResizeOp(std::string const& _varName, unsigned _len); - - void visitType(DataType _dataType, std::string const& _type, std::string const& _value); - - void visitArrayType(std::string const&, ArrayType const&); - - void createDeclAndParamList( - std::string const& _type, - DataType _dataType, - std::string& _varName, - std::string& _paramName - ); - std::string equalityChecksAsString(); std::string typedParametersAsString(CalleeType _calleeType); - void writeHelperFunctions(); + std::string helperFunctions(); + + std::string testCode(unsigned _invalidLength); + + std::pair<std::string, unsigned> structDecl(StructType const& _type); // Function definitions - // m_counter is used to derive values for typed variables - unsigned getNextCounter() - { - return m_counter++; - } - // m_varCounter is used to derive declared and parameterized variable names unsigned getNextVarCounter() { @@ -261,7 +212,7 @@ private: // Accepts an unsigned counter and returns a pair of strings // First string is declared name (s_varNamePrefix<varcounter_value>) // Second string is parameterized name (s_paramPrefix<varcounter_value>) - auto newVarNames(unsigned _varCounter) + static std::pair<std::string, std::string> newVarNames(unsigned _varCounter) { return std::make_pair( s_varNamePrefix + std::to_string(_varCounter), @@ -269,62 +220,79 @@ private: ); } - std::string getQualifier(DataType _dataType) - { - return ((isValueType(_dataType) || m_isStateVar) ? "" : "memory"); - } - bool isLastDynParamRightPadded() { return m_isLastDynParamRightPadded; } - // Static declarations - static std::string structTypeAsString(StructType const& _x); - static std::string boolValueAsString(unsigned _counter); - static std::string intValueAsString(unsigned _width, unsigned _counter); - static std::string uintValueAsString(unsigned _width, unsigned _counter); - static std::string integerValueAsString(bool _sign, unsigned _width, unsigned _counter); - static std::string addressValueAsString(unsigned _counter); - static std::string fixedByteValueAsString(unsigned _width, unsigned _counter); - - /// Returns a hex literal if _isHexLiteral is true, a string literal otherwise. - /// The size of the returned literal is _numBytes bytes. - /// @param _decorate If true, the returned string is enclosed within double quotes - /// if _isHexLiteral is false. - /// @param _isHexLiteral If true, the returned string is enclosed within - /// double quotes prefixed by the string "hex" if _decorate is true. If - /// _decorate is false, the returned string is returned as-is. - /// @return hex value as string - static std::string hexValueAsString( - unsigned _numBytes, - unsigned _counter, - bool _isHexLiteral, - bool _decorate = true - ); - - /// Concatenates the hash value obtained from monotonically increasing counter - /// until the desired number of bytes determined by _numBytes. - /// @param _width Desired number of bytes for hex value - /// @param _counter A counter value used for creating a keccak256 hash - /// @param _isHexLiteral Since this routine may be used to construct - /// string or hex literals, this flag is used to construct a valid output. - /// @return Valid hex or string literal of size _width bytes - static std::string variableLengthValueAsString( - unsigned _width, - unsigned _counter, - bool _isHexLiteral - ); - static std::vector<std::pair<bool, unsigned>> arrayDimensionsAsPairVector(ArrayType const& _x); - static std::string arrayDimInfoAsString(ArrayDimensionInfo const& _x); - static void arrayDimensionsAsStringVector( - ArrayType const& _x, - std::vector<std::string>& - ); - static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x); - static std::string arrayTypeAsString(std::string const&, ArrayType const&); static std::string delimiterToString(Delimiter _delimiter); - static std::string croppedString(unsigned _numBytes, unsigned _counter, bool _isHexLiteral); + + /// Contains the test program + std::ostringstream m_output; + /// Contains a subset of the test program. This subset contains + /// checks to be encoded in the test program + std::ostringstream m_checks; + /// Contains typed parameter list to be passed to callee functions + std::ostringstream m_typedParamsExternal; + std::ostringstream m_typedParamsPublic; + /// Predicate that is true if we are in contract scope + bool m_isStateVar; + unsigned m_counter; + unsigned m_varCounter; + /// Monotonically increasing return value for error reporting + unsigned m_returnValue; + /// Flag that indicates if last dynamically encoded parameter + /// passed to a function call is of a type that is going to be + /// right padded by the ABI encoder. + bool m_isLastDynParamRightPadded; + /// Struct counter + unsigned m_structCounter; + unsigned m_numStructsAdded; + /// Prefixes for declared and parameterized variable names + static auto constexpr s_varNamePrefix = "x_"; + static auto constexpr s_paramNamePrefix = "c_"; +}; + +/// Visitor interface for Solidity protobuf types. +template <typename T> +class AbiV2ProtoVisitor +{ +public: + virtual ~AbiV2ProtoVisitor() = default; + + virtual T visit(BoolType const& _node) = 0; + virtual T visit(IntegerType const& _node) = 0; + virtual T visit(FixedByteType const& _node) = 0; + virtual T visit(AddressType const& _node) = 0; + virtual T visit(DynamicByteArrayType const& _node) = 0; + virtual T visit(ArrayType const& _node) = 0; + virtual T visit(StructType const& _node) = 0; + virtual T visit(ValueType const& _node) + { + return visitValueType(_node); + } + virtual T visit(NonValueType const& _node) + { + return visitNonValueType(_node); + } + virtual T visit(Type const& _node) + { + return visitType(_node); + } + + enum class DataType + { + BYTES, + STRING, + VALUE, + ARRAY + }; + + /// Prefixes for declared and parameterized variable names + static auto constexpr s_varNamePrefix = "x_"; + static auto constexpr s_paramNamePrefix = "c_"; + static auto constexpr s_structNamePrefix = "S"; + static auto constexpr s_structFieldPrefix = "m"; // Static function definitions static bool isValueType(DataType _dataType) @@ -342,11 +310,6 @@ private: return _x.is_signed(); } - static std::string getBoolTypeAsString() - { - return "bool"; - } - static std::string getIntTypeAsString(IntegerType const& _x) { return ((isIntSigned(_x) ? "int" : "uint") + std::to_string(getIntWidth(_x))); @@ -364,7 +327,7 @@ private: static std::string getAddressTypeAsString(AddressType const& _x) { - return (_x.payable() ? "address payable": "address"); + return (_x.payable() ? "address payable" : "address"); } static DataType getDataTypeOfDynBytesType(DynamicByteArrayType const& _x) @@ -375,12 +338,6 @@ private: return DataType::BYTES; } - /// Returns true if input is either a string or bytes, false otherwise. - static bool isDataTypeBytesOrString(DataType _type) - { - return _type == DataType::STRING || _type == DataType::BYTES; - } - // Convert _counter to string and return its keccak256 hash static u256 hashUnsignedInt(unsigned _counter) { @@ -389,7 +346,7 @@ private: static u256 maskUnsignedInt(unsigned _counter, unsigned _numMaskNibbles) { - return hashUnsignedInt(_counter) & u256("0x" + std::string(_numMaskNibbles, 'f')); + return hashUnsignedInt(_counter) & u256("0x" + std::string(_numMaskNibbles, 'f')); } // Requires caller to pass number of nibbles (twice the number of bytes) as second argument. @@ -414,15 +371,6 @@ private: return _fuzz % s_maxArrayLength + 1; } - static std::pair<bool, unsigned> arrayDimInfoAsPair(ArrayDimensionInfo const& _x) - { - return ( - _x.is_static() ? - std::make_pair(true, getStaticArrayLengthFromFuzz(_x.length())) : - std::make_pair(false, getDynArrayLengthFromFuzz(_x.length(), 0)) - ); - } - /// Returns a pseudo-random value for the size of a string/hex /// literal. Used for creating variable length hex/string literals. /// @param _counter Monotonically increasing counter value @@ -435,48 +383,400 @@ private: return (_counter + 879) * 32 % (s_maxDynArrayLength + 1); } + static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x) + { + switch (_x.type()) { + case DynamicByteArrayType::BYTES: + return "bytes"; + case DynamicByteArrayType::STRING: + return "string"; + } + } +protected: +T visitValueType(ValueType const& _type) +{ + switch (_type.value_type_oneof_case()) + { + case ValueType::kInty: + return visit(_type.inty()); + case ValueType::kByty: + return visit(_type.byty()); + case ValueType::kAdty: + return visit(_type.adty()); + case ValueType::kBoolty: + return visit(_type.boolty()); + case ValueType::VALUE_TYPE_ONEOF_NOT_SET: + return T(); + } +} + +T visitNonValueType(NonValueType const& _type) +{ + switch (_type.nonvalue_type_oneof_case()) + { + case NonValueType::kDynbytearray: + return visit(_type.dynbytearray()); + case NonValueType::kArrtype: + return visit(_type.arrtype()); + case NonValueType::kStype: + return visit(_type.stype()); + case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET: + return T(); + } +} + +T visitType(Type const& _type) +{ + switch (_type.type_oneof_case()) + { + case Type::kVtype: + return visit(_type.vtype()); + case Type::kNvtype: + return visit(_type.nvtype()); + case Type::TYPE_ONEOF_NOT_SET: + return T(); + } +} +private: + static unsigned constexpr s_maxArrayLength = 4; + static unsigned constexpr s_maxDynArrayLength = 256; +}; + +/// Converts a protobuf type into a Solidity type string. +class TypeVisitor: public AbiV2ProtoVisitor<std::string> +{ +public: + TypeVisitor(unsigned _structSuffix = 0): + m_indentation(1), + m_structCounter(_structSuffix), + m_structStartCounter(_structSuffix), + m_structFieldCounter(0), + m_isLastDynParamRightPadded(false) + {} + + std::string visit(BoolType const&) override; + std::string visit(IntegerType const&) override; + std::string visit(FixedByteType const&) override; + std::string visit(AddressType const&) override; + std::string visit(ArrayType const&) override; + std::string visit(DynamicByteArrayType const&) override; + std::string visit(StructType const&) override; + using AbiV2ProtoVisitor<std::string>::visit; + std::string baseType() + { + return m_baseType; + } + bool isLastDynParamRightPadded() + { + return m_isLastDynParamRightPadded; + } + std::string structDef() + { + return m_structDef.str(); + } + unsigned numStructs() + { + return m_structCounter - m_structStartCounter; + } + static bool arrayOfStruct(ArrayType const& _type) + { + Type const& baseType = _type.t(); + if (baseType.has_nvtype() && baseType.nvtype().has_stype()) + return true; + else if (baseType.has_nvtype() && baseType.nvtype().has_arrtype()) + return arrayOfStruct(baseType.nvtype().arrtype()); + else + return false; + } +private: + void structDefinition(StructType const&); + + std::string indentation() + { + return std::string(m_indentation * 1, '\t'); + } + std::string lineString(std::string const& _line) + { + return indentation() + _line + "\n"; + } + + std::string m_baseType; + std::ostringstream m_structDef; + unsigned m_indentation; + unsigned m_structCounter; + unsigned m_structStartCounter; + unsigned m_structFieldCounter; + bool m_isLastDynParamRightPadded; + + static auto constexpr s_structTypeName = "S"; +}; + +/// Returns a pair of strings, first of which contains assignment statements +/// to initialize a given type, and second of which contains checks to be +/// placed inside the coder function to test abi en/decoding. +class AssignCheckVisitor: public AbiV2ProtoVisitor<std::pair<std::string, std::string>> +{ +public: + AssignCheckVisitor( + std::string _varName, + std::string _paramName, + unsigned _errorStart, + bool _stateVar, + unsigned _counter, + unsigned _structCounter + ) + { + m_counter = m_counterStart = _counter; + m_varName = _varName; + m_paramName = _paramName; + m_errorCode = m_errorStart = _errorStart; + m_indentation = 2; + m_stateVar = _stateVar; + m_structCounter = m_structStart = _structCounter; + m_arrayOfStruct = false; + } + std::pair<std::string, std::string> visit(BoolType const&) override; + std::pair<std::string, std::string> visit(IntegerType const&) override; + std::pair<std::string, std::string> visit(FixedByteType const&) override; + std::pair<std::string, std::string> visit(AddressType const&) override; + std::pair<std::string, std::string> visit(ArrayType const&) override; + std::pair<std::string, std::string> visit(DynamicByteArrayType const&) override; + std::pair<std::string, std::string> visit(StructType const&) override; + using AbiV2ProtoVisitor<std::pair<std::string, std::string>>::visit; + + unsigned errorStmts() + { + return m_errorCode - m_errorStart; + } + + unsigned counted() + { + return m_counter - m_counterStart; + } + + unsigned structs() + { + return m_structCounter - m_structStart; + } +private: + std::string indentation() + { + return std::string(m_indentation * 1, '\t'); + } + unsigned counter() + { + return m_counter++; + } + + std::pair<std::string, std::string> assignAndCheckStringPair( + std::string const& _varRef, + std::string const& _checkRef, + std::string const& _assignValue, + std::string const& _checkValue, + DataType _type + ); + std::string assignString(std::string const&, std::string const&); + std::string checkString(std::string const&, std::string const&, DataType); + unsigned m_counter; + unsigned m_counterStart; + std::string m_varName; + std::string m_paramName; + unsigned m_errorCode; + unsigned m_errorStart; + unsigned m_indentation; + bool m_stateVar; + unsigned m_structCounter; + unsigned m_structStart; + bool m_arrayOfStruct; +}; + +/// Returns a valid value (as a string) for a given type. +class ValueGetterVisitor: AbiV2ProtoVisitor<std::string> +{ +public: + ValueGetterVisitor(unsigned _counter = 0): m_counter(_counter) {} + + std::string visit(BoolType const&) override; + std::string visit(IntegerType const&) override; + std::string visit(FixedByteType const&) override; + std::string visit(AddressType const&) override; + std::string visit(DynamicByteArrayType const&) override; + std::string visit(ArrayType const&) override + { + solAssert(false, "ABIv2 proto fuzzer: Cannot call valuegettervisitor on complex type"); + } + std::string visit(StructType const&) override + { + solAssert(false, "ABIv2 proto fuzzer: Cannot call valuegettervisitor on complex type"); + } + using AbiV2ProtoVisitor<std::string>::visit; +private: + unsigned counter() + { + return m_counter++; + } + + static std::string intValueAsString(unsigned _width, unsigned _counter); + static std::string uintValueAsString(unsigned _width, unsigned _counter); + static std::string integerValueAsString(bool _sign, unsigned _width, unsigned _counter); + static std::string addressValueAsString(unsigned _counter); + static std::string fixedByteValueAsString(unsigned _width, unsigned _counter); + + /// Returns a hex literal if _isHexLiteral is true, a string literal otherwise. + /// The size of the returned literal is _numBytes bytes. + /// @param _decorate If true, the returned string is enclosed within double quotes + /// if _isHexLiteral is false. + /// @param _isHexLiteral If true, the returned string is enclosed within + /// double quotes prefixed by the string "hex" if _decorate is true. If + /// _decorate is false, the returned string is returned as-is. + /// @return hex value as string + static std::string hexValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral, + bool _decorate = true + ); + /// Returns a hex/string literal of variable length whose value and /// size are pseudo-randomly determined from the counter value. /// @param _counter A monotonically increasing counter value /// @param _isHexLiteral Flag that indicates whether hex (if true) or /// string literal (false) is desired /// @return A variable length hex/string value - static std::string bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) + static std::string bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral); + + /// Concatenates the hash value obtained from monotonically increasing counter + /// until the desired number of bytes determined by _numBytes. + /// @param _width Desired number of bytes for hex value + /// @param _counter A counter value used for creating a keccak256 hash + /// @param _isHexLiteral Since this routine may be used to construct + /// string or hex literals, this flag is used to construct a valid output. + /// @return Valid hex or string literal of size _width bytes + static std::string variableLengthValueAsString( + unsigned _width, + unsigned _counter, + bool _isHexLiteral + ); + + /// Returns a value that is @a _numBytes bytes long. + /// @param _numBytes: Number of bytes of desired value + /// @param _counter: A counter value + /// @param _isHexLiteral: True if desired value is a hex literal, false otherwise + static std::string croppedString(unsigned _numBytes, unsigned _counter, bool _isHexLiteral); + + unsigned m_counter; +}; + +/// Returns true if protobuf array specification is well-formed, false otherwise +class ValidityVisitor: AbiV2ProtoVisitor<bool> +{ +public: + ValidityVisitor() = default; + + bool visit(BoolType const&) override { - return variableLengthValueAsString( - getVarLength(_counter), - _counter, - _isHexLiteral - ); + return true; } - /// Contains the test program - std::ostringstream m_output; - /// Temporary storage for state variable definitions - std::ostringstream m_statebuffer; - /// Contains a subset of the test program. This subset contains - /// checks to be encoded in the test program - std::ostringstream m_checks; - /// Contains typed parameter list to be passed to callee functions - std::ostringstream m_typedParamsExternal; - std::ostringstream m_typedParamsPublic; - /// Predicate that is true if we are in contract scope - bool m_isStateVar; - unsigned m_counter; - unsigned m_varCounter; - /// Monotonically increasing return value for error reporting - unsigned m_returnValue; - /// Flag that indicates if last dynamically encoded parameter - /// passed to a function call is of a type that is going to be - /// right padded by the ABI encoder. - bool m_isLastDynParamRightPadded; - static unsigned constexpr s_maxArrayLength = 4; - static unsigned constexpr s_maxArrayDimensions = 4; - static unsigned constexpr s_maxDynArrayLength = 256; - /// Prefixes for declared and parameterized variable names - static auto constexpr s_varNamePrefix = "x_"; - static auto constexpr s_paramNamePrefix = "c_"; + bool visit(IntegerType const&) override + { + return true; + } + + bool visit(FixedByteType const&) override + { + return true; + } + + bool visit(AddressType const&) override + { + return true; + } + + bool visit(DynamicByteArrayType const&) override + { + return true; + } + + bool visit(ArrayType const& _type) override + { + return visit(_type.t()); + } + + bool visit(StructType const& _type) override + { + for (auto const& t: _type.t()) + if (visit(t)) + return true; + return false; + } + + using AbiV2ProtoVisitor<bool>::visit; +}; + +/// Returns true if visited type is dynamically encoded by the abi coder, +/// false otherwise. +class DynParamVisitor: AbiV2ProtoVisitor<bool> +{ +public: + DynParamVisitor() = default; + + bool visit(BoolType const&) override + { + return false; + } + + bool visit(IntegerType const&) override + { + return false; + } + + bool visit(FixedByteType const&) override + { + return false; + } + + bool visit(AddressType const&) override + { + return false; + } + + bool visit(DynamicByteArrayType const&) override + { + return true; + } + + bool visit(ArrayType const& _type) override + { + // Return early if array spec is not well-formed + if (!ValidityVisitor().visit(_type)) + return false; + + // Array is dynamically encoded if it at least one of the following is true + // - at least one dimension is dynamically sized + // - base type is dynamically encoded + if (!_type.is_static()) + return true; + else + return visit(_type.t()); + } + + bool visit(StructType const& _type) override + { + // Return early if empty struct + if (!ValidityVisitor().visit(_type)) + return false; + + // Struct is dynamically encoded if at least one of its fields + // is dynamically encoded. + for (auto const& t: _type.t()) + if (visit(t)) + return true; + return false; + } + + using AbiV2ProtoVisitor<bool>::visit; }; } } -} +} \ No newline at end of file From 12ed08eddb7610409d5c470a5b4c64598b4a0a36 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry <bhargava.shastry@ethereum.org> Date: Sun, 6 Oct 2019 09:02:27 +0200 Subject: [PATCH 2/3] Place upper bound number on number of array dimensions --- test/tools/ossfuzz/protoToAbiV2.cpp | 1 + test/tools/ossfuzz/protoToAbiV2.h | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index aa35c80c8..01af91cde 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -548,6 +548,7 @@ string TypeVisitor::visit(ArrayType const& _type) { if (!ValidityVisitor().visit(_type)) return ""; + string baseType = visit(_type.t()); solAssert(!baseType.empty(), ""); string arrayBraces = _type.is_static() ? diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index a04e36af2..9b6349ea4 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -258,6 +258,7 @@ template <typename T> class AbiV2ProtoVisitor { public: + static unsigned constexpr s_maxArrayDimensions = 3; virtual ~AbiV2ProtoVisitor() = default; virtual T visit(BoolType const& _node) = 0; @@ -533,7 +534,6 @@ public: m_indentation = 2; m_stateVar = _stateVar; m_structCounter = m_structStart = _structCounter; - m_arrayOfStruct = false; } std::pair<std::string, std::string> visit(BoolType const&) override; std::pair<std::string, std::string> visit(IntegerType const&) override; @@ -587,7 +587,6 @@ private: bool m_stateVar; unsigned m_structCounter; unsigned m_structStart; - bool m_arrayOfStruct; }; /// Returns a valid value (as a string) for a given type. @@ -671,7 +670,7 @@ private: class ValidityVisitor: AbiV2ProtoVisitor<bool> { public: - ValidityVisitor() = default; + ValidityVisitor(): m_arrayDimensions(0) {} bool visit(BoolType const&) override { @@ -700,6 +699,9 @@ public: bool visit(ArrayType const& _type) override { + m_arrayDimensions++; + if (m_arrayDimensions > s_maxArrayDimensions) + return false; return visit(_type.t()); } @@ -711,6 +713,7 @@ public: return false; } + unsigned m_arrayDimensions; using AbiV2ProtoVisitor<bool>::visit; }; From 04becb94586e5f02e264f3e16387db62c8d2bb82 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry <bhargava.shastry@ethereum.org> Date: Tue, 8 Oct 2019 02:09:59 +0200 Subject: [PATCH 3/3] Minor refactoring --- test/tools/ossfuzz/protoToAbiV2.cpp | 27 ++--- test/tools/ossfuzz/protoToAbiV2.h | 149 +++++++++++++++++++++------- 2 files changed, 127 insertions(+), 49 deletions(-) diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index 01af91cde..9415bdcd4 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -4,7 +4,7 @@ using namespace std; using namespace dev; using namespace dev::test::abiv2fuzzer; -string ProtoConverter::appendVarDeclToOutput( +string ProtoConverter::getVarDecl( string const& _type, string const& _varName, string const& _qualifier @@ -114,9 +114,7 @@ template <typename T> pair<string, string> ProtoConverter::processType(T const& _type, bool _isValueType) { ostringstream local, global; - auto varNames = newVarNames(getNextVarCounter()); - string varName = varNames.first; - string paramName = varNames.second; + auto [varName, paramName] = newVarNames(getNextVarCounter()); string location{}; if (!m_isStateVar && !_isValueType) location = "memory"; @@ -160,9 +158,9 @@ pair<string, string> ProtoConverter::varDecl( // variable declaration if (m_isStateVar) - global << appendVarDeclToOutput(typeStr, _varName, _location); + global << getVarDecl(typeStr, _varName, _location); else - local << appendVarDeclToOutput(typeStr, _varName, _location); + local << getVarDecl(typeStr, _varName, _location); // Add typed params for calling public and external functions with said type appendTypedParams( @@ -312,7 +310,7 @@ std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType) } } -/// Test function to be called externally. +// Test function to be called externally. string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs) { // TODO: Support more than one but less than N local variables @@ -491,10 +489,10 @@ pragma experimental ABIEncoderV2;)"; string testFunction = visit(_x.testfunction(), storageVarDefs); /* Structure of contract body * - Storage variable declarations - * - Struct type declarations + * - Struct definitions * - Test function - * - Storage variable definitions - * - Local variable definitions + * - Storage variable assignments + * - Local variable definitions and assignments * - Test code proper (calls public and external functions) * - Helper functions */ @@ -557,8 +555,13 @@ string TypeVisitor::visit(ArrayType const& _type) string("]") : string("[]"); m_baseType += arrayBraces; - if (!_type.is_static()) - m_isLastDynParamRightPadded = true; + + // If we don't know yet if the array will be dynamically encoded, + // check again. If we already know that it will be, there's no + // need to do anything. + if (!m_isLastDynParamRightPadded) + m_isLastDynParamRightPadded = DynParamVisitor().visit(_type); + return baseType + arrayBraces; } diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index 9b6349ea4..bd20b1628 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -97,16 +97,18 @@ */ -namespace dev { -namespace test { -namespace abiv2fuzzer { - +namespace dev +{ +namespace test +{ +namespace abiv2fuzzer +{ /// Converts a protobuf input into a Solidity program that tests /// abi coding. class ProtoConverter { public: - ProtoConverter() : + ProtoConverter(): m_isStateVar(true), m_counter(0), m_varCounter(0), @@ -125,33 +127,31 @@ private: ADD, SKIP }; + /// Enum of possible function types that decode abi + /// encoded parameters. enum class CalleeType { PUBLIC, EXTERNAL }; - std::pair<std::string, std::string> visit(VarDecl const&); - std::string visit(TestFunction const&, std::string const&); + + /// Visitors for various Protobuf types + /// Visit top-level contract specification void visit(Contract const&); - template <typename T> - std::pair<std::string, std::string> processType(T const& _type, bool _isValueType); + /// Convert test function specification into Solidity test + /// function + /// @param _testSpec: Protobuf test function specification + /// @param _storageDefs: String containing Solidity assignment + /// statements to be placed inside the scope of the test function. + std::string visit(TestFunction const& _testSpec, std::string const& _storageDefs); - template <typename T> - std::pair<std::string, std::string> assignChecker( - std::string const& _varName, - std::string const& _paramName, - T _type - ); - - template <typename T> - std::pair<std::string, std::string> varDecl( - std::string const& _varName, - std::string const& _paramName, - T _type, - bool _isValueType, - std::string const& _location - ); + /// Visitors for the remaining protobuf types. They convert + /// the input protobuf specification type into Solidity code. + /// @return A pair of strings, first of which contains Solidity + /// code to be placed inside contract scope, second of which contains + /// Solidity code to be placed inside test function scope. + std::pair<std::string, std::string> visit(VarDecl const&); std::pair<std::string, std::string> visit(Type const&); std::pair<std::string, std::string> visit(ValueType const&); std::pair<std::string, std::string> visit(NonValueType const&); @@ -163,7 +163,59 @@ private: std::pair<std::string, std::string> visit(ArrayType const&); std::pair<std::string, std::string> visit(StructType const&); - // Utility functions + /// Convert a protobuf type @a _T into Solidity code to be placed + /// inside contract and test function scopes. + /// @param: _type (of parameterized type protobuf type T) is the type + /// of protobuf input to be converted. + /// @param: _isValueType is true if _type is a Solidity value type e.g., uint + /// and false otherwise e.g., string + /// @return: A pair of strings, first of which contains Solidity + /// code to be placed inside contract scope, second of which contains + /// Solidity code to be placed inside test function scope. + template <typename T> + std::pair<std::string, std::string> processType(T const& _type, bool _isValueType); + + /// Convert a protobuf type @a _T into Solidity variable assignment and check + /// statements to be placed inside contract and test function scopes. + /// @param: _varName is the name of the Solidity variable + /// @param: _paramName is the name of the Solidity parameter that is passed + /// to the test function + /// @param: _type (of parameterized type protobuf type T) is the type + /// of protobuf input to be converted. + /// @return: A pair of strings, first of which contains Solidity + /// statements to be placed inside contract scope, second of which contains + /// Solidity statements to be placed inside test function scope. + template <typename T> + std::pair<std::string, std::string> assignChecker( + std::string const& _varName, + std::string const& _paramName, + T _type + ); + + /// Convert a protobuf type @a _T into Solidity variable declaration statement. + /// @param: _varName is the name of the Solidity variable + /// @param: _paramName is the name of the Solidity parameter that is passed + /// to the test function + /// @param: _type (of parameterized type protobuf type T) is the type + /// of protobuf input to be converted. + /// @param: _isValueType is a boolean that is true if _type is a + /// Solidity value type e.g., uint and false otherwise e.g., string + /// @param: _location is the Solidity location qualifier string to + /// be used inside variable declaration statements + /// @return: A pair of strings, first of which contains Solidity + /// variable declaration statement to be placed inside contract scope, + /// second of which contains Solidity variable declaration statement + /// to be placed inside test function scope. + template <typename T> + std::pair<std::string, std::string> varDecl( + std::string const& _varName, + std::string const& _paramName, + T _type, + bool _isValueType, + std::string const& _location + ); + + /// Appends a function parameter to the function parameter stream. void appendTypedParams( CalleeType _calleeType, bool _isValueType, @@ -172,6 +224,8 @@ private: Delimiter _delimiter ); + /// Appends a function parameter to the public test function's + /// parameter stream. void appendTypedParamsPublic( bool _isValueType, std::string const& _typeString, @@ -179,6 +233,8 @@ private: Delimiter _delimiter = Delimiter::ADD ); + /// Appends a function parameter to the external test function's + /// parameter stream. void appendTypedParamsExternal( bool _isValueType, std::string const& _typeString, @@ -186,32 +242,41 @@ private: Delimiter _delimiter = Delimiter::ADD ); - std::string appendVarDeclToOutput( + /// Returns a Solidity variable declaration statement + /// @param _type: string containing Solidity type of the + /// variable to be declared. + /// @param _varName: string containing Solidity variable + /// name + /// @param _qualifier: string containing location where + /// the variable will be placed + std::string getVarDecl( std::string const& _type, std::string const& _varName, std::string const& _qualifier ); + /// Return checks that are encoded as Solidity if statements + /// as string std::string equalityChecksAsString(); + /// Return comma separated typed function parameters as string std::string typedParametersAsString(CalleeType _calleeType); + /// Return Solidity helper functions as string std::string helperFunctions(); + /// Return top-level test code as string std::string testCode(unsigned _invalidLength); - std::pair<std::string, unsigned> structDecl(StructType const& _type); - - // Function definitions - // m_varCounter is used to derive declared and parameterized variable names + /// Return the next variable count that is used for + /// variable naming. unsigned getNextVarCounter() { return m_varCounter++; } - // Accepts an unsigned counter and returns a pair of strings - // First string is declared name (s_varNamePrefix<varcounter_value>) - // Second string is parameterized name (s_paramPrefix<varcounter_value>) + /// Return a pair of names for Solidity variable and the same variable when + /// passed as a function parameter. static std::pair<std::string, std::string> newVarNames(unsigned _varCounter) { return std::make_pair( @@ -220,11 +285,14 @@ private: ); } + /// Checks if the last dynamically encoded Solidity type is right + /// padded, returning true if it is and false otherwise. bool isLastDynParamRightPadded() { return m_isLastDynParamRightPadded; } + /// Convert delimter to a comma or null string. static std::string delimiterToString(Delimiter _delimiter); /// Contains the test program @@ -290,10 +358,7 @@ public: }; /// Prefixes for declared and parameterized variable names - static auto constexpr s_varNamePrefix = "x_"; - static auto constexpr s_paramNamePrefix = "c_"; static auto constexpr s_structNamePrefix = "S"; - static auto constexpr s_structFieldPrefix = "m"; // Static function definitions static bool isValueType(DataType _dataType) @@ -386,7 +451,8 @@ public: static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x) { - switch (_x.type()) { + switch (_x.type()) + { case DynamicByteArrayType::BYTES: return "bytes"; case DynamicByteArrayType::STRING: @@ -699,6 +765,12 @@ public: bool visit(ArrayType const& _type) override { + // Mark array type as invalid in one of the following is true + // - contains more than s_maxArrayDimensions dimensions + // - contains an invalid base type, which happens in the + // following cases + // - array base type is invalid + // - array base type is empty m_arrayDimensions++; if (m_arrayDimensions > s_maxArrayDimensions) return false; @@ -707,6 +779,9 @@ public: bool visit(StructType const& _type) override { + // A struct is marked invalid only if all of its fields + // are invalid. This is done to prevent an empty struct + // being defined (which is a Solidity error). for (auto const& t: _type.t()) if (visit(t)) return true;