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(BlockStmtGenerator) SEP \
MACRO(ContractGenerator) SEP \
MACRO(FunctionCallGenerator) SEP \
MACRO(FunctionGenerator) SEP \
MACRO(ImportGenerator) SEP \
MACRO(PragmaGenerator) SEP \

View File

@ -165,6 +165,17 @@ string PragmaGenerator::visit()
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()
{
/*
@ -189,6 +200,9 @@ string ImportGenerator::visit()
state->sourceUnitState[state->currentPath()]->resolveImports(
state->sourceUnitState[importPath]->exports
);
state->sourceUnitState[state->currentPath()]->mergeFunctionState(
state->sourceUnitState[importPath]->freeFunctions
);
}
return os.str();
}
@ -205,10 +219,12 @@ string ContractGenerator::visit()
ScopeGuard reset([&]() {
mutator->generator<FunctionGenerator>()->scope(true);
state->unindent();
state->exitContract();
});
auto set = [&]() {
state->indent();
mutator->generator<FunctionGenerator>()->scope(false);
state->enterContract();
};
ostringstream os;
string inheritance;
@ -286,7 +302,8 @@ void StatementGenerator::setup()
{
addGenerators({
{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))
{
os << std::visit(GenericVisitor{
[&](auto const& _item) { return _item->generate(); }
[](auto const& _item) { return _item->generate(); }
}, child.first);
if (holds_alternative<shared_ptr<BlockStmtGenerator>>(child.first) &&
generateUncheckedBlock
@ -358,7 +375,7 @@ string FunctionGenerator::visit()
{
string visibility;
string name = state->newFunction();
state->updateFunction(name);
state->updateFunction(name, m_freeFunction);
if (!m_freeFunction)
visibility = "external";
@ -385,6 +402,10 @@ string FunctionGenerator::visit()
// Make sure block stmt generator does not output an unchecked block
mutator->generator<BlockStmtGenerator>()->unchecked(false);
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
// block.
if (block.str().empty())
@ -420,22 +441,9 @@ pair<SolidityTypePtr, string> ExpressionGenerator::randomLValueExpression()
}
}
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)
optional<pair<SolidityTypePtr, string>> ExpressionGenerator::lValueExpression(
pair<SolidityTypePtr, string> _typeName
)
{
// Filter non-identical variables of the same type.
auto liveTypedVariables = state->currentFunctionState()->inputs |
@ -453,6 +461,30 @@ optional<pair<SolidityTypePtr, string>> ExpressionGenerator::expression(pair<Sol
}) |
ranges::to<vector<pair<SolidityTypePtr, string>>>();
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.
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
return nullopt;
}
return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1];
else
return varRef.value();
}
string LiteralGenerator::operator()(shared_ptr<AddressType> const&)
@ -574,7 +607,7 @@ SolidityTypePtr TypeProvider::type()
case Type::ADDRESS:
return make_shared<AddressType>();
case Type::FUNCTION:
return make_shared<FunctionType>();
return make_shared<FunctionType>(false);
case Type::CONTRACT:
if (state->sourceUnitState[state->currentPath()]->contractType())
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>
shared_ptr<T> SolidityGenerator::generator()
{

View File

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