mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
1255 lines
31 KiB
C++
1255 lines
31 KiB
C++
/*
|
|
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/SolidityGenerator.h>
|
|
|
|
#include <libsolutil/Whiskers.h>
|
|
|
|
#include <boost/preprocessor.hpp>
|
|
#include <boost/range/adaptor/map.hpp>
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace solidity::test::fuzzer;
|
|
using namespace solidity::util;
|
|
using namespace std;
|
|
using PrngUtil = solidity::test::fuzzer::GenerationProbability;
|
|
|
|
GeneratorBase::GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator)
|
|
{
|
|
mutator = std::move(_mutator);
|
|
rand = mutator->randomEngine();
|
|
state = mutator->testState();
|
|
}
|
|
|
|
string GeneratorBase::visitChildren()
|
|
{
|
|
ostringstream os;
|
|
// Randomise visit order
|
|
vector<GeneratorPtr> randomisedChildren;
|
|
for (auto child: generators)
|
|
randomisedChildren.push_back(child);
|
|
shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand);
|
|
std::cout << "Visiting children" << std::endl;
|
|
for (auto child: randomisedChildren)
|
|
{
|
|
std::cout << "Visiting " << std::visit(NameVisitor{}, child) << std::endl;
|
|
os << std::visit(GeneratorVisitor{}, child);
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
void TestCaseGenerator::setup()
|
|
{
|
|
addGenerators({
|
|
mutator->generator<SourceUnitGenerator>()
|
|
});
|
|
}
|
|
|
|
string TestCaseGenerator::visit()
|
|
{
|
|
ostringstream os;
|
|
for (unsigned i = 0; i < PrngUtil{}.distributionOneToN(s_maxSourceUnits, rand); i++)
|
|
{
|
|
string sourcePath = path();
|
|
os << "\n"
|
|
<< "==== Source: "
|
|
<< sourcePath
|
|
<< " ===="
|
|
<< "\n";
|
|
addSourceUnit(sourcePath);
|
|
m_numSourceUnits++;
|
|
os << visitChildren();
|
|
}
|
|
return os.str();
|
|
}
|
|
|
|
void SourceUnitGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<PragmaGenerator>(),
|
|
mutator->generator<ImportGenerator>(),
|
|
mutator->generator<ConstantVariableDeclaration>(),
|
|
mutator->generator<EnumDeclaration>(),
|
|
mutator->generator<FunctionDefinitionGenerator>(),
|
|
mutator->generator<ContractDefinitionGenerator>()
|
|
}
|
|
);
|
|
mutator->generator<FunctionDefinitionGenerator>()->freeFunctionMode();
|
|
}
|
|
|
|
string SourceUnitGenerator::visit()
|
|
{
|
|
return visitChildren();
|
|
}
|
|
|
|
string PragmaGenerator::visit()
|
|
{
|
|
static constexpr const char* preamble = R"(
|
|
pragma solidity >= 0.0.0;
|
|
pragma experimental SMTChecker;
|
|
)";
|
|
// Choose equally at random from coder v1 and v2
|
|
string abiPragma = "pragma abicoder v" +
|
|
to_string(PrngUtil{}.distributionOneToN(2, rand)) +
|
|
";\n";
|
|
return preamble + abiPragma;
|
|
}
|
|
|
|
template <typename T>
|
|
shared_ptr<T> SolidityGenerator::generator()
|
|
{
|
|
for (auto& g: m_generators)
|
|
if (holds_alternative<shared_ptr<T>>(g))
|
|
return get<shared_ptr<T>>(g);
|
|
solAssert(false, "");
|
|
}
|
|
|
|
SolidityGenerator::SolidityGenerator(unsigned _seed)
|
|
{
|
|
m_rand = make_shared<RandomEngine>(_seed);
|
|
m_generators = {};
|
|
m_state = make_shared<TestState>(m_rand);
|
|
}
|
|
|
|
template <size_t I>
|
|
void SolidityGenerator::createGenerators()
|
|
{
|
|
if constexpr (I < std::variant_size_v<Generator>)
|
|
{
|
|
createGenerator<std::variant_alternative_t<I, Generator>>();
|
|
createGenerators<I + 1>();
|
|
}
|
|
}
|
|
|
|
using MP = solidity::test::fuzzer::GenerationProbability;
|
|
|
|
const std::vector<std::string> FunctionDefinitionGenerator::s_visibility = {
|
|
"public",
|
|
"private",
|
|
"external",
|
|
"internal"
|
|
};
|
|
|
|
const vector<string> FunctionDefinitionGenerator::s_mutability = {
|
|
"payable",
|
|
"view",
|
|
"pure",
|
|
"" // non payable
|
|
};
|
|
|
|
map<NatSpecGenerator::TagCategory, vector<NatSpecGenerator::Tag>> NatSpecGenerator::s_tagLookup = {
|
|
{
|
|
NatSpecGenerator::TagCategory::CONTRACT,
|
|
{
|
|
NatSpecGenerator::Tag::TITLE,
|
|
NatSpecGenerator::Tag::AUTHOR,
|
|
NatSpecGenerator::Tag::NOTICE,
|
|
NatSpecGenerator::Tag::DEV,
|
|
}
|
|
},
|
|
{
|
|
NatSpecGenerator::TagCategory::FUNCTION,
|
|
{
|
|
NatSpecGenerator::Tag::NOTICE,
|
|
NatSpecGenerator::Tag::DEV,
|
|
NatSpecGenerator::Tag::PARAM,
|
|
NatSpecGenerator::Tag::RETURN,
|
|
NatSpecGenerator::Tag::INHERITDOC
|
|
}
|
|
},
|
|
{
|
|
NatSpecGenerator::TagCategory::PUBLICSTATEVAR,
|
|
{
|
|
NatSpecGenerator::Tag::NOTICE,
|
|
NatSpecGenerator::Tag::DEV,
|
|
NatSpecGenerator::Tag::RETURN,
|
|
NatSpecGenerator::Tag::INHERITDOC
|
|
}
|
|
},
|
|
{
|
|
NatSpecGenerator::TagCategory::EVENT,
|
|
{
|
|
NatSpecGenerator::Tag::NOTICE,
|
|
NatSpecGenerator::Tag::DEV,
|
|
NatSpecGenerator::Tag::PARAM
|
|
}
|
|
}
|
|
};
|
|
|
|
const vector<string> FunctionDefinitionGenerator::s_freeFunctionMutability = {
|
|
"view",
|
|
"pure",
|
|
"" // non payable
|
|
};
|
|
|
|
string GenerationProbability::generateRandomAsciiString(size_t _length, std::shared_ptr<RandomEngine> _rand)
|
|
{
|
|
vector<char> s{};
|
|
for (size_t i = 0; i < _length * 2; i++)
|
|
s.push_back(
|
|
static_cast<char>(Distribution(0x21, 0x7e)(*_rand))
|
|
);
|
|
return string(s.begin(), s.end());
|
|
}
|
|
|
|
string GenerationProbability::generateRandomHexString(size_t _length, std::shared_ptr<RandomEngine> _rand)
|
|
{
|
|
static char const* hexDigit = "0123456789abcdefABCDEF";
|
|
vector<char> s{};
|
|
for (size_t i = 0; i < _length * 2; i++)
|
|
s.push_back(hexDigit[distributionOneToN(22, _rand) - 1]);
|
|
return string(s.begin(), s.end());
|
|
}
|
|
|
|
pair<GenerationProbability::NumberLiteral, string> GenerationProbability::generateRandomNumberLiteral(
|
|
size_t _length,
|
|
std::shared_ptr<RandomEngine> _rand
|
|
)
|
|
{
|
|
// static char const* hexDigit = "0123456789abcdefABCDEF";
|
|
static char const* decimalDigit = "0123456789";
|
|
vector<char> s{};
|
|
for (size_t i = 0; i < _length; i++)
|
|
s.push_back(decimalDigit[distributionOneToN(10, _rand) - 1]);
|
|
if (s[0] == '0')
|
|
s[0] = decimalDigit[distributionOneToN(9, _rand)];
|
|
return pair(NumberLiteral::DECIMAL, string(s.begin(), s.end()));
|
|
}
|
|
|
|
string ExportedSymbols::randomSymbol(shared_ptr<RandomEngine> _rand)
|
|
{
|
|
auto it = symbols.begin();
|
|
auto idx = (*_rand)() % symbols.size();
|
|
for (size_t i = 0; i < idx; i++)
|
|
it++;
|
|
return *it;
|
|
}
|
|
|
|
string ExportedSymbols::randomUserDefinedType(shared_ptr<RandomEngine> _rand)
|
|
{
|
|
auto it = types.begin();
|
|
auto idx = (*_rand)() % types.size();
|
|
for (size_t i = 0; i < idx; i++)
|
|
it++;
|
|
return *it;
|
|
}
|
|
|
|
bool FunctionState::operator==(const FunctionState& _other)
|
|
{
|
|
if (_other.inputParameters.size() != inputParameters.size())
|
|
return false;
|
|
else
|
|
{
|
|
unsigned index = 0;
|
|
for (auto const& type: _other.inputParameters)
|
|
if (type.first->type != inputParameters[index++].first->type)
|
|
return false;
|
|
return name == _other.name;
|
|
}
|
|
}
|
|
|
|
string TestState::randomPath()
|
|
{
|
|
solAssert(!empty(), "Solc custom mutator: Null test state");
|
|
for (auto iter = sourceUnitStates.begin(); iter != sourceUnitStates.end(); )
|
|
{
|
|
// Choose this element equally at random
|
|
if (MP{}.chooseOneOfN(sourceUnitStates.size(), rand))
|
|
return iter->first;
|
|
// If not chosen, increment iterator checking if this
|
|
// is the last element. If it is not the last element
|
|
// continue, otherwise choose it.
|
|
else if (++iter == sourceUnitStates.end())
|
|
return (--iter)->first;
|
|
}
|
|
solAssert(false, "Solc custom mutator: No source path chosen");
|
|
}
|
|
|
|
GeneratorPtr GeneratorBase::randomGenerator()
|
|
{
|
|
solAssert(generators.size() > 0, "Invalid hierarchy");
|
|
|
|
auto it = generators.begin();
|
|
auto idx = (*rand)() % generators.size();
|
|
for (size_t i = 0; i < idx; i++)
|
|
it++;
|
|
return *it;
|
|
}
|
|
|
|
string TestCaseGenerator::randomPath()
|
|
{
|
|
solAssert(!empty(), "Solc custom mutator: Invalid source unit");
|
|
return path(MP{}.distributionOneToN(m_numSourceUnits, rand) - 1);
|
|
}
|
|
|
|
string ExpressionGenerator::doubleQuotedStringLiteral()
|
|
{
|
|
string s = MP{}.generateRandomAsciiString(
|
|
MP{}.distributionOneToN(s_maxStringLength, rand),
|
|
rand
|
|
);
|
|
return s;
|
|
}
|
|
|
|
string ExpressionGenerator::hexLiteral()
|
|
{
|
|
size_t lengthInBytes = std::visit(SolidityType::TypeIndexVisitor{}, m_type.type.first) + 1;
|
|
string s = MP{}.generateRandomHexString(
|
|
MP{}.distributionOneToN(lengthInBytes, rand),
|
|
rand
|
|
);
|
|
return "hex\"" + s + "\"";
|
|
}
|
|
|
|
string ExpressionGenerator::numberLiteral()
|
|
{
|
|
size_t lengthInBytes = std::visit(SolidityType::TypeIndexVisitor{}, m_type.type.first) + 1;
|
|
if (lengthInBytes > 32)
|
|
lengthInBytes -= 32;
|
|
auto [n, s] = MP{}.generateRandomNumberLiteral(
|
|
MP{}.distributionOneToN(lengthInBytes, rand),
|
|
rand
|
|
);
|
|
if (n == MP::NumberLiteral::HEX)
|
|
return "hex\"" + s + "\"";
|
|
else
|
|
return s;
|
|
}
|
|
|
|
string ExpressionGenerator::addressLiteral()
|
|
{
|
|
string addr = "0x" + MP{}.generateRandomHexString(20, rand);
|
|
return getChecksummedAddress(addr);
|
|
}
|
|
|
|
string ExpressionGenerator::literal()
|
|
{
|
|
string lit;
|
|
switch (m_type.typeCategory)
|
|
{
|
|
case SolidityType::TypeCategory::ADDRESS:
|
|
lit = addressLiteral();
|
|
break;
|
|
case SolidityType::TypeCategory::BOOL:
|
|
lit = boolLiteral();
|
|
break;
|
|
case SolidityType::TypeCategory::BYTES:
|
|
lit = hexLiteral();
|
|
break;
|
|
case SolidityType::TypeCategory::INTEGER:
|
|
lit = numberLiteral();
|
|
break;
|
|
case SolidityType::TypeCategory::TYPEMAX:
|
|
solAssert(false, "");
|
|
}
|
|
return typeString() + "(" + lit + ")";
|
|
}
|
|
|
|
string ExpressionGenerator::identifier()
|
|
{
|
|
vector<string> ids;
|
|
// TODO: Implement typed identifiers
|
|
// if (state->currentSourceState().symbols())
|
|
// for (auto &item: state->currentSourceState().exportedSymbols.symbols)
|
|
// ids.push_back(item);
|
|
if (auto f = state->currentSourceState().currentFunction(); f && f->identifiers())
|
|
{
|
|
for (auto& item: f->inputParameters)
|
|
if (item.first->typeCategory == m_type.typeCategory)
|
|
ids.push_back(item.second);
|
|
for (auto& item: f->returnParameters)
|
|
if (item.first->typeCategory == m_type.typeCategory)
|
|
ids.push_back(item.second);
|
|
for (auto& item: f->locals)
|
|
if (item.first->typeCategory == m_type.typeCategory)
|
|
ids.push_back(item.second);
|
|
}
|
|
if (ids.size() > 0)
|
|
return ids[MP{}.distributionOneToN(ids.size(), rand) - 1];
|
|
else
|
|
return "";
|
|
}
|
|
|
|
string ExpressionGenerator::expression()
|
|
{
|
|
if (nestingDepthTooHigh())
|
|
return literal();
|
|
|
|
incrementNestingDepth();
|
|
|
|
string expr;
|
|
switch (MP{}.distributionOneToN(Type::TYPEMAX, rand) - 1)
|
|
{
|
|
case Type::INDEXACCESS:
|
|
// TODO: Implement index access
|
|
expr = literal();
|
|
// expr = Whiskers(R"(<baseExpr>[<indexExpr>])")
|
|
// ("baseExpr", expression())
|
|
// ("indexExpr", expression())
|
|
// .render();
|
|
break;
|
|
case Type::INDEXRANGEACCESS:
|
|
// TODO: Implement index range access
|
|
expr = literal();
|
|
// expr = Whiskers(R"(<baseExpr>[<startExpr>:<endExpr>])")
|
|
// ("baseExpr", expression())
|
|
// ("startExpr", expression())
|
|
// ("endExpr", expression())
|
|
// .render();
|
|
break;
|
|
case Type::METATYPE:
|
|
// TODO: Implement metatype
|
|
expr = literal();
|
|
// expr = Whiskers(R"(type(<typeName>))")
|
|
// ("typeName", randomTypeString())
|
|
// .render();
|
|
break;
|
|
case Type::BITANDOP:
|
|
if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER)
|
|
expr = expression() + " & " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
case Type::BITOROP:
|
|
if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER)
|
|
expr = expression() + " | " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
case Type::BITXOROP:
|
|
if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER)
|
|
expr = expression() + " ^ " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
case Type::ANDOP:
|
|
if (m_type.typeCategory == SolidityType::TypeCategory::BOOL)
|
|
expr = expression() + " && " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
case Type::OROP:
|
|
if (m_type.typeCategory == SolidityType::TypeCategory::BOOL)
|
|
expr = expression() + " || " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
case Type::NEWEXPRESSION:
|
|
// TODO: Implement new expression
|
|
expr = literal();
|
|
// expr = Whiskers(R"(new <typeName>)")
|
|
// ("typeName", randomTypeString())
|
|
// .render();
|
|
break;
|
|
case Type::CONDITIONAL:
|
|
{
|
|
SolidityType oldType = m_type;
|
|
setType(SolidityType(SolidityType::TypeCategory::BOOL, rand));
|
|
string condition = expression();
|
|
setType(oldType);
|
|
expr = condition + " ? " + expression() + " : " + expression();
|
|
break;
|
|
}
|
|
case Type::ASSIGNMENT:
|
|
{
|
|
// TODO: Implement lvalue expressions
|
|
auto id = identifier();
|
|
if (!id.empty())
|
|
expr = id + " = " + expression();
|
|
else
|
|
expr = literal();
|
|
break;
|
|
}
|
|
case Type::INLINEARRAY:
|
|
{
|
|
// TODO: Implement inline array expressions
|
|
expr = literal();
|
|
// vector<string> exprs{};
|
|
// size_t numElementsInTuple = MP{}.distributionOneToN(s_maxElementsInlineArray, rand);
|
|
// for (size_t i = 0; i < numElementsInTuple; i++)
|
|
// exprs.push_back(expression());
|
|
// expr = Whiskers(R"([<inlineArrayExpression>])")
|
|
// ("inlineArrayExpression", boost::algorithm::join(exprs, ", "))
|
|
// .render();
|
|
break;
|
|
}
|
|
case Type::IDENTIFIER:
|
|
{
|
|
auto id = identifier();
|
|
if (id.empty())
|
|
expr = literal();
|
|
else
|
|
expr = id;
|
|
break;
|
|
}
|
|
case Type::LITERAL:
|
|
expr = literal();
|
|
break;
|
|
case Type::TUPLE:
|
|
{
|
|
// TODO: Implement tuple
|
|
expr = literal();
|
|
// vector<string> exprs{};
|
|
// size_t numElementsInTuple = MP{}.distributionOneToN(s_maxElementsInTuple, rand);
|
|
// for (size_t i = 0; i < numElementsInTuple; i++)
|
|
// exprs.push_back(expression());
|
|
// expr = Whiskers(R"((<tupleExpression>))")
|
|
// ("tupleExpression", boost::algorithm::join(exprs, ", "))
|
|
// .render();
|
|
break;
|
|
}
|
|
default:
|
|
expr = literal();
|
|
}
|
|
// Typed expression
|
|
return typeString() + "(" + expr + ")";
|
|
}
|
|
|
|
string ExpressionGenerator::visit()
|
|
{
|
|
string expr{};
|
|
if (m_compileTimeConstantExpressionsOnly)
|
|
// TODO: Reference identifiers that point to
|
|
// compile time constant expressions.
|
|
return literal();
|
|
else
|
|
return expression();
|
|
}
|
|
|
|
string StateVariableDeclarationGenerator::visibility()
|
|
{
|
|
switch (MP{}.distributionOneToN(Visibility::VISIBILITYMAX, rand) - 1)
|
|
{
|
|
case Visibility::INTERNAL:
|
|
return "internal";
|
|
case Visibility::PRIVATE:
|
|
return "private";
|
|
case Visibility::PUBLIC:
|
|
return "public";
|
|
default:
|
|
solAssert(false, "");
|
|
}
|
|
}
|
|
|
|
void StateVariableDeclarationGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<ExpressionGenerator>(),
|
|
mutator->generator<NatSpecGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string StateVariableDeclarationGenerator::visit()
|
|
{
|
|
string id = identifier();
|
|
string vis = visibility();
|
|
bool immutable = false;
|
|
bool constant = MP{}.chooseOneOfN(2, rand);
|
|
if (!constant)
|
|
immutable = MP{}.chooseOneOfN(2, rand);
|
|
solAssert(!(constant && immutable), "State variable cannot be both constant and immutable");
|
|
// Immutables cannot have a non-value type
|
|
if (immutable)
|
|
generator<ExpressionGenerator>()->setValueType();
|
|
string expr = generator<ExpressionGenerator>()->visit();
|
|
string type = generator<ExpressionGenerator>()->typeString();
|
|
// TODO: Actually restrict this setting to public state variables only
|
|
generator<NatSpecGenerator>()->tagCategory(NatSpecGenerator::TagCategory::PUBLICSTATEVAR);
|
|
string natSpecString = generator<NatSpecGenerator>()->visit();
|
|
return Whiskers(m_declarationTemplate)
|
|
("natSpecString", natSpecString)
|
|
("type", type)
|
|
("vis", vis)
|
|
("constant", constant)
|
|
("immutable", immutable)
|
|
("id", id)
|
|
("value", expr)
|
|
.render();
|
|
}
|
|
|
|
//void UserDefinedTypeGenerator::setup()
|
|
//{
|
|
// addGenerators({mutator->generator<FunctionTypeGenerator>()});
|
|
//}
|
|
//
|
|
//string UserDefinedTypeGenerator::visit()
|
|
//{
|
|
// switch (MP{}.distributionOneToN(2, rand))
|
|
// {
|
|
// case 1:
|
|
// if (state->currentSourceState().userDefinedTypes())
|
|
// return state->currentSourceState().exportedSymbols.randomUserDefinedType(rand);
|
|
// else
|
|
// return "uint";
|
|
// case 2:
|
|
// return generator<FunctionTypeGenerator>()->visit();
|
|
// }
|
|
// solAssert(false, "");
|
|
//}
|
|
|
|
string Location::visit()
|
|
{
|
|
switch (loc)
|
|
{
|
|
case Location::Loc::CALLDATA:
|
|
return "calldata";
|
|
case Location::Loc::MEMORY:
|
|
return "memory";
|
|
case Location::Loc::STORAGE:
|
|
return "storage";
|
|
case Location::Loc::STACK:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
string LocationGenerator::visit()
|
|
{
|
|
return "";
|
|
// TODO: Implement locations
|
|
switch (MP{}.distributionOneToN(4, rand))
|
|
{
|
|
case 1:
|
|
return Location(Location::Loc::MEMORY).visit();
|
|
case 2:
|
|
return Location(Location::Loc::STORAGE).visit();
|
|
case 3:
|
|
return Location(Location::Loc::CALLDATA).visit();
|
|
case 4:
|
|
return Location(Location::Loc::STACK).visit();
|
|
}
|
|
solAssert(false, "");
|
|
}
|
|
|
|
void SimpleVarDeclGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<ExpressionGenerator>(),
|
|
mutator->generator<LocationGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string SimpleVarDeclGenerator::visit()
|
|
{
|
|
return Whiskers(simpleVarDeclTemplate)
|
|
("type", generator<ExpressionGenerator>()->typeString())
|
|
("location", generator<LocationGenerator>()->visit())
|
|
("name", "v")
|
|
("assign", true)
|
|
("expression", generator<ExpressionGenerator>()->visit())
|
|
.render();
|
|
|
|
}
|
|
|
|
string ExpressionStatement::visit()
|
|
{
|
|
// TODO: Implement expression generation
|
|
return Whiskers(exprStmtTemplate)("expression", "1").render();
|
|
}
|
|
|
|
void StatementGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<ExpressionGenerator>(),
|
|
mutator->generator<LocationGenerator>(),
|
|
}
|
|
);
|
|
}
|
|
|
|
string StatementGenerator::simpleStatement()
|
|
{
|
|
bool variableDecl = MP{}.chooseOneOfN(2, rand);
|
|
string stmt;
|
|
if (variableDecl)
|
|
{
|
|
if (auto f = state->currentSourceState().currentFunction(); f)
|
|
{
|
|
if (f->numReturns > 0)
|
|
{
|
|
auto t = f->returnParameters[
|
|
MP{}.distributionOneToN(f->returnParameters.size(), rand) - 1];
|
|
generator<ExpressionGenerator>()->setType(t.first);
|
|
stmt = t.second + " = " + generator<ExpressionGenerator>()->visit() + ";\n";
|
|
}
|
|
else
|
|
{
|
|
auto t = generator<ExpressionGenerator>()->randomType();
|
|
f->addVariable(t);
|
|
generator<ExpressionGenerator>()->setType(*t);
|
|
stmt = t->type.second +
|
|
" " +
|
|
generator<LocationGenerator>()->visit() +
|
|
" " +
|
|
"v" + to_string(f->numLocals - 1) +
|
|
" = " +
|
|
generator<ExpressionGenerator>()->visit() +
|
|
";" +
|
|
"\n";
|
|
}
|
|
}
|
|
else
|
|
stmt = generator<ExpressionGenerator>()->visit() + ";\n";
|
|
}
|
|
else
|
|
stmt = generator<ExpressionGenerator>()->visit() + ";" + "\n";
|
|
return stmt;
|
|
}
|
|
|
|
string StatementGenerator::blockStatement()
|
|
{
|
|
static size_t constexpr maxBlockStmts = 3;
|
|
string stmt = "{";
|
|
if (auto f = state->currentSourceState().currentFunction(); f && f->returnParameters.size() > 0)
|
|
// Always assign value to return parameters
|
|
for (auto t: f->returnParameters)
|
|
{
|
|
generator<ExpressionGenerator>()->setType(t.first);
|
|
stmt = t.second + " = " + generator<ExpressionGenerator>()->visit() + ";\n";
|
|
}
|
|
// Add pseudo randomly generated statements
|
|
for (size_t i = 0; i < MP{}.distributionOneToN(maxBlockStmts, rand); i++)
|
|
stmt += statement();
|
|
stmt += "}";
|
|
return stmt;
|
|
}
|
|
|
|
string StatementGenerator::statement()
|
|
{
|
|
if (nestingDepthTooHigh())
|
|
return simpleStatement();
|
|
|
|
incrementNestingDepth();
|
|
|
|
string stmt;
|
|
switch (randomType((*rand)()))
|
|
{
|
|
case Type::BLOCK:
|
|
{
|
|
stmt = blockStatement();
|
|
break;
|
|
}
|
|
case Type::SIMPLE:
|
|
stmt = simpleStatement();
|
|
break;
|
|
case Type::IF:
|
|
generator<ExpressionGenerator>()->setType(
|
|
{SolidityType::TypeCategory::BOOL, rand}
|
|
);
|
|
stmt = "if(" + generator<ExpressionGenerator>()->visit() + ")";
|
|
stmt += blockStatement();
|
|
break;
|
|
case Type::FOR:
|
|
{
|
|
bool loop = m_loop;
|
|
m_loop = true;
|
|
stmt = "for(" + simpleStatement();
|
|
std::cout << stmt << std::endl;
|
|
generator<ExpressionGenerator>()->setType(
|
|
{SolidityType::TypeCategory::BOOL, rand}
|
|
);
|
|
stmt += generator<ExpressionGenerator>()->visit() +
|
|
"; " +
|
|
generator<ExpressionGenerator>()->visit() +
|
|
")";
|
|
std::cout << stmt << std::endl;
|
|
stmt += blockStatement();
|
|
m_loop = loop;
|
|
break;
|
|
}
|
|
case Type::WHILE:
|
|
{
|
|
bool loop = m_loop;
|
|
m_loop = true;
|
|
generator<ExpressionGenerator>()->setType(
|
|
{SolidityType::TypeCategory::BOOL, rand}
|
|
);
|
|
stmt = "while(" + generator<ExpressionGenerator>()->visit() + ")";
|
|
stmt += blockStatement();
|
|
m_loop = loop;
|
|
break;
|
|
}
|
|
case Type::DOWHILE:
|
|
{
|
|
bool loop = m_loop;
|
|
m_loop = true;
|
|
stmt += "do" + blockStatement();
|
|
generator<ExpressionGenerator>()->setType(
|
|
{SolidityType::TypeCategory::BOOL, rand}
|
|
);
|
|
stmt += "while(" + generator<ExpressionGenerator>()->visit() + ");";
|
|
m_loop = loop;
|
|
break;
|
|
}
|
|
case Type::CONTINUE:
|
|
if (m_loop)
|
|
stmt = "continue;";
|
|
break;
|
|
case Type::BREAK:
|
|
if (m_loop)
|
|
stmt = "break;";
|
|
break;
|
|
case Type::TRY:
|
|
// TODO: Implement try
|
|
break;
|
|
case Type::RETURN:
|
|
{
|
|
if (state->currentSourceState().currentFunction())
|
|
{
|
|
size_t numReturns = state->currentSourceState().currentFunction()->numReturns;
|
|
if (numReturns > 0)
|
|
{
|
|
stmt = "return (";
|
|
string sep{};
|
|
for (size_t i = 0; i < numReturns; i++)
|
|
{
|
|
stmt += sep + "r" + to_string(i);
|
|
if (sep.empty())
|
|
sep = ", ";
|
|
}
|
|
stmt += ");\n";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Type::EMIT:
|
|
// TODO
|
|
break;
|
|
case Type::ASSEMBLY:
|
|
stmt = "assembly {}";
|
|
break;
|
|
default:
|
|
stmt = simpleStatement();
|
|
break;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
string StatementGenerator::visit()
|
|
{
|
|
static size_t constexpr maxStatements = 5;
|
|
ostringstream os;
|
|
os << "{" << std::endl;
|
|
for (size_t i = 0; i < maxStatements; i++)
|
|
os << statement();
|
|
os << "}";
|
|
return os.str();
|
|
}
|
|
|
|
string VariableDeclaration::visit()
|
|
{
|
|
return Whiskers(varDeclTemplate)
|
|
("type", "uint"/*type->visit()*/)
|
|
("location", location.visit())
|
|
("name", identifier)
|
|
.render();
|
|
}
|
|
|
|
string VariableDeclarationGenerator::identifier()
|
|
{
|
|
string id = "v" + to_string(MP{}.distributionOneToN(10, rand));
|
|
return id;
|
|
}
|
|
|
|
void VariableDeclarationGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<LocationGenerator>(),
|
|
mutator->generator<ExpressionGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string VariableDeclarationGenerator::visit()
|
|
{
|
|
string type = generator<ExpressionGenerator>()->typeString();
|
|
string location = generator<LocationGenerator>()->visit();
|
|
return type + " " + location + " " + identifier();
|
|
}
|
|
|
|
void ParameterListGenerator::setup()
|
|
{
|
|
addGenerators({mutator->generator<VariableDeclarationGenerator>()});
|
|
}
|
|
|
|
string ParameterListGenerator::visit()
|
|
{
|
|
size_t numParameters = MP{}.distributionOneToN(4, rand);
|
|
ostringstream out;
|
|
string sep{};
|
|
for (size_t i = 0; i < numParameters; i++)
|
|
{
|
|
out << sep << generator<VariableDeclarationGenerator>()->visit();
|
|
if (sep.empty())
|
|
sep = ", ";
|
|
}
|
|
return out.str();
|
|
}
|
|
|
|
string FunctionDefinitionGenerator::functionIdentifier()
|
|
{
|
|
switch (MP{}.distributionOneToN(3, rand))
|
|
{
|
|
case 1:
|
|
return "f" + to_string(MP{}.distributionOneToN(10, rand));
|
|
case 2:
|
|
return "fallback";
|
|
case 3:
|
|
return "receive";
|
|
}
|
|
solAssert(false, "Invalid function identifier");
|
|
}
|
|
|
|
void FunctionDefinitionGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<ParameterListGenerator>(),
|
|
mutator->generator<ExpressionGenerator>(),
|
|
mutator->generator<NatSpecGenerator>(),
|
|
mutator->generator<StatementGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string FunctionDefinitionGenerator::visit()
|
|
{
|
|
m_functionState = make_shared<FunctionState>();
|
|
state->currentSourceState().enterFunction(m_functionState);
|
|
string identifier = functionIdentifier();
|
|
if (!state->currentSourceState().exportedSymbols.symbols.count(identifier))
|
|
state->currentSourceState().exportedSymbols.symbols.insert(identifier);
|
|
else
|
|
return "";
|
|
m_functionState->setName(identifier);
|
|
string modInvocation = "";
|
|
string virtualise = m_freeFunction ? "" : MP{}.chooseOneOfNStrings({"virtual", ""}, rand);
|
|
string override = "";
|
|
string visibility = m_freeFunction ? "" : MP{}.chooseOneOfNStrings(s_visibility, rand);
|
|
string mutability = m_freeFunction ?
|
|
MP{}.chooseOneOfNStrings(s_freeFunctionMutability, rand) :
|
|
MP{}.chooseOneOfNStrings(s_mutability, rand);
|
|
|
|
size_t numInputs = MP{}.distributionOneToN(4, rand) - 1;
|
|
string sep{};
|
|
string inputs{};
|
|
for (size_t i = 0; i < numInputs; i++)
|
|
{
|
|
string location;
|
|
auto inp = generator<ExpressionGenerator>()->randomType();
|
|
if (inp->type.second == "bytes")
|
|
location = MP{}.chooseOneOfN(2, rand) ? "memory" : "calldata";
|
|
m_functionState->addInput(inp);
|
|
inputs += sep + inp->type.second + " " + location + " " + "i" + to_string(m_functionState->numInputs - 1);
|
|
if (sep.empty())
|
|
sep = ", ";
|
|
}
|
|
sep.clear();
|
|
size_t numReturns = MP{}.distributionOneToN(4, rand) - 1;
|
|
string returns{};
|
|
for (size_t i = 0; i < numReturns; i++)
|
|
{
|
|
string location;
|
|
auto r = generator<ExpressionGenerator>()->randomType();
|
|
if (r->type.second == "bytes")
|
|
location = MP{}.chooseOneOfN(2, rand) ? "memory" : "calldata";
|
|
m_functionState->addReturn(r);
|
|
returns += sep + r->type.second + " " + location + " " + "r" + to_string(m_functionState->numReturns - 1);
|
|
if (sep.empty())
|
|
sep = ", ";
|
|
}
|
|
|
|
generator<NatSpecGenerator>()->tagCategory(NatSpecGenerator::TagCategory::FUNCTION);
|
|
string natSpecString = generator<NatSpecGenerator>()->visit();
|
|
if (
|
|
(visibility == "internal" || visibility == "private") &&
|
|
mutability == "payable"
|
|
)
|
|
visibility = "public";
|
|
if (visibility == "private" && virtualise == "virtual")
|
|
visibility = "public";
|
|
state->currentSourceState().leaveFunction();
|
|
return Whiskers(m_functionTemplate)
|
|
("natSpecString", natSpecString)
|
|
("id", identifier)
|
|
("paramList", inputs)
|
|
("visibility", visibility)
|
|
("stateMutability", mutability)
|
|
("modInvocation", modInvocation)
|
|
("virtual", virtualise)
|
|
("overrideSpec", override)
|
|
("return", !returns.empty())
|
|
("retParamList", returns)
|
|
("definition", true)
|
|
("body", generator<StatementGenerator>()->visit())
|
|
.render();
|
|
}
|
|
|
|
string EnumDeclaration::visit()
|
|
{
|
|
string name = enumName();
|
|
if (!state->currentSourceState().exportedSymbols.types.count(name))
|
|
state->currentSourceState().exportedSymbols.types.insert(name);
|
|
else
|
|
return "";
|
|
|
|
string members{};
|
|
string sep{};
|
|
for (size_t i = 0; i < MP{}.distributionOneToN(s_maxMembers, rand); i++)
|
|
{
|
|
members += sep + "M" + to_string(i);
|
|
if (sep.empty())
|
|
sep = ", ";
|
|
}
|
|
return Whiskers(enumTemplate)
|
|
("name", name)
|
|
("members", members)
|
|
.render();
|
|
}
|
|
|
|
//void FunctionTypeGenerator::setup()
|
|
//{
|
|
// addGenerators(
|
|
// {
|
|
// mutator->generator<TypeGenerator>()
|
|
// }
|
|
// );
|
|
//}
|
|
//
|
|
//string FunctionTypeGenerator::visit()
|
|
//{
|
|
// string visibility = MP{}.chooseOneOfNStrings(s_visibility, rand);
|
|
// size_t numParams = MP{}.distributionOneToN(4, rand) - 1;
|
|
// size_t numReturns = MP{}.distributionOneToN(4, rand) - 1;
|
|
// string sep{};
|
|
// string params{};
|
|
// for (size_t i = 0; i < numParams; i++)
|
|
// {
|
|
// params += sep + generator<TypeGenerator>()->visit();
|
|
// if (sep.empty())
|
|
// sep = ", ";
|
|
// }
|
|
// sep = {};
|
|
// string returns{};
|
|
// for (size_t i = 0; i < numReturns; i++)
|
|
// {
|
|
// returns += sep + generator<TypeGenerator>()->visit();
|
|
// if (sep.empty())
|
|
// sep = ", ";
|
|
// }
|
|
// return Whiskers(m_functionTypeTemplate)
|
|
// ("paramList", params)
|
|
// ("visibility", visibility)
|
|
// ("stateMutability", MP{}.chooseOneOfNStrings(FunctionDefinitionGenerator::s_mutability, rand))
|
|
// ("return", !returns.empty())
|
|
// ("retParamList", returns)
|
|
// .render();
|
|
//}
|
|
|
|
void ConstantVariableDeclaration::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<ExpressionGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string ConstantVariableDeclaration::visit()
|
|
{
|
|
// TODO: Set compileTimeConstantExpressionsOnly in
|
|
// ExpressionGenerator to true
|
|
string type = generator<ExpressionGenerator>()->typeString();
|
|
return Whiskers(constantVarDeclTemplate)
|
|
("type", type)
|
|
("name", "c")
|
|
("expression", type + "(" + generator<ExpressionGenerator>()->visit() + ")")
|
|
.render();
|
|
}
|
|
|
|
void ContractDefinitionGenerator::setup()
|
|
{
|
|
addGenerators(
|
|
{
|
|
mutator->generator<StateVariableDeclarationGenerator>(),
|
|
mutator->generator<FunctionDefinitionGenerator>(),
|
|
mutator->generator<NatSpecGenerator>()
|
|
}
|
|
);
|
|
}
|
|
|
|
string ContractDefinitionGenerator::visit()
|
|
{
|
|
mutator->generator<FunctionDefinitionGenerator>()->contractFunctionMode();
|
|
string stateVar = generator<StateVariableDeclarationGenerator>()->visit();
|
|
string func = generator<FunctionDefinitionGenerator>()->visit();
|
|
generator<NatSpecGenerator>()->tagCategory(NatSpecGenerator::TagCategory::CONTRACT);
|
|
string natSpecString = generator<NatSpecGenerator>()->visit();
|
|
mutator->generator<FunctionDefinitionGenerator>()->freeFunctionMode();
|
|
// TODO: Implement inheritance
|
|
return Whiskers(m_contractTemplate)
|
|
("natSpecString", natSpecString)
|
|
("abstract", MP{}.chooseOneOfN(s_abstractInvProb, rand))
|
|
("id", "Cx")
|
|
("inheritance", false)
|
|
("inheritanceSpecifierList", "X")
|
|
("stateVar", stateVar)
|
|
("function", func)
|
|
.render();
|
|
}
|
|
|
|
void TestState::print()
|
|
{
|
|
std::cout << "Printing test state" << std::endl;
|
|
for (auto const& item: sourceUnitStates)
|
|
std::cout << "Path: " << item.first << std::endl;
|
|
}
|
|
|
|
string TestState::randomNonCurrentPath()
|
|
{
|
|
solAssert(size() >= 2, "Solc custom mutator: Invalid test state");
|
|
string fallBackPath{};
|
|
for (auto const& item: sourceUnitStates)
|
|
{
|
|
string iterPath = item.first;
|
|
if (iterPath != currentSourceName)
|
|
{
|
|
// Fallback to first encountered non current path
|
|
fallBackPath = iterPath;
|
|
if (MP{}.chooseOneOfN(size() - 1, rand))
|
|
return iterPath;
|
|
}
|
|
}
|
|
return fallBackPath;
|
|
}
|
|
|
|
string ImportGenerator::visit()
|
|
{
|
|
state->print();
|
|
/*
|
|
* Case 1: No source units defined
|
|
* Case 2: One source unit defined
|
|
* Case 3: At least two source units defined
|
|
*/
|
|
// No import
|
|
if (state->empty())
|
|
return {};
|
|
// Self import with a small probability
|
|
else if (state->size() == 1)
|
|
{
|
|
if (MP{}.chooseOneOfN(s_selfImportInvProb, rand))
|
|
return Whiskers(m_importPathAs)
|
|
("path", state->randomPath())
|
|
("as", false)
|
|
.render();
|
|
else
|
|
return {};
|
|
}
|
|
// Import pseudo randomly choosen source unit
|
|
else
|
|
{
|
|
string importPath = state->randomNonCurrentPath();
|
|
auto importedSymbols = state->sourceUnitStates[importPath].exportedSymbols.symbols;
|
|
state->currentSourceState().exportedSymbols.symbols.insert(
|
|
importedSymbols.begin(),
|
|
importedSymbols.end()
|
|
);
|
|
return Whiskers(m_importPathAs)
|
|
("path", importPath)
|
|
("as", false)
|
|
.render();
|
|
}
|
|
}
|
|
|
|
NatSpecGenerator::Tag NatSpecGenerator::randomTag(TagCategory _category)
|
|
{
|
|
return s_tagLookup[_category][MP{}.distributionOneToN(s_tagLookup[_category].size(), rand) - 1];
|
|
}
|
|
|
|
string NatSpecGenerator::randomNatSpecString(TagCategory _category)
|
|
{
|
|
if (m_nestingDepth > s_maxNestedTags)
|
|
return {};
|
|
else
|
|
{
|
|
m_nestingDepth++;
|
|
string tag{};
|
|
switch (randomTag(_category))
|
|
{
|
|
case Tag::TITLE:
|
|
tag = "@title";
|
|
break;
|
|
case Tag::AUTHOR:
|
|
tag = "@author";
|
|
break;
|
|
case Tag::NOTICE:
|
|
tag = "@notice";
|
|
break;
|
|
case Tag::DEV:
|
|
tag = "@dev";
|
|
break;
|
|
case Tag::PARAM:
|
|
tag = "@param";
|
|
break;
|
|
case Tag::RETURN:
|
|
tag = "@return";
|
|
break;
|
|
case Tag::INHERITDOC:
|
|
tag = "@inheritdoc";
|
|
break;
|
|
}
|
|
return Whiskers(m_tagTemplate)
|
|
("tag", tag)
|
|
("random", MP{}.generateRandomAsciiString(s_maxTextLength, rand))
|
|
("recurse", randomNatSpecString(_category))
|
|
.render();
|
|
}
|
|
}
|
|
|
|
string NatSpecGenerator::visit()
|
|
{
|
|
// TODO: Enable Natspec strings once we have better precision
|
|
#if 1
|
|
return "";
|
|
#else
|
|
reset();
|
|
return Whiskers(R"(<nl>/// <natSpecString><nl>)")
|
|
("natSpecString", randomNatSpecString(m_tag))
|
|
("nl", "\n")
|
|
.render();
|
|
#endif
|
|
}
|
|
|
|
string SolidityGenerator::generateTestProgram()
|
|
{
|
|
createGenerators();
|
|
for (auto &g: m_generators)
|
|
std::visit(AddDependenciesVisitor{}, g);
|
|
string program = generator<TestCaseGenerator>()->visit();
|
|
destroyGenerators();
|
|
destroyState();
|
|
return program;
|
|
}
|