Add function output params

This commit is contained in:
Bhargava Shastry 2021-04-29 19:00:34 +02:00
parent 49bdc1571e
commit ef29a5e287
3 changed files with 261 additions and 76 deletions

View File

@ -222,6 +222,47 @@ string ContractGenerator::visit()
return os.str(); return os.str();
} }
string FunctionType::toString()
{
auto typeString = [](std::vector<SolidityTypePtr>& _types)
{
std::string sep;
std::string typeStr;
for (auto const& i: _types)
{
typeStr += sep + std::visit(GenericVisitor{
[&](auto const& _item) { return _item->toString(); }
}, i);
if (sep.empty())
sep = ",";
}
return typeStr;
};
std::string retString = std::string("function ") + "(" + typeString(inputs) + ")";
if (outputs.empty())
return retString + " external pure";
else
return retString + " external pure returns (" + typeString(outputs) + ")";
}
string FunctionState::params(Params _p)
{
vector<string> params = (_p == Params::INPUT ? inputs : outputs) |
ranges::views::transform(
[](auto& _item) -> string
{
return visit(
GenericVisitor{[](auto const& _item) {
return _item->toString();
}}, _item.first) +
" " +
_item.second;
}) |
ranges::to<vector<string>>();
return "(" + boost::algorithm::join(params, ",") + ")";
}
string FunctionGenerator::visit() string FunctionGenerator::visit()
{ {
string visibility; string visibility;
@ -230,16 +271,47 @@ string FunctionGenerator::visit()
if (!m_freeFunction) if (!m_freeFunction)
visibility = "external"; visibility = "external";
auto inputType = TypeGenerator{state}.type(); // Add I/O
state->currentFunctionState()->addInput(pair<string, shared_ptr<SolidityType>>("i1", inputType)); if (uRandDist->likely(s_maxInputs + 1))
string inputParams = inputType->toString() + " i1"; for (unsigned i = 0; i < uRandDist->distributionOneToN(s_maxInputs); i++)
return indentation(state->indentationLevel) + state->currentFunctionState()->addInput(TypeProvider{state}.type());
"function " + name + "(" + inputParams + ") " + visibility + " pure {}\n";
if (uRandDist->likely(s_maxOutputs + 1))
for (unsigned i = 0; i < uRandDist->distributionOneToN(s_maxOutputs); i++)
state->currentFunctionState()->addOutput(TypeProvider{state}.type());
ostringstream function;
function << indentation(state->indentationLevel)
<< "function "
<< name
<< state->currentFunctionState()->params(FunctionState::Params::INPUT)
<< " "
<< visibility
<< " pure"
<< " returns"
<< state->currentFunctionState()->params(FunctionState::Params::OUTPUT)
<< " {}\n";
return function.str();
} }
shared_ptr<SolidityType> TypeGenerator::type() optional<SolidityTypePtr> TypeProvider::type(SolidityTypePtr _type)
{ {
switch (typeCategory()) vector<SolidityTypePtr> matchingTypes = state->currentFunctionState()->inputs |
ranges::views::filter([&_type](auto& _item) {
return _item.first == _type;
}) |
ranges::views::transform([](auto& _item) { return _item.first; }) |
ranges::to<vector<SolidityTypePtr>>();
if (matchingTypes.empty())
return nullopt;
else
return matchingTypes[state->uRandDist->distributionOneToN(matchingTypes.size()) - 1];
}
SolidityTypePtr TypeProvider::type()
{
switch (randomTypeCategory())
{ {
case Type::INTEGER: case Type::INTEGER:
{ {
@ -250,10 +322,10 @@ shared_ptr<SolidityType> TypeGenerator::type()
); );
// Choose signed/unsigned type with probability of 1/2 = 0.5 // Choose signed/unsigned type with probability of 1/2 = 0.5
bool signedType = state->uRandDist->probable(2); bool signedType = state->uRandDist->probable(2);
return dynamic_pointer_cast<SolidityType>(make_shared<IntegerType>(b, signedType)); return make_shared<IntegerType>(b, signedType);
} }
case Type::BOOL: case Type::BOOL:
return dynamic_pointer_cast<SolidityType>(make_shared<BoolType>()); return make_shared<BoolType>();
case Type::FIXEDBYTES: case Type::FIXEDBYTES:
{ {
FixedBytesType::Bytes w = static_cast<FixedBytesType::Bytes>( FixedBytesType::Bytes w = static_cast<FixedBytesType::Bytes>(
@ -261,18 +333,18 @@ shared_ptr<SolidityType> TypeGenerator::type()
static_cast<size_t>(FixedBytesType::Bytes::W32) static_cast<size_t>(FixedBytesType::Bytes::W32)
) )
); );
return dynamic_pointer_cast<SolidityType>(make_shared<FixedBytesType>(w)); return make_shared<FixedBytesType>(w);
} }
case Type::BYTES: case Type::BYTES:
return dynamic_pointer_cast<SolidityType>(make_shared<BytesType>()); return make_shared<BytesType>();
case Type::ADDRESS: case Type::ADDRESS:
return dynamic_pointer_cast<SolidityType>(make_shared<AddressType>()); return make_shared<AddressType>();
case Type::FUNCTION: case Type::FUNCTION:
return dynamic_pointer_cast<SolidityType>(make_shared<FunctionType>()); return make_shared<FunctionType>();
case Type::CONTRACT: case Type::CONTRACT:
if (state->sourceUnitState[state->currentPath()]->contractType()) if (state->sourceUnitState[state->currentPath()]->contractType())
return state->sourceUnitState[state->currentPath()]->randomContractType(); return state->sourceUnitState[state->currentPath()]->randomContractType();
return dynamic_pointer_cast<SolidityType>(make_shared<BoolType>()); return make_shared<BoolType>();
default: default:
solAssert(false, ""); solAssert(false, "");
} }

