Introduce function call statements.

This commit is contained in:
Bhargava Shastry 2021-05-05 16:24:13 +02:00
parent 0000bb0eea
commit 0e15a794d8
3 changed files with 323 additions and 55 deletions

View File

@ -44,6 +44,7 @@
MACRO(AssignmentStmtGenerator) SEP \ MACRO(AssignmentStmtGenerator) SEP \
MACRO(BlockStmtGenerator) SEP \ MACRO(BlockStmtGenerator) SEP \
MACRO(ContractGenerator) SEP \ MACRO(ContractGenerator) SEP \
MACRO(FunctionCallGenerator) SEP \
MACRO(FunctionGenerator) SEP \ MACRO(FunctionGenerator) SEP \
MACRO(ImportGenerator) SEP \ MACRO(ImportGenerator) SEP \
MACRO(PragmaGenerator) SEP \ MACRO(PragmaGenerator) SEP \

View File

@ -165,6 +165,17 @@ string PragmaGenerator::visit()
return boost::algorithm::join(pragmas, "\n") + "\n"; return boost::algorithm::join(pragmas, "\n") + "\n";
} }
void SourceState::resolveImports(map<SolidityTypePtr, string> _importedSymbols)
{
for (auto const& item: _importedSymbols)
exports.emplace(item);
}
void SourceState::mergeFunctionState(set<shared_ptr<FunctionState>> _importedFreeFunctions)
{
freeFunctions += _importedFreeFunctions;
}
string ImportGenerator::visit() string ImportGenerator::visit()
{ {
/* /*
@ -189,6 +200,9 @@ string ImportGenerator::visit()
state->sourceUnitState[state->currentPath()]->resolveImports( state->sourceUnitState[state->currentPath()]->resolveImports(
state->sourceUnitState[importPath]->exports state->sourceUnitState[importPath]->exports
); );
state->sourceUnitState[state->currentPath()]->mergeFunctionState(
state->sourceUnitState[importPath]->freeFunctions
);
} }
return os.str(); return os.str();
} }
@ -205,10 +219,12 @@ string ContractGenerator::visit()
ScopeGuard reset([&]() { ScopeGuard reset([&]() {
mutator->generator<FunctionGenerator>()->scope(true); mutator->generator<FunctionGenerator>()->scope(true);
state->unindent(); state->unindent();
state->exitContract();
}); });
auto set = [&]() { auto set = [&]() {
state->indent(); state->indent();
mutator->generator<FunctionGenerator>()->scope(false); mutator->generator<FunctionGenerator>()->scope(false);
state->enterContract();
}; };
ostringstream os; ostringstream os;
string inheritance; string inheritance;
@ -286,7 +302,8 @@ void StatementGenerator::setup()
{ {
addGenerators({ addGenerators({
{mutator->generator<BlockStmtGenerator>(), 1}, {mutator->generator<BlockStmtGenerator>(), 1},
{mutator->generator<AssignmentStmtGenerator>(), 1} {mutator->generator<AssignmentStmtGenerator>(), 1},
{mutator->generator<FunctionCallGenerator>(), 1}
}); });
} }
@ -309,7 +326,7 @@ string StatementGenerator::visit()
if (uRandDist->likely(child.second + 1)) if (uRandDist->likely(child.second + 1))
{ {
os << std::visit(GenericVisitor{ os << std::visit(GenericVisitor{
[&](auto const& _item) { return _item->generate(); } [](auto const& _item) { return _item->generate(); }
}, child.first); }, child.first);
if (holds_alternative<shared_ptr<BlockStmtGenerator>>(child.first) && if (holds_alternative<shared_ptr<BlockStmtGenerator>>(child.first) &&
generateUncheckedBlock generateUncheckedBlock
@ -358,7 +375,7 @@ string FunctionGenerator::visit()
{ {
string visibility; string visibility;
string name = state->newFunction(); string name = state->newFunction();
state->updateFunction(name); state->updateFunction(name, m_freeFunction);
if (!m_freeFunction) if (!m_freeFunction)
visibility = "external"; visibility = "external";
@ -385,6 +402,10 @@ string FunctionGenerator::visit()
// Make sure block stmt generator does not output an unchecked block // Make sure block stmt generator does not output an unchecked block
mutator->generator<BlockStmtGenerator>()->unchecked(false); mutator->generator<BlockStmtGenerator>()->unchecked(false);
block << visitChildren(); block << visitChildren();
if (m_freeFunction)
state->currentSourceState()->addFreeFunction(state->currentFunctionState());
else
state->currentContractState()->addFunction(state->currentFunctionState());
// Since visitChildren() may not visit block stmt, we default to an empty // Since visitChildren() may not visit block stmt, we default to an empty
// block. // block.
if (block.str().empty()) if (block.str().empty())
@ -420,22 +441,9 @@ pair<SolidityTypePtr, string> ExpressionGenerator::randomLValueExpression()
} }
} }
optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression() optional<pair<SolidityTypePtr, string>> ExpressionGenerator::lValueExpression(
{ pair<SolidityTypePtr, string> _typeName
auto currentFunctionState = state->currentFunctionState(); )
// TODO: Remove this barrier once we support more expression types.
if (currentFunctionState->inputs.empty() && currentFunctionState->outputs.empty())
return nullopt;
return randomLValueExpression();
}
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. // Filter non-identical variables of the same type.
auto liveTypedVariables = state->currentFunctionState()->inputs | auto liveTypedVariables = state->currentFunctionState()->inputs |
@ -453,6 +461,30 @@ optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression(pair<Sol
}) | }) |
ranges::to<vector<pair<SolidityTypePtr, string>>>(); ranges::to<vector<pair<SolidityTypePtr, string>>>();
if (liveTypedVariables.empty()) if (liveTypedVariables.empty())
return nullopt;
else
return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1];
}
optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression()
{
auto currentFunctionState = state->currentFunctionState();
// TODO: Remove this barrier once we support more expression types.
if (currentFunctionState->inputs.empty() && currentFunctionState->outputs.empty())
return nullopt;
return randomLValueExpression();
}
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)
{
auto varRef = lValueExpression(_typeName);
if (!varRef.has_value())
{ {
// TODO: Generate literals for contract and function types. // TODO: Generate literals for contract and function types.
if (!(holds_alternative<shared_ptr<FunctionType>>(_typeName.first) || holds_alternative<shared_ptr<ContractType>>(_typeName.first))) if (!(holds_alternative<shared_ptr<FunctionType>>(_typeName.first) || holds_alternative<shared_ptr<ContractType>>(_typeName.first)))
@ -460,7 +492,8 @@ optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression(pair<Sol
else else
return nullopt; return nullopt;
} }
return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1]; else
return varRef.value();
} }
string LiteralGenerator::operator()(shared_ptr<AddressType> const&) string LiteralGenerator::operator()(shared_ptr<AddressType> const&)
@ -574,7 +607,7 @@ SolidityTypePtr TypeProvider::type()
case Type::ADDRESS: case Type::ADDRESS:
return make_shared<AddressType>(); return make_shared<AddressType>();
case Type::FUNCTION: case Type::FUNCTION:
return make_shared<FunctionType>(); return make_shared<FunctionType>(false);
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();
@ -584,6 +617,150 @@ SolidityTypePtr TypeProvider::type()
} }
} }
string FunctionCallGenerator::lhs(vector<pair<SolidityTypePtr, string>> _functionReturnTypeNames)
{
ExpressionGenerator exprGen{state};
ostringstream callStmtLhs;
auto assignToVars = _functionReturnTypeNames |
ranges::views::transform([&exprGen](auto const& _item) -> pair<bool, optional<pair<SolidityTypePtr, string>>> {
auto e = exprGen.lValueExpression(_item);
if (e.has_value())
return {true, e.value()};
else
return {false, nullopt};
});
bool useExistingVars = ranges::all_of(
assignToVars,
[](auto const& _item) -> bool { return _item.first; }
);
if (useExistingVars)
{
auto vars = assignToVars |
ranges::views::transform([](auto const& _item) { return _item.second.value().second; }) |
ranges::to<vector<string>>();
callStmtLhs << "("
<< boost::algorithm::join(vars, ",")
<< ") = ";
}
else
{
auto newVars = _functionReturnTypeNames |
ranges::views::transform([&](auto const& _item) -> string {
state->currentFunctionState()->addLocal(_item.first);
string varName = state->currentFunctionState()->locals.back().second;
return std::visit(
GenericVisitor{[](auto const& _it) { return _it->toString(); }},
_item.first
) +
" " +
varName;
}) |
ranges::to<vector<string>>();
callStmtLhs << "("
<< boost::algorithm::join(newVars, ", ")
<< ") = ";
}
return callStmtLhs.str();
}
optional<string> FunctionCallGenerator::rhs(vector<pair<SolidityTypePtr, string>> _functionInputTypeNames)
{
ExpressionGenerator exprGen{state};
ostringstream callStmtRhs;
auto inputArguments = _functionInputTypeNames |
ranges::views::transform([&exprGen](auto const& _item) -> pair<bool, optional<pair<SolidityTypePtr, string>>>
{
auto e = exprGen.expression(_item);
if (e.has_value())
return {true, e.value()};
else
return {false, nullopt};
});
bool inputArgsValid = ranges::all_of(
inputArguments,
[](auto const& _item) -> bool { return _item.first; }
);
if (inputArgsValid)
{
auto vars = inputArguments |
ranges::views::transform([](auto const& _item) { return _item.second.value().second; }) |
ranges::to<vector<string>>();
callStmtRhs << boost::algorithm::join(vars, ",");
return callStmtRhs.str();
}
else
{
return nullopt;
}
}
string FunctionCallGenerator::callStmt(shared_ptr<FunctionState> _callee)
{
ostringstream callStmtStream;
string lhsExpr;
string rhsExpr;
bool callValid = true;
// Create lhs expression only if function outputs non-zero return values.
if (!_callee->outputs.empty())
lhsExpr = lhs(_callee->outputs);
// Create arguments only if function contains non-zero input parameters.
if (!_callee->inputs.empty())
{
auto callRhs = rhs(_callee->inputs);
// Arguments may not be found for function and contract types. In this
// case, do not make the call.
if (callRhs.has_value())
rhsExpr = (_callee->type->functionScope() ? "" : "this.") + _callee->name + "(" + callRhs.value() + ");";
else
callValid = false;
}
else
rhsExpr = (_callee->type->functionScope() ? "" : "this.") + _callee->name + "();";
if (callValid)
callStmtStream << indentation()
<< lhsExpr
<< rhsExpr;
callStmtStream << "\n";
return callStmtStream.str();
}
string FunctionCallGenerator::visit()
{
// // TODO: Generalise call to varargs function
// for (auto const& f: state->currentFunctionState()->inputs)
// if (holds_alternative<shared_ptr<FunctionType>>(f.first))
// return indentation() + f.second + "();\n";
// Consolidate available functions
auto availableFunctions = state->currentSourceState()->freeFunctions;
if (state->insideContract)
availableFunctions += state->currentContractState()->functions;
if (availableFunctions.empty())
return "\n";
shared_ptr<FunctionState> callee;
if (availableFunctions.size() > 1)
{
for (auto const& i: availableFunctions)
if (uRandDist->probable(availableFunctions.size()))
callee = i;
}
else
callee = *availableFunctions.begin();
if (callee)
return callStmt(callee);
else
return "\n";
}
template <typename T> template <typename T>
shared_ptr<T> SolidityGenerator::generator() shared_ptr<T> SolidityGenerator::generator()
{ {

View File

@ -112,15 +112,6 @@ struct UniformRandomDistribution
std::unique_ptr<RandomEngine> randomEngine; std::unique_ptr<RandomEngine> randomEngine;
}; };
struct ContractState
{
explicit ContractState(std::shared_ptr<UniformRandomDistribution> _urd):
uRandDist(std::move(_urd))
{}
std::shared_ptr<UniformRandomDistribution> uRandDist;
};
class SolType class SolType
{ {
public: public:
@ -302,7 +293,10 @@ public:
class FunctionType: public SolType class FunctionType: public SolType
{ {
public: public:
FunctionType() = default; FunctionType(bool _freeFunction)
{
freeFunction = _freeFunction;
}
~FunctionType() override ~FunctionType() override
{ {
inputs.clear(); inputs.clear();
@ -319,6 +313,11 @@ public:
outputs.emplace_back(_output); outputs.emplace_back(_output);
} }
bool functionScope()
{
return freeFunction;
}
std::string toString() override; std::string toString() override;
bool operator==(FunctionType const& _rhs) bool operator==(FunctionType const& _rhs)
{ {
@ -333,20 +332,30 @@ public:
std::vector<SolidityTypePtr> inputs; std::vector<SolidityTypePtr> inputs;
std::vector<SolidityTypePtr> outputs; std::vector<SolidityTypePtr> outputs;
bool freeFunction;
}; };
/// Forward declaration /// Forward declaration
struct TestState; struct TestState;
struct FunctionState;
struct SourceState struct SourceState
{ {
explicit SourceState(std::shared_ptr<UniformRandomDistribution> _urd): explicit SourceState(
std::shared_ptr<UniformRandomDistribution> _urd,
std::string _sourceName
):
uRandDist(std::move(_urd)), uRandDist(std::move(_urd)),
importedSources({}) importedSources({}),
sourceName(_sourceName)
{} {}
void addFreeFunction(std::string& _functionName) void addFreeFunction(std::string& _functionName)
{ {
exports[std::make_shared<FunctionType>()] = _functionName; exports[std::make_shared<FunctionType>(true)] = _functionName;
}
void addFreeFunction(std::shared_ptr<FunctionState> _state)
{
freeFunctions.emplace(_state);
} }
bool freeFunction(std::string const& _functionName) bool freeFunction(std::string const& _functionName)
{ {
@ -387,11 +396,9 @@ struct SourceState
{ {
importedSources.emplace(_sourcePath); importedSources.emplace(_sourcePath);
} }
void resolveImports(std::map<SolidityTypePtr, std::string> _imports) void resolveImports(std::map<SolidityTypePtr, std::string> _importedSymbols);
{ void mergeFunctionState(std::set<std::shared_ptr<FunctionState>> _importedFreeFunctions);
for (auto const& item: _imports)
exports.emplace(item);
}
[[nodiscard]] bool sourcePathImported(std::string const& _sourcePath) const [[nodiscard]] bool sourcePathImported(std::string const& _sourcePath) const
{ {
return importedSources.count(_sourcePath); return importedSources.count(_sourcePath);
@ -399,12 +406,16 @@ struct SourceState
~SourceState() ~SourceState()
{ {
importedSources.clear(); importedSources.clear();
freeFunctions.clear();
exports.clear();
} }
/// Prints source state to @param _os. /// Prints source state to @param _os.
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<SolidityTypePtr, std::string> exports; std::map<SolidityTypePtr, std::string> exports;
std::set<std::shared_ptr<FunctionState>> freeFunctions;
std::string sourceName;
}; };
struct FunctionState struct FunctionState
@ -414,27 +425,63 @@ struct FunctionState
INPUT, INPUT,
OUTPUT OUTPUT
}; };
FunctionState() = default; FunctionState(std::string _functionName, bool _freeFunction):
numInputs(0),
numOutpus(0),
numLocals(0),
name(_functionName)
{
type = std::make_shared<FunctionType>(_freeFunction);
}
~FunctionState() ~FunctionState()
{ {
inputs.clear(); inputs.clear();
outputs.clear(); outputs.clear();
locals.clear();
} }
using TypeId = std::pair<SolidityTypePtr, std::string>;
void addInput(SolidityTypePtr _input) void addInput(SolidityTypePtr _input)
{ {
inputs.emplace(_input, "i" + std::to_string(numInputs++)); inputs.emplace_back(_input, "i" + std::to_string(numInputs++));
type->addInput(_input);
} }
void addOutput(SolidityTypePtr _output) void addOutput(SolidityTypePtr _output)
{ {
outputs.emplace(_output, "o" + std::to_string(numOutpus++)); outputs.emplace_back(_output, "o" + std::to_string(numOutpus++));
type->addOutput(_output);
}
void addLocal(SolidityTypePtr _local)
{
locals.emplace_back(_local, "l" + std::to_string(numLocals++));
} }
std::string params(Params _p); std::string params(Params _p);
std::map<SolidityTypePtr, std::string> inputs; std::vector<std::pair<SolidityTypePtr, std::string>> inputs;
std::map<SolidityTypePtr, std::string> outputs; std::vector<std::pair<SolidityTypePtr, std::string>> outputs;
unsigned numInputs = 0; std::vector<std::pair<SolidityTypePtr, std::string>> locals;
unsigned numOutpus = 0; std::shared_ptr<FunctionType> type;
unsigned numInputs;
unsigned numOutpus;
unsigned numLocals;
std::string name;
};
struct ContractState
{
explicit ContractState(
std::shared_ptr<UniformRandomDistribution> _urd,
std::string _contractName
):
uRandDist(std::move(_urd)),
name(_contractName)
{}
void addFunction(std::shared_ptr<FunctionState> _function)
{
functions.emplace(_function);
}
std::set<std::shared_ptr<FunctionState>> functions;
std::shared_ptr<UniformRandomDistribution> uRandDist;
std::string name;
}; };
struct TestState struct TestState
@ -449,28 +496,29 @@ struct TestState
numSourceUnits(0), numSourceUnits(0),
numContracts(0), numContracts(0),
numFunctions(0), numFunctions(0),
indentationLevel(0) indentationLevel(0),
insideContract(false)
{} {}
/// Adds @param _path to @name sourceUnitPaths updates /// Adds @param _path to @name sourceUnitPaths updates
/// @name currentSourceUnitPath. /// @name currentSourceUnitPath.
void addSourceUnit(std::string const& _path) void addSourceUnit(std::string const& _path)
{ {
sourceUnitState.emplace(_path, std::make_shared<SourceState>(uRandDist)); sourceUnitState.emplace(_path, std::make_shared<SourceState>(uRandDist, _path));
currentSourceUnitPath = _path; currentSourceUnitPath = _path;
} }
/// Adds @param _name to @name contractState updates /// Adds @param _name to @name contractState updates
/// @name currentContract. /// @name currentContract.
void addContract(std::string const& _name) void addContract(std::string const& _name)
{ {
contractState.emplace(_name, std::make_shared<ContractState>(uRandDist)); contractState.emplace(_name, std::make_shared<ContractState>(uRandDist, _name));
sourceUnitState[currentSourceUnitPath]->exports[ sourceUnitState[currentSourceUnitPath]->exports[
std::make_shared<ContractType>(_name) std::make_shared<ContractType>(_name)
] = _name; ] = _name;
currentContract = _name; currentContract = _name;
} }
void addFunction(std::string const& _name) void addFunction(std::string const& _name, bool _freeFunction)
{ {
functionState.emplace(_name, std::make_shared<FunctionState>()); functionState.emplace(_name, std::make_shared<FunctionState>(_name, _freeFunction));
currentFunction = _name; currentFunction = _name;
} }
std::shared_ptr<FunctionState> currentFunctionState() std::shared_ptr<FunctionState> currentFunctionState()
@ -483,6 +531,11 @@ struct TestState
std::string currentSource = currentPath(); std::string currentSource = currentPath();
return sourceUnitState[currentSource]; return sourceUnitState[currentSource];
} }
std::shared_ptr<ContractState> currentContractState()
{
std::string contract = currentContractName();
return contractState[contract];
}
/// Returns true if @name sourceUnitPaths is empty, /// Returns true if @name sourceUnitPaths is empty,
/// false otherwise. /// false otherwise.
[[nodiscard]] bool empty() const [[nodiscard]] bool empty() const
@ -520,6 +573,11 @@ struct TestState
solAssert(numFunctions > 0, ""); solAssert(numFunctions > 0, "");
return currentFunction; return currentFunction;
} }
std::string currentContractName() const
{
solAssert(numContracts > 0, "");
return currentContract;
}
/// Adds @param _path to list of source paths in global test /// Adds @param _path to list of source paths in global test
/// state and increments @name m_numSourceUnits. /// state and increments @name m_numSourceUnits.
void updateSourcePath(std::string const& _path) void updateSourcePath(std::string const& _path)
@ -534,9 +592,9 @@ struct TestState
addContract(_name); addContract(_name);
numContracts++; numContracts++;
} }
void updateFunction(std::string const& _name) void updateFunction(std::string const& _name, bool _freeFunction)
{ {
addFunction(_name); addFunction(_name, _freeFunction);
numFunctions++; numFunctions++;
} }
void addSource() void addSource()
@ -554,6 +612,14 @@ struct TestState
{ {
--indentationLevel; --indentationLevel;
} }
void enterContract()
{
insideContract = true;
}
void exitContract()
{
insideContract = false;
}
~TestState() ~TestState()
{ {
sourceUnitState.clear(); sourceUnitState.clear();
@ -590,6 +656,8 @@ struct TestState
size_t numFunctions; size_t numFunctions;
/// Indentation level /// Indentation level
unsigned indentationLevel; unsigned indentationLevel;
/// Contract scope
bool insideContract;
/// Source name prefix /// Source name prefix
std::string const sourceUnitNamePrefix = "su"; std::string const sourceUnitNamePrefix = "su";
/// Contract name prefix /// Contract name prefix
@ -668,10 +736,15 @@ struct ExpressionGenerator
TYPEMAX TYPEMAX
}; };
std::optional<std::pair<SolidityTypePtr, std::string>> expression(std::pair<SolidityTypePtr, std::string> _typeName); std::optional<std::pair<SolidityTypePtr, std::string>> expression(
std::pair<SolidityTypePtr, std::string> _typeName
);
std::pair<SolidityTypePtr, std::string> literal(SolidityTypePtr _type); std::pair<SolidityTypePtr, std::string> literal(SolidityTypePtr _type);
std::optional<std::pair<SolidityTypePtr, std::string>> expression(); std::optional<std::pair<SolidityTypePtr, std::string>> expression();
std::pair<SolidityTypePtr, std::string> randomLValueExpression(); std::pair<SolidityTypePtr, std::string> randomLValueExpression();
std::optional<std::pair<SolidityTypePtr, std::string>> lValueExpression(
std::pair<SolidityTypePtr, std::string> _typeName
);
std::shared_ptr<TestState> state; std::shared_ptr<TestState> state;
}; };
@ -924,6 +997,23 @@ private:
static constexpr size_t s_uncheckedInvProb = 13; static constexpr size_t s_uncheckedInvProb = 13;
}; };
class FunctionCallGenerator: public GeneratorBase
{
public:
FunctionCallGenerator(std::shared_ptr<SolidityGenerator> _mutator):
GeneratorBase(std::move(_mutator))
{}
std::string visit() override;
std::string name() override
{
return "Function call generator";
}
private:
std::string lhs(std::vector<std::pair<SolidityTypePtr, std::string>> _functionReturnTypeNames);
std::optional<std::string> rhs(std::vector<std::pair<SolidityTypePtr, std::string>> _functionInputTypeNames);
std::string callStmt(std::shared_ptr<FunctionState> _callee);
};
class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator> class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator>
{ {
public: public: