Generate multiple contract tests per iteration and minor cleanup

This commit is contained in:
Bhargava Shastry 2020-04-16 19:06:07 +02:00
parent b2c624bf9c
commit 537e5b8e6c
4 changed files with 86 additions and 346 deletions

View File

@ -25,8 +25,8 @@
using namespace solidity::test::solprotofuzzer::adaptor;
using namespace solidity::test::solprotofuzzer;
using namespace std;
using namespace solidity::util;
using namespace std;
namespace
{
@ -721,46 +721,31 @@ bool SolContract::validTest()
}
/// Must be called only when validTest() returns true.
tuple<string, string, string> SolContract::validContractTest()
void SolContract::validContractTests(map<string, map<string, string>>& _testSet)
{
string chosenContractName;
string chosenFunctionName{};
string expectedOutput{};
// If we can provide a test case right away, do so.
// Add test cases in current contract
if (!m_contractFunctionMap.empty())
{
chosenContractName = name();
unsigned functionIdx = random() % m_contractFunctionMap.size();
unsigned mapIdx = 0;
_testSet[chosenContractName] = map<string, string>{};
for (auto &e: m_contractFunctionMap)
{
if (functionIdx == mapIdx)
{
chosenFunctionName = e.first;
expectedOutput = e.second;
break;
}
mapIdx++;
}
solAssert(m_contractFunctionMap.count(chosenFunctionName), "Sol proto adaptor: Invalid contract function chosen");
return make_tuple(chosenContractName, chosenFunctionName, expectedOutput);
_testSet[chosenContractName].insert(make_pair(e.first, e.second));
}
// Otherwise, continue search in base contracts
else
{
for (auto &b: m_baseContracts)
if (b->type() == SolBaseContract::BaseType::CONTRACT)
if (b->contract()->validTest())
return b->contract()->validContractTest();
}
solAssert(false, "Sol proto adaptor: No contract test found");
// Continue search in base contracts
for (auto &b: m_baseContracts)
if (b->type() == SolBaseContract::BaseType::CONTRACT)
if (b->contract()->validTest())
b->contract()->validContractTests(_testSet);
}
tuple<string, string, string> SolContract::pseudoRandomTest()
{
solAssert(validTest(), "Sol proto adaptor: Please call validTest()");
return validContractTest();
}
//tuple<string, string, string> SolContract::pseudoRandomTest()
//{
// solAssert(validTest(), "Sol proto adaptor: Please call validTest()");
// return validContractTests();
//}
void SolContract::addFunctions(Contract const& _contract)
{
@ -1294,7 +1279,7 @@ string SolContract::str()
("bases", bases.str())
("isAbstract", abstract())
("contractName", name())
("inheritance", m_baseContracts.size() > 0)
("inheritance", !m_baseContracts.empty())
("baseNames", baseNames())
("functions", functions.str())
.render();
@ -1369,12 +1354,12 @@ library <name> {
bool SolLibrary::validTest() const
{
return m_publicFunctionMap.size() > 0;
return !m_publicFunctionMap.empty();
}
pair<string, string> SolLibrary::pseudoRandomTest()
{
solAssert(m_publicFunctionMap.size() > 0, "Sol proto adaptor: Empty library map");
solAssert(!m_publicFunctionMap.empty(), "Sol proto adaptor: Empty library map");
string chosenFunction;
unsigned numFunctions = m_publicFunctionMap.size();
unsigned functionIndex = randomNumber() % numFunctions;
@ -1390,4 +1375,4 @@ pair<string, string> SolLibrary::pseudoRandomTest()
}
solAssert(m_publicFunctionMap.count(chosenFunction), "Sol proto adaptor: Invalid library function chosen");
return pair(chosenFunction, m_publicFunctionMap[chosenFunction]);
}
}

View File