View File

@ -23,6 +23,7 @@
#pragma once #pragma once
#include <test/tools/ossfuzz/Generators.h> #include <test/tools/ossfuzz/Generators.h>
#include <test/tools/ossfuzz/Types.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
@ -31,6 +32,7 @@
#include <set> #include <set>
#include <variant> #include <variant>
#include <range/v3/algorithm/all_of.hpp>
#include <range/v3/range/conversion.hpp> #include <range/v3/range/conversion.hpp>
#include <range/v3/view/filter.hpp> #include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp> #include <range/v3/view/transform.hpp>
@ -42,22 +44,25 @@ class SolidityGenerator;
/// Type declarations /// Type declarations
#define SEMICOLON() ; #define SEMICOLON() ;
#define FORWARDDECLAREGENERATORS(G) class G #define FORWARDDECLARE(G) class G
GENERATORLIST(FORWARDDECLAREGENERATORS, SEMICOLON(), SEMICOLON()) GENERATORLIST(FORWARDDECLARE, SEMICOLON(), SEMICOLON())
#undef FORWARDDECLAREGENERATORS TYPELIST(FORWARDDECLARE, SEMICOLON(), SEMICOLON())
#undef FORWARDDECLARE
#undef SEMICOLON #undef SEMICOLON
#define COMMA() , #define COMMA() ,
using GeneratorPtr = std::variant<
#define VARIANTOFSHARED(G) std::shared_ptr<G>
GENERATORLIST(VARIANTOFSHARED, COMMA(), )
>;
#undef VARIANTOFSHARED
using Generator = std::variant< using Generator = std::variant<
#define VARIANTOFGENERATOR(G) G #define VARIANTOFGENERATOR(G) G
GENERATORLIST(VARIANTOFGENERATOR, COMMA(), ) GENERATORLIST(VARIANTOFGENERATOR, COMMA(), )
>; >;
#undef VARIANTOFGENERATOR using GeneratorPtr = std::variant<
#define VARIANTOFSHARED(G) std::shared_ptr<G>
GENERATORLIST(VARIANTOFSHARED, COMMA(), )
>;
using SolidityTypePtr = std::variant<
TYPELIST(VARIANTOFSHARED, COMMA(), )
>;
#undef VARIANTOFSHARED
#undef COMMA #undef COMMA
using RandomEngine = std::mt19937_64; using RandomEngine = std::mt19937_64;
using Distribution = std::uniform_int_distribution<size_t>; using Distribution = std::uniform_int_distribution<size_t>;
@ -116,14 +121,16 @@ struct ContractState
std::shared_ptr<UniformRandomDistribution> uRandDist; std::shared_ptr<UniformRandomDistribution> uRandDist;
}; };
struct SolidityType class SolType
{ {
virtual ~SolidityType() = default; public:
virtual ~SolType() = default;
virtual std::string toString() = 0; virtual std::string toString() = 0;
}; };
struct IntegerType: SolidityType class IntegerType: public SolType
{ {
public:
enum class Bits: size_t enum class Bits: size_t
{ {
B8 = 1, B8 = 1,
@ -167,6 +174,11 @@ struct IntegerType: SolidityType
signedType(_signed), signedType(_signed),
numBits(static_cast<size_t>(_bits) * 8) numBits(static_cast<size_t>(_bits) * 8)
{} {}
bool operator==(IntegerType const& _rhs)
{
return this->signedType == _rhs.signedType &&
this->numBits == _rhs.numBits;
}
std::string toString() override std::string toString() override
{ {
return (signedType ? "int" : "uint") + std::to_string(numBits); return (signedType ? "int" : "uint") + std::to_string(numBits);
@ -175,25 +187,36 @@ struct IntegerType: SolidityType
size_t numBits; size_t numBits;
}; };
struct BoolType: SolidityType class BoolType: public SolType
{ {
public:
std::string toString() override std::string toString() override
{ {
return "bool"; return "bool";
} }
bool operator==(BoolType const&)
{
return true;
}
}; };
struct AddressType: SolidityType class AddressType: public SolType
{ {
public:
// TODO: Implement address payable // TODO: Implement address payable
std::string toString() override std::string toString() override
{ {
return "address"; return "address";
} }
bool operator==(AddressType const&)
{
return true;
}
}; };
struct FixedBytesType: SolidityType class FixedBytesType: public SolType
{ {
public:
enum class Bytes: size_t enum class Bytes: size_t
{ {
W1 = 1, W1 = 1,
@ -232,6 +255,10 @@ struct FixedBytesType: SolidityType
FixedBytesType(Bytes _width): numBytes(static_cast<size_t>(_width)) FixedBytesType(Bytes _width): numBytes(static_cast<size_t>(_width))
{} {}
bool operator==(FixedBytesType const& _rhs)
{
return this->numBytes == _rhs.numBytes;
}
std::string toString() override std::string toString() override
{ {
return "bytes" + std::to_string(numBytes); return "bytes" + std::to_string(numBytes);
@ -239,16 +266,22 @@ struct FixedBytesType: SolidityType
size_t numBytes; size_t numBytes;
}; };
struct BytesType: SolidityType class BytesType: public SolType
{ {
public:
std::string toString() override std::string toString() override
{ {
return "bytes memory"; return "bytes memory";
} }
bool operator==(BytesType const&)
{
return true;
}
}; };
struct ContractType: SolidityType class ContractType: public SolType
{ {
public:
ContractType(std::string _name): contractName(_name) ContractType(std::string _name): contractName(_name)
{} {}
std::string toString() override std::string toString() override
@ -259,11 +292,16 @@ struct ContractType: SolidityType
{ {
return contractName; return contractName;
} }
bool operator==(ContractType const&)
{
return true;
}
std::string contractName; std::string contractName;
}; };
struct FunctionType: SolidityType class FunctionType: public SolType
{ {
public:
FunctionType() = default; FunctionType() = default;
~FunctionType() override ~FunctionType() override
{ {
@ -271,40 +309,30 @@ struct FunctionType: SolidityType
outputs.clear(); outputs.clear();
} }
void addInput(std::shared_ptr<SolidityType> _input) void addInput(SolidityTypePtr _input)
{ {
inputs.emplace_back(_input); inputs.emplace_back(_input);
} }
void addOutput(std::shared_ptr<SolidityType> _output) void addOutput(SolidityTypePtr _output)
{ {
outputs.emplace_back(_output); outputs.emplace_back(_output);
} }
std::string toString() override std::string toString() override;
bool operator==(FunctionType const& _rhs)
{ {
auto typeString = [](std::vector<std::shared_ptr<SolidityType>>& _types) if (_rhs.inputs.size() != this->inputs.size() || _rhs.outputs.size() != this->outputs.size())
{ return false;
std::string sep; if (!std::equal(_rhs.inputs.begin(), _rhs.inputs.end(), this->inputs.begin()) ||
std::string typeStr; !std::equal(_rhs.outputs.begin(), _rhs.outputs.end(), this->outputs.begin())
for (auto const& i: _types) )
{ return false;
typeStr += sep + i->toString(); return true;
if (sep.empty())
sep = ",";
}
return typeStr;
};
std::string retString = std::string("function ") + "(" + typeString(inputs) + ")";
if (outputs.empty())
return retString + " external pure";
else
return retString + " external pure returns (" + typeString(outputs) + ")";
} }
std::vector<std::shared_ptr<SolidityType>> inputs; std::vector<SolidityTypePtr> inputs;
std::vector<std::shared_ptr<SolidityType>> outputs; std::vector<SolidityTypePtr> outputs;
}; };
/// Forward declaration /// Forward declaration
@ -318,7 +346,7 @@ struct SourceState
{} {}
void addFreeFunction(std::string& _functionName) void addFreeFunction(std::string& _functionName)
{ {
exports[std::dynamic_pointer_cast<SolidityType>(std::make_shared<FunctionType>())] = _functionName; exports[std::make_shared<FunctionType>()] = _functionName;
} }
bool freeFunction(std::string const& _functionName) bool freeFunction(std::string const& _functionName)
{ {
@ -327,37 +355,39 @@ struct SourceState
bool contractType() bool contractType()
{ {
return !(exports | ranges::views::filter([](auto& _i) { return !(exports | ranges::views::filter([](auto& _i) {
return bool(std::dynamic_pointer_cast<ContractType>(_i.first)); return std::holds_alternative<std::shared_ptr<ContractType>>(_i.first);
})).empty(); })).empty();
} }
std::string randomContract() std::string randomContract()
{ {
auto contracts = exports | auto contracts = exports |
ranges::views::filter([](auto& _item) -> bool { ranges::views::filter([](auto& _item) -> bool {
return bool(std::dynamic_pointer_cast<ContractType>(_item.first)); return std::holds_alternative<std::shared_ptr<ContractType>>(
_item.first
);
}) | }) |
ranges::views::transform([](auto& _item) -> std::string { ranges::views::transform([](auto& _item) -> std::string {
return _item.second; return _item.second;
}) | ranges::to<std::vector<std::string>>; }) | ranges::to<std::vector<std::string>>;
return contracts[uRandDist->distributionOneToN(contracts.size()) - 1]; return contracts[uRandDist->distributionOneToN(contracts.size()) - 1];
} }
std::shared_ptr<SolidityType> randomContractType() std::shared_ptr<ContractType> randomContractType()
{ {
auto contracts = exports | auto contracts = exports |
ranges::views::filter([](auto& _item) -> bool { ranges::views::filter([](auto& _item) -> bool {
return bool(std::dynamic_pointer_cast<ContractType>(_item.first)); return std::holds_alternative<std::shared_ptr<ContractType>>(_item.first);
}) | }) |
ranges::views::transform([](auto& _item) -> std::shared_ptr<SolidityType> { ranges::views::transform([](auto& _item) -> std::shared_ptr<ContractType> {
return _item.first; return std::get<std::shared_ptr<ContractType>>(_item.first);
}) | }) |
ranges::to<std::vector<std::shared_ptr<SolidityType>>>; ranges::to<std::vector<std::shared_ptr<ContractType>>>;
return contracts[uRandDist->distributionOneToN(contracts.size()) - 1]; return contracts[uRandDist->distributionOneToN(contracts.size()) - 1];
} }
void addImportedSourcePath(std::string& _sourcePath) void addImportedSourcePath(std::string& _sourcePath)
{ {
importedSources.emplace(_sourcePath); importedSources.emplace(_sourcePath);
} }
void resolveImports(std::map<std::shared_ptr<SolidityType>, std::string> _imports) void resolveImports(std::map<SolidityTypePtr, std::string> _imports)
{ {
for (auto const& item: _imports) for (auto const& item: _imports)
exports.emplace(item); exports.emplace(item);
@ -374,28 +404,37 @@ struct SourceState
void print(std::ostream& _os) const; void print(std::ostream& _os) const;
std::shared_ptr<UniformRandomDistribution> uRandDist; std::shared_ptr<UniformRandomDistribution> uRandDist;
std::set<std::string> importedSources; std::set<std::string> importedSources;
std::map<std::shared_ptr<SolidityType>, std::string> exports; std::map<SolidityTypePtr, std::string> exports;
}; };
struct FunctionState struct FunctionState
{ {
enum class Params
{
INPUT,
OUTPUT
};
FunctionState() = default; FunctionState() = default;
~FunctionState() ~FunctionState()
{ {
inputs.clear(); inputs.clear();
outputs.clear(); outputs.clear();
} }
void addInput(std::pair<std::string, std::shared_ptr<SolidityType>> _input) using TypeId = std::pair<SolidityTypePtr, std::string>;
void addInput(SolidityTypePtr _input)
{ {
inputs.emplace(_input); inputs.emplace(_input, "i" + std::to_string(numInputs++));
} }
void addOutput(std::pair<std::string, std::shared_ptr<SolidityType>> _output) void addOutput(SolidityTypePtr _output)
{ {
outputs.emplace(_output); outputs.emplace(_output, "o" + std::to_string(numOutpus++));
} }
std::string params(Params _p);
std::map<std::string, std::shared_ptr<SolidityType>> inputs; std::map<SolidityTypePtr, std::string> inputs;
std::map<std::string, std::shared_ptr<SolidityType>> outputs; std::map<SolidityTypePtr, std::string> outputs;
unsigned numInputs = 0;
unsigned numOutpus = 0;
}; };
struct TestState struct TestState
@ -547,9 +586,9 @@ struct TestState
std::string const functionPrefix = "f"; std::string const functionPrefix = "f";
}; };
struct TypeGenerator struct TypeProvider
{ {
TypeGenerator(std::shared_ptr<TestState> _state): state(std::move(_state)) TypeProvider(std::shared_ptr<TestState> _state): state(std::move(_state))
{} {}
enum class Type: size_t enum class Type: size_t
@ -564,9 +603,10 @@ struct TypeGenerator
TYPEMAX TYPEMAX
}; };
std::shared_ptr<SolidityType> type(); SolidityTypePtr type();
std::optional<SolidityTypePtr> type(SolidityTypePtr _typePtr);
Type typeCategory() Type randomTypeCategory()
{ {
return static_cast<Type>(state->uRandDist->distributionOneToN(static_cast<size_t>(Type::TYPEMAX) - 1)); return static_cast<Type>(state->uRandDist->distributionOneToN(static_cast<size_t>(Type::TYPEMAX) - 1));
} }
@ -574,6 +614,22 @@ struct TypeGenerator
std::shared_ptr<TestState> state; std::shared_ptr<TestState> state;
}; };
struct ExpressionGenerator
{
ExpressionGenerator(std::shared_ptr<TestState> _state): state(std::move(_state))
{}
enum class Expr: size_t
{
VARREF = 1,
TYPEMAX
};
std::string expression(SolidityTypePtr _type);
std::shared_ptr<TestState> state;
};
struct GeneratorBase struct GeneratorBase
{ {
explicit GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator); explicit GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator);
@ -742,7 +798,8 @@ public:
} }
private: private:
bool m_freeFunction; bool m_freeFunction;
static constexpr unsigned s_maxInputs = 4;
static constexpr unsigned s_maxOutputs = 4;
}; };
class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator> class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator>

View File

@ -0,0 +1,56 @@
/*
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
/**
* Convenience macros for Solidity generator type declarations.
*/
#pragma once
/*
* Alphabetically sorted Solidity types.
* SEP must appear between two elements and ENDSEP must
* appear after the last element.
*
* This macro applies another macro (MACRO) that is
* passed as input to this macro on the list of Generator
* types. Example that uses forward declaration:
*
* #define MACRO(T) struct T;
* #define SEMICOLON() ;
*
* TYPELIST(MACRO, SEMICOLON(), SEMICOLON())
*
* produces
*
* struct PragmaGenerator;class SourceUnitGenerator;class TestCaseGenerator;
*
*/
#define TYPELIST(MACRO, SEP, ENDSEP) \
MACRO(AddressType) SEP \
MACRO(BoolType) SEP \
MACRO(BytesType) SEP \
MACRO(ContractType) SEP \
MACRO(FixedBytesType) SEP \
MACRO(FunctionType) SEP \
MACRO(IntegerType) ENDSEP