diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 265533397..5a9eb9428 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -35,6 +35,7 @@ if (OSSFUZZ) solc_ossfuzz.cpp ../fuzzer_common.cpp ../../TestCaseReader.cpp + LiteralGeneratorUtil.cpp SolidityGenerator.cpp SolidityCustomMutatorInterface.cpp ) diff --git a/test/tools/ossfuzz/LiteralGeneratorUtil.cpp b/test/tools/ossfuzz/LiteralGeneratorUtil.cpp new file mode 100644 index 000000000..3ba632b98 --- /dev/null +++ b/test/tools/ossfuzz/LiteralGeneratorUtil.cpp @@ -0,0 +1,74 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +using namespace solidity::test::fuzzer; +using namespace std; + +template +V LiteralGeneratorUtil::integerValue(size_t _counter) +{ + V value = V( + u256(solidity::util::keccak256(solidity::util::h256(_counter))) % + u256(boost::math::tools::max_value()) + ); + if (boost::multiprecision::is_signed_number::value && value % 2 == 0) + return value * (-1); + else + return value; +} + +string LiteralGeneratorUtil::integerValue(size_t _counter, size_t _intWidth, bool _signed) +{ + if (_signed) + return signedIntegerValue(_counter, _intWidth); + else + return unsignedIntegerValue(_counter, _intWidth); +} + +string LiteralGeneratorUtil::fixedBytes(size_t _numBytes, size_t _counter, bool _isHexLiteral) +{ + solAssert( + _numBytes > 0 && _numBytes <= 32, + "Literal Generator: 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. + size_t 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) + size_t startPos = 66 - numMaskNibbles; + // Extracts the least significant numMaskNibbles from the result + // of maskUnsignedIntToHex(). + return solidity::util::toHex( + u256(solidity::util::keccak256(solidity::util::h256(_counter))) & + u256("0x" + std::string(numMaskNibbles, 'f')), + solidity::util::HexPrefix::Add + ).substr(startPos, numMaskNibbles); +} diff --git a/test/tools/ossfuzz/LiteralGeneratorUtil.h b/test/tools/ossfuzz/LiteralGeneratorUtil.h new file mode 100644 index 000000000..516b4a629 --- /dev/null +++ b/test/tools/ossfuzz/LiteralGeneratorUtil.h @@ -0,0 +1,101 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include + +#include + +/// Convenience macros +/// Returns a valid Solidity integer width w such that 8 <= w <= 256. +#define INTWIDTH(z, n, _ununsed) BOOST_PP_MUL(BOOST_PP_ADD(n, 1), 8) +/// Using declaration that aliases long boost multiprecision types with +/// s(u) where is a valid Solidity integer width and "s" +/// stands for "signed" and "u" for "unsigned". +#define USINGDECL(z, n, sign) \ + using BOOST_PP_CAT(BOOST_PP_IF(sign, s, u), INTWIDTH(z, n,)) = \ + boost::multiprecision::number< \ + boost::multiprecision::cpp_int_backend< \ + INTWIDTH(z, n,), \ + INTWIDTH(z, n,), \ + BOOST_PP_IF( \ + sign, \ + boost::multiprecision::signed_magnitude, \ + boost::multiprecision::unsigned_magnitude \ + ), \ + boost::multiprecision::unchecked, \ + void \ + > \ + >; +/// Instantiate the using declarations for signed and unsigned integer types. +BOOST_PP_REPEAT(32, USINGDECL, 1) +BOOST_PP_REPEAT(32, USINGDECL, 0) +/// Case implementation that returns an integer value of the specified type. +/// For signed integers, we divide by two because the range for boost multiprecision +/// types is double that of Solidity integer types. Example, 8-bit signed boost +/// number range is [-255, 255] but Solidity `int8` range is [-128, 127] +#define CASEIMPL(z, n, sign) \ + case INTWIDTH(z, n,): \ + stream << BOOST_PP_IF( \ + sign, \ + integerValue< \ + BOOST_PP_CAT( \ + BOOST_PP_IF(sign, s, u), \ + INTWIDTH(z, n,) \ + )>(_counter) / 2, \ + integerValue< \ + BOOST_PP_CAT( \ + BOOST_PP_IF(sign, s, u), \ + INTWIDTH(z, n,) \ + )>(_counter) \ + ); \ + break; +/// Switch implementation that instantiates case statements for (un)signed +/// Solidity integer types. +#define SWITCHIMPL(sign) \ + std::ostringstream stream; \ + switch (_intWidth) \ + { \ + BOOST_PP_REPEAT(32, CASEIMPL, sign) \ + } \ + return stream.str(); + +namespace solidity::test::fuzzer +{ +struct LiteralGeneratorUtil +{ + template + V integerValue(size_t _counter); + + std::string signedIntegerValue(size_t _counter, size_t _intWidth) + { + SWITCHIMPL(1) + } + + std::string unsignedIntegerValue(size_t _counter, size_t _intWidth) + { + SWITCHIMPL(0) + } + + std::string integerValue(size_t _counter, size_t _intWidth, bool _signed); + std::string fixedBytes(size_t _numBytes, size_t _counter, bool _isHexLiteral); +}; +} diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index 03dc5c97e..264b2a1e7 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -17,6 +17,7 @@ // SPDX-License-Identifier: GPL-3.0 #include +#include #include #include @@ -26,6 +27,7 @@ #include + using namespace solidity::test::fuzzer; using namespace solidity::test::fuzzer::mutator; using namespace solidity::util; @@ -242,10 +244,13 @@ string FunctionType::toString() }; std::string retString = std::string("function ") + "(" + typeString(inputs) + ")"; + + // TODO: Detect function state mutability instead of generating blanket + // impure functions. if (outputs.empty()) - return retString + " external pure"; + return retString + " external"; else - return retString + " external pure returns (" + typeString(outputs) + ")"; + return retString + " external returns (" + typeString(outputs) + ")"; } string FunctionState::params(Params _p) @@ -271,7 +276,7 @@ string AssignmentStmtGenerator::visit() auto lhs = exprGen.expression(); if (!lhs.has_value()) return "\n"; - auto rhs = exprGen.expression(lhs.value().first); + auto rhs = exprGen.expression(lhs.value()); if (!rhs.has_value()) return "\n"; return indentation() + lhs.value().second + " = " + rhs.value().second + ";\n"; @@ -287,7 +292,34 @@ void StatementGenerator::setup() string StatementGenerator::visit() { - return visitChildren(); + bool unchecked = uRandDist->probable(s_uncheckedBlockInvProb); + bool inUnchecked = mutator->generator()->unchecked(); + // Do not generate nested unchecked blocks. + bool generateUncheckedBlock = unchecked && !inUnchecked; + if (generateUncheckedBlock) + mutator->generator()->unchecked(true); + + ostringstream os; + // Randomise visit order + vector> randomisedChildren; + for (auto const& child: generators) + randomisedChildren.push_back(child); + shuffle(randomisedChildren.begin(), randomisedChildren.end(), *uRandDist->randomEngine); + for (auto const& child: randomisedChildren) + if (uRandDist->likely(child.second + 1)) + { + os << std::visit(GenericVisitor{ + [&](auto const& _item) { return _item->generate(); } + }, child.first); + if (holds_alternative>(child.first) && + generateUncheckedBlock + ) + { + get>(child.first)->unchecked(false); + get>(child.first)->resetInUnchecked(); + } + } + return os.str(); } void BlockStmtGenerator::setup() @@ -303,9 +335,13 @@ string BlockStmtGenerator::visit() return "\n"; incrementNestingDepth(); ostringstream block; - // Make block unchecked with a small probability - bool unchecked = uRandDist->probable(s_uncheckedInvProb); - block << indentation() + (unchecked ? "unchecked " : "") + "{\n"; + if (unchecked() && !m_inUnchecked) + { + block << indentation() + "unchecked " + "{\n"; + m_inUnchecked = true; + } + else + block << indentation() + "{\n"; state->indent(); block << visitChildren(); state->unindent(); @@ -341,15 +377,16 @@ string FunctionGenerator::visit() << name << state->currentFunctionState()->params(FunctionState::Params::INPUT) << " " - << visibility - << " pure"; + << visibility; if (!state->currentFunctionState()->outputs.empty()) function << " returns" << state->currentFunctionState()->params(FunctionState::Params::OUTPUT); ostringstream block; + // Make sure block stmt generator does not output an unchecked block + mutator->generator()->unchecked(false); + block << visitChildren(); // Since visitChildren() may not visit block stmt, we default to an empty // block. - block << visitChildren(); if (block.str().empty()) block << indentation() << "{ }\n"; function << "\n" << block.str(); @@ -392,23 +429,105 @@ optional> ExpressionGenerator::expression() return randomLValueExpression(); } -optional> ExpressionGenerator::expression(SolidityTypePtr _type) +pair ExpressionGenerator::literal(SolidityTypePtr _type) { + string literalValue = visit(LiteralGenerator{state}, _type); + return pair(_type, literalValue); +} + +optional> ExpressionGenerator::expression(pair _typeName) +{ + // Filter non-identical variables of the same type. auto liveTypedVariables = state->currentFunctionState()->inputs | - ranges::views::filter([&_type](auto& _item) { - return _item.first.index() == _type.index() && visit(TypeComparator{}, _item.first, _type); + ranges::views::filter([&_typeName](auto& _item) { + return _item.first.index() == _typeName.first.index() && + _item.second != _typeName.second && + visit(TypeComparator{}, _item.first, _typeName.first); }) | ranges::to>>(); liveTypedVariables += state->currentFunctionState()->outputs | - ranges::views::filter([&_type](auto& _item) { - return _item.first.index() == _type.index() && visit(TypeComparator{}, _item.first, _type); + ranges::views::filter([&_typeName](auto& _item) { + return _item.first.index() == _typeName.first.index() && + _item.second != _typeName.second && + visit(TypeComparator{}, _item.first, _typeName.first); }) | ranges::to>>(); if (liveTypedVariables.empty()) - return nullopt; + { + // TODO: Generate literals for contract and function types. + if (!(holds_alternative>(_typeName.first) || holds_alternative>(_typeName.first))) + return literal(_typeName.first); + else + return nullopt; + } return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1]; } +string LiteralGenerator::operator()(shared_ptr const&) +{ + string preChecksumAddress = LiteralGeneratorUtil{}.fixedBytes( + 20, + (*state->uRandDist->randomEngine)(), + true + ); + return string("address(") + + solidity::util::getChecksummedAddress(preChecksumAddress) + + ")"; +} + +string LiteralGenerator::operator()(shared_ptr const&) +{ + if (state->uRandDist->probable(2)) + return "true"; + else + return "false"; +} + +string LiteralGenerator::operator()(shared_ptr const&) +{ + return "\"" + + LiteralGeneratorUtil{}.fixedBytes( + state->uRandDist->distributionOneToN(32), + (*state->uRandDist->randomEngine)(), + true + ) + + "\""; +} + +string LiteralGenerator::operator()(shared_ptr const& _type) +{ + bool bytes20 = _type->numBytes == 20; + string literalString = "0x" + + LiteralGeneratorUtil{}.fixedBytes( + _type->numBytes, + (*state->uRandDist->randomEngine)(), + true + ); + if (bytes20) + return "bytes20(address(" + solidity::util::getChecksummedAddress(literalString) + "))"; + else + return literalString; +} + +string LiteralGenerator::operator()(shared_ptr const&) +{ + solAssert(false, ""); +} + +string LiteralGenerator::operator()(shared_ptr const&) +{ + solAssert(false, ""); +} + +string LiteralGenerator::operator()(shared_ptr const& _type) +{ + return LiteralGeneratorUtil{}.integerValue( + (*state->uRandDist->randomEngine)(), + _type->numBits, + _type->signedType + ); +} + optional TypeProvider::type(SolidityTypePtr _type) { vector matchingTypes = state->currentFunctionState()->inputs | diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index 68e48bdba..d02e0f712 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -475,7 +475,13 @@ struct TestState } std::shared_ptr currentFunctionState() { - return functionState[currentFunction]; + std::string function = currentFunctionName(); + return functionState[function]; + } + std::shared_ptr currentSourceState() + { + std::string currentSource = currentPath(); + return sourceUnitState[currentSource]; } /// Returns true if @name sourceUnitPaths is empty, /// false otherwise. @@ -509,6 +515,11 @@ struct TestState solAssert(numSourceUnits > 0, ""); return currentSourceUnitPath; } + std::string currentFunctionName() const + { + solAssert(numFunctions > 0, ""); + return currentFunction; + } /// Adds @param _path to list of source paths in global test /// state and increments @name m_numSourceUnits. void updateSourcePath(std::string const& _path) @@ -631,6 +642,21 @@ struct TypeComparator } }; +struct LiteralGenerator +{ + explicit LiteralGenerator(std::shared_ptr _state): state(std::move(_state)) + {} + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + std::string operator()(std::shared_ptr const& _type); + + std::shared_ptr state; +}; + struct ExpressionGenerator { ExpressionGenerator(std::shared_ptr _state): state(std::move(_state)) @@ -642,7 +668,8 @@ struct ExpressionGenerator TYPEMAX }; - std::optional> expression(SolidityTypePtr _type); + std::optional> expression(std::pair _typeName); + std::pair literal(SolidityTypePtr _type); std::optional> expression(); std::pair randomLValueExpression(); @@ -830,6 +857,8 @@ public: void setup() override; std::string visit() override; std::string name() override { return "Statement generator"; } +private: + static constexpr unsigned s_uncheckedBlockInvProb = 37; }; class AssignmentStmtGenerator: public GeneratorBase @@ -847,7 +876,9 @@ class BlockStmtGenerator: public GeneratorBase public: explicit BlockStmtGenerator(std::shared_ptr _mutator): GeneratorBase(std::move(_mutator)), - m_nestingDepth(0) + m_nestingDepth(0), + m_unchecked(false), + m_inUnchecked(false) {} void endVisit() override { @@ -872,8 +903,22 @@ public: void setup() override; std::string visit() override; std::string name() override { return "Block statement generator"; } + void unchecked(bool _unchecked) + { + m_unchecked = _unchecked; + } + bool unchecked() + { + return m_unchecked; + } + void resetInUnchecked() + { + m_inUnchecked = false; + } private: size_t m_nestingDepth; + bool m_unchecked; + bool m_inUnchecked; static constexpr unsigned s_maxStatements = 4; static constexpr unsigned s_maxNestingDepth = 3; static constexpr size_t s_uncheckedInvProb = 13;