Do not generate nested unchecked blocks; add typed literals.

This commit is contained in:
Bhargava Shastry 2021-05-03 18:46:17 +02:00
parent fdb1bc0b3d
commit 0000bb0eea
5 changed files with 359 additions and 19 deletions

View File

@ -35,6 +35,7 @@ if (OSSFUZZ)
solc_ossfuzz.cpp
../fuzzer_common.cpp
../../TestCaseReader.cpp
LiteralGeneratorUtil.cpp
SolidityGenerator.cpp
SolidityCustomMutatorInterface.cpp
)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <test/tools/ossfuzz/LiteralGeneratorUtil.h>
#include <liblangutil/Exceptions.h>
using namespace solidity::test::fuzzer;
using namespace std;
template<typename V>
V LiteralGeneratorUtil::integerValue(size_t _counter)
{
V value = V(
u256(solidity::util::keccak256(solidity::util::h256(_counter))) %
u256(boost::math::tools::max_value<V>())
);
if (boost::multiprecision::is_signed_number<V>::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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libsolutil/Keccak256.h>
#include <boost/preprocessor.hpp>
#include <sstream>
/// 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)<width> where <width> 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<typename V>
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);
};
}

View File

@ -17,6 +17,7 @@
// SPDX-License-Identifier: GPL-3.0
#include <test/tools/ossfuzz/SolidityGenerator.h>
#include <test/tools/ossfuzz/LiteralGeneratorUtil.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/Visitor.h>
@ -26,6 +27,7 @@
#include <boost/range/algorithm/copy.hpp>
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<BlockStmtGenerator>()->unchecked();
// Do not generate nested unchecked blocks.
bool generateUncheckedBlock = unchecked && !inUnchecked;
if (generateUncheckedBlock)
mutator->generator<BlockStmtGenerator>()->unchecked(true);
ostringstream os;
// Randomise visit order
vector<std::pair<GeneratorPtr, unsigned>> 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<shared_ptr<BlockStmtGenerator>>(child.first) &&
generateUncheckedBlock
)
{
get<shared_ptr<BlockStmtGenerator>>(child.first)->unchecked(false);
get<shared_ptr<BlockStmtGenerator>>(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<BlockStmtGenerator>()->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<pair<SolidityTypePtr, string>> ExpressionGenerator::expression()
return randomLValueExpression();
}
optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression(SolidityTypePtr _type)
pair<SolidityTypePtr, string> ExpressionGenerator::literal(SolidityTypePtr _type)
{
string literalValue = visit(LiteralGenerator{state}, _type);
return pair(_type, literalValue);
}
optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression(pair<SolidityTypePtr, string> _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<vector<pair<SolidityTypePtr, string>>>();
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<vector<pair<SolidityTypePtr, string>>>();
if (liveTypedVariables.empty())
return nullopt;
{
// TODO: Generate literals for contract and function types.
if (!(holds_alternative<shared_ptr<FunctionType>>(_typeName.first) || holds_alternative<shared_ptr<ContractType>>(_typeName.first)))
return literal(_typeName.first);
else
return nullopt;
}
return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1];
}
string LiteralGenerator::operator()(shared_ptr<AddressType> const&)
{
string preChecksumAddress = LiteralGeneratorUtil{}.fixedBytes(
20,
(*state->uRandDist->randomEngine)(),
true
);
return string("address(") +
solidity::util::getChecksummedAddress(preChecksumAddress) +
")";
}
string LiteralGenerator::operator()(shared_ptr<BoolType> const&)
{
if (state->uRandDist->probable(2))
return "true";
else
return "false";
}
string LiteralGenerator::operator()(shared_ptr<BytesType> const&)
{
return "\"" +
LiteralGeneratorUtil{}.fixedBytes(
state->uRandDist->distributionOneToN(32),
(*state->uRandDist->randomEngine)(),
true
) +
"\"";
}
string LiteralGenerator::operator()(shared_ptr<FixedBytesType> 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<ContractType> const&)
{
solAssert(false, "");
}
string LiteralGenerator::operator()(shared_ptr<FunctionType> const&)
{
solAssert(false, "");
}
string LiteralGenerator::operator()(shared_ptr<IntegerType> const& _type)
{
return LiteralGeneratorUtil{}.integerValue(
(*state->uRandDist->randomEngine)(),
_type->numBits,
_type->signedType
);
}
optional<SolidityTypePtr> TypeProvider::type(SolidityTypePtr _type)
{
vector<SolidityTypePtr> matchingTypes = state->currentFunctionState()->inputs |

View File

@ -475,7 +475,13 @@ struct TestState
}
std::shared_ptr<FunctionState> currentFunctionState()
{
return functionState[currentFunction];
std::string function = currentFunctionName();
return functionState[function];
}
std::shared_ptr<SourceState> 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<TestState> _state): state(std::move(_state))
{}
std::string operator()(std::shared_ptr<AddressType> const& _type);
std::string operator()(std::shared_ptr<BoolType> const& _type);
std::string operator()(std::shared_ptr<BytesType> const& _type);
std::string operator()(std::shared_ptr<ContractType> const& _type);
std::string operator()(std::shared_ptr<FixedBytesType> const& _type);
std::string operator()(std::shared_ptr<FunctionType> const& _type);
std::string operator()(std::shared_ptr<IntegerType> const& _type);
std::shared_ptr<TestState> state;
};
struct ExpressionGenerator
{
ExpressionGenerator(std::shared_ptr<TestState> _state): state(std::move(_state))
@ -642,7 +668,8 @@ struct ExpressionGenerator
TYPEMAX
};
std::optional<std::pair<SolidityTypePtr, std::string>> expression(SolidityTypePtr _type);
std::optional<std::pair<SolidityTypePtr, std::string>> expression(std::pair<SolidityTypePtr, std::string> _typeName);
std::pair<SolidityTypePtr, std::string> literal(SolidityTypePtr _type);
std::optional<std::pair<SolidityTypePtr, std::string>> expression();
std::pair<SolidityTypePtr, std::string> 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<SolidityGenerator> _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;