@ -26,6 +26,8 @@
namespace solidity::test::solprotofuzzer::adaptor
{
/// Forward declarations
/// Solidity contract abstraction class
struct SolContract;
/// Solidity interface abstraction class
@ -38,31 +40,6 @@ struct SolInterfaceFunction;
struct SolContractFunction;
/// Solidity library function abstraction class
struct SolLibraryFunction;
/// Solidity contract function override abstraction class
struct CFunctionOverride;
/// Solidity interface function override abstraction class
struct IFunctionOverride;
/// Type that defines a contract function override as a variant of interface or
/// contract function types.
using OverrideFunction = std::variant<std::unique_ptr<SolInterfaceFunction const>, std::unique_ptr<SolContractFunction const>>;
/// Type that defines a list of base contracts as a list of variants of interface or
/// contract types.
using BaseContracts = std::vector<std::variant<std::shared_ptr<SolInterface const>, std::shared_ptr<SolContract const>>>;
/// Type that defines a contract function override as a pair of base contracts
/// and its (their) function.
using OverrideCFunction = std::pair<BaseContracts, OverrideFunction>;
/// Type that defines an interface function override as a pair of interface
/// and its function.
using OverrideIFunction = std::pair<std::vector<std::shared_ptr<SolInterface const>>, std::unique_ptr<SolInterfaceFunction const>>;
/// Type that defines a map of interface overrides
using InterfaceOverrideMap = std::map<std::shared_ptr<SolInterfaceFunction>, std::vector<std::shared_ptr<SolInterface>>>;
/// Type that defines an interface function as either a vanilla interface function
/// or an override function.
using IFunction = std::variant<std::unique_ptr<SolInterfaceFunction>, OverrideIFunction>;
/// Type that defines a contract function as either a vanilla contract function
/// or an override function.
using CFunction = std::variant<std::unique_ptr<SolContractFunction>, OverrideCFunction>;
/// Variant type that points to one of contract, interface protobuf messages
using ProtoBaseContract = std::variant<Contract const*, Interface const*>;
@ -369,7 +346,7 @@ struct SolLibrary
}
std::string newReturnValue()
{
return std::to_string(m_returnValue++);
return std::to_string(random());
}
std::string m_libraryName;
@ -392,8 +369,6 @@ struct SolBaseContract
std::shared_ptr<SolRandomNumGenerator> _prng
);
std::variant<std::vector<std::shared_ptr<SolContractFunction>>, std::vector<std::shared_ptr<SolInterfaceFunction>>>
baseFunctions();
BaseType type() const;
std::string name();
std::string str();
@ -409,56 +384,6 @@ struct SolBaseContract
std::string lastBaseName();
std::variant<std::shared_ptr<SolInterface>, std::shared_ptr<SolContract>> m_base;
std::string m_baseName;
std::shared_ptr<SolRandomNumGenerator> m_prng;
};
struct SolFunction
{
enum Type
{
INTERFACE,
CONTRACT
};
using FunctionVariant = std::variant<std::shared_ptr<SolInterfaceFunction>, std::shared_ptr<SolContractFunction>>;
Type operator()(FunctionVariant _var) const;
};
struct ContractFunctionAttributes
{
ContractFunctionAttributes(
std::variant<std::shared_ptr<SolInterfaceFunction>, std::shared_ptr<SolContractFunction>> _solFunction
)
{
if (SolFunction{}(_solFunction) == SolFunction::INTERFACE)
{
auto f = std::get<std::shared_ptr<SolInterfaceFunction>>(_solFunction);
m_name = f->name();
m_mutability = f->mutability();
m_visibility = SolFunctionVisibility::EXTERNAL;
}
else
{
auto f = std::get<std::shared_ptr<SolContractFunction>>(_solFunction);
m_name = f->name();
m_mutability = f->mutability();
m_visibility = f->visibility();
}
}
bool namesake(ContractFunctionAttributes const& _rhs) const;
bool operator==(ContractFunctionAttributes const& _rhs) const;
bool operator!=(ContractFunctionAttributes const& _rhs) const;
void merge(ContractFunctionAttributes const& _rhs);
std::string m_name;
SolFunctionVisibility m_visibility;
SolFunctionStateMutability m_mutability;
std::string m_returnValue;
bool m_virtual;
bool m_override;
bool m_implemeted;
std::vector<std::string> m_bases;
};
struct SolContract
@ -473,25 +398,14 @@ struct SolContract
void merge();
std::string str();
std::string interfaceOverrideStr();
std::string contractOverrideStr();
void disallowedBase(std::shared_ptr<SolBaseContract> _base1, std::shared_ptr<SolBaseContract> _base2);
void addFunctions(Contract const& _contract);
void addBases(Contract const& _contract);
void addOverrides();
void interfaceFunctionOverride(
std::shared_ptr<SolInterface> _base,
std::shared_ptr<SolInterfaceFunction> _function
);
void contractFunctionOverride(
std::shared_ptr<SolContract> _base,
std::shared_ptr<SolContractFunction> _function
);
bool validTest();
std::string baseNames() const;
std::tuple<std::string, std::string, std::string> validContractTest();
std::tuple<std::string, std::string, std::string> pseudoRandomTest();
void validContractTests(
std::map<std::string, std::map<std::string, std::string>>& _testSet
);
unsigned randomNumber() const
{
@ -593,7 +507,6 @@ struct SolInterface
std::string str() const;
/// Returns the Solidity code for all base interfaces
/// inherited by this interface.
std::string baseInterfaceStr() const;
@ -619,198 +532,4 @@ struct SolInterface
std::vector<std::shared_ptr<SolInterface>> m_baseInterfaces;
std::shared_ptr<SolRandomNumGenerator> m_prng;
};
/* Contract functions may be overridden by other contracts. Base and derived contracts
* may either be abstract or non-abstract. That gives us four possibilities:
* - both abstract
* - both non abstract
* - one of them abstract, the other non abstract
*/
struct CFunctionOverride
{
enum class DerivedType
{
ABSTRACTCONTRACT,
CONTRACT
};
CFunctionOverride(
std::variant<std::shared_ptr<SolContract>, std::shared_ptr<SolInterface>> _base,
std::shared_ptr<SolContractFunction> _function,
SolContract* _derived,
bool _implemented,
bool _virtualized,
bool _explicitInheritance,
std::string _returnValue
)
{
m_baseContract = _base;
m_baseFunction = _function;
m_derivedProgram = _derived;
m_implemented = _implemented;
m_virtualized = _virtualized;
m_explicitlyInherited = _explicitInheritance;
m_returnValue = _returnValue;
m_derivedType = _derived->abstract() ? DerivedType::ABSTRACTCONTRACT : DerivedType::CONTRACT;
}
std::string str() const;
std::string name() const;
bool contractFunction() const;
SolFunctionVisibility visibility() const;
SolFunctionStateMutability mutability() const;
std::string commaSeparatedBaseNames() const;
std::string baseName() const;
// std::shared_ptr<SolContract> baseContract() const
// {
// return m_baseContract;
// }
std::shared_ptr<SolContractFunction> baseFunction() const
{
return m_baseFunction;
}
std::variant<std::shared_ptr<SolContract>, std::shared_ptr<SolInterface>> m_baseContract;
std::shared_ptr<SolContractFunction> m_baseFunction;
SolContract* m_derivedProgram;
/// Flag that is true if overridden function is implemented in derived contract
bool m_implemented = false;
/// Flag that is true if overridden function implemented in derived contract is
/// marked virtual
bool m_virtualized = false;
/// Flag that is true if overridden function is redeclared but not implemented
bool m_explicitlyInherited = false;
/// The uint value to be returned if the overridden interface function is implemented
std::string m_returnValue;
DerivedType m_derivedType;
bool implemented() const
{
return m_implemented;
}
bool virtualized() const
{
return m_virtualized;
}
bool explicitlyInherited() const
{
return m_explicitlyInherited;
}
std::string returnValue() const
{
return m_returnValue;
}
};
/* Difference between interface function declaration and interface
* function override is that the former can not be implemented and
* should not be marked virtual i.e., virtual is implicit.
*
* Interface function declarations may be implicitly or explicitly
* inherited by derived interfaces. To explicitly inherit base
* interface's function declaration, derived base must redeclare
* the said function and mark it override. If base interface function
* does not redeclare base interface function, it implicitly inherits
* it from base and exposes it to its derived interfaces.
*
* Interface functions inherited by contracts may be implicitly or
* explicitly inherited. Derived non abstract contracts must explicitly
* override and implement inherited interface functions unless they have
* already been implemented by one of its bases. Abstract contracts
* may implicitly or explicitly inherit base interface functions. If
* explicitly inherited, they must be redeclared and marked override.
* When a base interface function is explicitly inherited by a contract
* it may be marked virtual.
*/
struct IFunctionOverride
{
enum class DerivedType
{
INTERFACE,
ABSTRACTCONTRACT,
CONTRACT
};
IFunctionOverride(
std::shared_ptr<SolInterface> _baseInterface,
std::shared_ptr<SolInterfaceFunction const> _baseFunction,
std::variant<SolInterface*, SolContract*> _derivedProgram,
bool _implement,
bool _virtual,
bool _explicitInherit,
std::string _returnValue
);
std::string str() const;
std::string interfaceStr() const;
std::string contractStr() const;
void setImplement()
{
m_implemented = true;
}
void setVirtual()
{
m_virtualized = true;
}
void setExplicitInherit()
{
m_explicitlyInherited = true;
}
bool implemented() const
{
return m_implemented;
}
bool virtualized() const
{
return m_virtualized;
}
bool explicitlyInherited() const
{
return m_explicitlyInherited;
}
std::string returnValue() const
{
return m_returnValue;
}
std::string baseName() const
{
return m_baseInterface->name();
}
std::shared_ptr<SolInterface> m_baseInterface;
std::shared_ptr<SolInterfaceFunction const> m_baseFunction;
std::variant<SolInterface*, SolContract*> m_derivedProgram;
/// Flag that is true if overridden function is implemented in derived contract
bool m_implemented = false;
/// Flag that is true if overridden function implemented in derived contract is
/// marked virtual
bool m_virtualized = false;
/// Flag that is true if overridden function is redeclared but not implemented
bool m_explicitlyInherited = false;
/// The uint value to be returned if the overridden interface function is implemented
std::string m_returnValue;
DerivedType m_derivedType;
};
}

