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;