mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Do not generate nested unchecked blocks; add typed literals.
This commit is contained in:
parent
fdb1bc0b3d
commit
0000bb0eea
@ -35,6 +35,7 @@ if (OSSFUZZ)
|
||||
solc_ossfuzz.cpp
|
||||
../fuzzer_common.cpp
|
||||
../../TestCaseReader.cpp
|
||||
LiteralGeneratorUtil.cpp
|
||||
SolidityGenerator.cpp
|
||||
SolidityCustomMutatorInterface.cpp
|
||||
)
|
||||
|
74
test/tools/ossfuzz/LiteralGeneratorUtil.cpp
Normal file
74
test/tools/ossfuzz/LiteralGeneratorUtil.cpp
Normal 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);
|
||||
}
|
101
test/tools/ossfuzz/LiteralGeneratorUtil.h
Normal file
101
test/tools/ossfuzz/LiteralGeneratorUtil.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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 |
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user