View File

@ -37,7 +37,7 @@ string ProtoConverter::protoToSolidity(Program const& _p)
string ProtoConverter::visit(TestContract const& _testContract)
{
string testCode;
ostringstream testCode;
string usingLibDecl;
m_libraryTest = false;
@ -47,7 +47,7 @@ string ProtoConverter::visit(TestContract const& _testContract)
{
if (emptyLibraryTests())
{
testCode = Whiskers(R"(
testCode << Whiskers(R"(
return 0;)")
.render();
}
@ -60,7 +60,7 @@ string ProtoConverter::visit(TestContract const& _testContract)
using <libraryName> for uint;)")
("libraryName", get<0>(testTuple))
.render();
testCode = Whiskers(R"(
testCode << Whiskers(R"(
uint x;
if (x.<testFunction>() != <expectedOutput>)
return 1;
@ -73,21 +73,45 @@ string ProtoConverter::visit(TestContract const& _testContract)
}
case TestContract::CONTRACT:
if (emptyContractTests())
testCode = Whiskers(R"(
testCode << Whiskers(R"(
return 0;)")
.render();
else
{
auto testTuple = pseudoRandomContractTest();
testCode = Whiskers(R"(
<contractName> testContract = new <contractName>();
if (testContract.<testFunction>() != <expectedOutput>)
return 1;
return 0;)")
("contractName", get<0>(testTuple))
("testFunction", get<1>(testTuple))
("expectedOutput", get<2>(testTuple))
.render();
unsigned errorCode = 1;
unsigned contractVarIndex = 0;
for (auto &testTuple: m_contractTests)
{
// Do this to avoid stack too deep errors
// We require uint as a return var, so we
// cannot have more than 16 variables without
// running into stack too deep errors
if (contractVarIndex >= s_maxVars)
break;
string contractName = testTuple.first;
string contractVarName = "tc" + to_string(contractVarIndex);
testCode << Whiskers(R"(
<contractName> <contractVarName> = new <contractName>();)")
("contractName", contractName)
("contractVarName", contractVarName)
.render();
for (auto &t: testTuple.second)
{
testCode << Whiskers(R"(
if (<contractVarName>.<testFunction>() != <expectedOutput>)
return <errorCode>;)")
("contractVarName", contractVarName)
("testFunction", t.first)
("expectedOutput", t.second)
("errorCode", to_string(errorCode))
.render();
errorCode++;
}
contractVarIndex++;
}
// Expected return value
testCode << Whiskers(R"(
return 0;)").render();
}
break;
}
@ -101,7 +125,7 @@ contract C {<?isLibrary><usingDecl></isLibrary>
)")
("isLibrary", m_libraryTest)
("usingDecl", usingLibDecl)
("testCode", testCode)
("testCode", testCode.str())
.render();
}
@ -161,7 +185,16 @@ string ProtoConverter::visit(Contract const& _contract)
auto contract = SolContract(_contract, programName(&_contract), m_randomGen);
if (contract.validTest())
{
m_contractTests.push_back(contract.pseudoRandomTest());
map<string, map<string, string>> testSet;
contract.validContractTests(testSet);
for (auto &contractTestSet: testSet)
{
m_contractTests.insert(pair(contractTestSet.first, map<string, string>{}));
for (auto &contractTest: contractTestSet.second)
m_contractTests[contractTestSet.first].insert(
make_pair(contractTest.first, contractTest.second)
);
}
return contract.str();
}
// There is no point in generating a contract that can not provide
@ -220,12 +253,12 @@ tuple<string, string, string> ProtoConverter::pseudoRandomLibraryTest()
return m_libraryTests[index];
}
tuple<string, string, string> ProtoConverter::pseudoRandomContractTest()
{
solAssert(m_contractTests.size() > 0, "Sol proto fuzzer: No contract tests found");
unsigned index = randomNumber() % m_contractTests.size();
return m_contractTests[index];
}
//tuple<string, string, string> ProtoConverter::pseudoRandomContractTest()
//{
// solAssert(m_contractTests.size() > 0, "Sol proto fuzzer: No contract tests found");
// unsigned index = randomNumber() % m_contractTests.size();
// return m_contractTests[index];
//}
void ProtoConverter::openProgramScope(CIL _program)
{

View File

@ -64,11 +64,11 @@ private:
std::string visit(Contract const& _contract);
std::string programName(CIL _program);
std::tuple<std::string, std::string, std::string> pseudoRandomLibraryTest();
std::tuple<std::string, std::string, std::string> pseudoRandomContractTest();
// std::tuple<std::string, std::string, std::string> pseudoRandomContractTest();
void openProgramScope(CIL _program);
unsigned randomNumber();
bool emptyLibrary(Library const& _library)
static bool emptyLibrary(Library const& _library)
{
return _library.funcdef_size() == 0;
}
@ -86,7 +86,10 @@ private:
std::shared_ptr<SolRandomNumGenerator> m_randomGen;
std::map<CIL, std::string> m_programNameMap;
std::vector<std::tuple<std::string, std::string, std::string>> m_libraryTests;
std::vector<std::tuple<std::string, std::string, std::string>> m_contractTests;
std::map<std::string, std::map<std::string, std::string>> m_contractTests;
std::string m_libraryName;
/// Maximum number of local variables in test function
static unsigned constexpr s_maxVars = 15;
};
}