More clean up and bug fixes

This commit is contained in:
Bhargava Shastry 2020-04-13 14:15:17 +02:00
parent 89b5ac510b
commit 1b4bdb1996
4 changed files with 73 additions and 509 deletions

View File

@ -268,6 +268,28 @@ string SolLibraryFunction::str() const
.render();
}
unsigned SolBaseContract::functionIndex()
{
if (type() == BaseType::INTERFACE)
return interface()->functionIndex();
else
{
solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract");
return contract()->functionIndex();
}
}
string SolBaseContract::lastBaseName()
{
if (type() == BaseType::INTERFACE)
return interface()->lastBaseName();
else
{
solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract");
return contract()->lastBaseName();
}
}
SolBaseContract::BaseType SolBaseContract::type() const
{
if (holds_alternative<shared_ptr<SolInterface>>(m_base))
@ -290,6 +312,17 @@ string SolBaseContract::str()
}
}
string SolBaseContract::name()
{
if (type() == BaseType::INTERFACE)
return interface()->name();
else
{
solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract");
return contract()->name();
}
}
SolBaseContract::SolBaseContract(ProtoBaseContract _base, string _name, shared_ptr<SolRandomNumGenerator> _prng)
{
if (holds_alternative<Contract const*>(_base))
@ -512,6 +545,19 @@ interface <programName><?inheritance> is <baseNames></inheritance> {
.render();
}
string SolContract::baseNames() const
{
ostringstream bases;
string separator{};
for (auto &b: m_baseContracts)
{
bases << separator << b->name();
if (separator.empty())
separator = ", ";
}
return bases.str();
}
bool SolContract::validTest() const
{
// Check if at least one contract has one valid test function
@ -824,27 +870,27 @@ void SolContract::addFunctions(Contract const& _contract)
void SolContract::addBases(Contract const& _contract)
{
shared_ptr<SolBaseContract> base;
for (auto &b: _contract.bases())
{
switch (b.contract_or_interface_oneof_case())
{
case ContractOrInterface::kC:
m_baseContracts.push_back(
make_shared<SolBaseContract>(
SolBaseContract(&b.c(), newBaseName(), m_prng)
)
);
base = make_shared<SolBaseContract>(SolBaseContract(&b.c(), newBaseName(), m_prng));
m_baseContracts.push_back(base);
break;
case ContractOrInterface::kI:
m_baseContracts.push_back(
make_shared<SolBaseContract>(
SolBaseContract(&b.i(), newBaseName(), m_prng)
)
);
base = make_shared<SolBaseContract>(SolBaseContract(&b.i(), newBaseName(), m_prng));
m_baseContracts.push_back(base);
break;
case ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET:
break;
}
// Worst case, we override all base functions so we
// increment derived contract's function index by
// this amount.
m_functionIndex += base->functionIndex();
m_lastBaseName = base->lastBaseName();
}
}
@ -889,7 +935,7 @@ string SolContract::contractOverrideStr() const
}
overriddenFunctions << Whiskers(R"(
function <functionName>() <visibility> <stateMutability><?isVirtual> virtual</isVirtual>
override<?multiple>(<baseNames>)</multiple> returns (uint)</isImplemented><body><!isImplemented>;</isImplemented>)")
override<?multiple>(<baseNames>)</multiple> returns (uint)<?isImplemented><body><!isImplemented>;</isImplemented>)")
("functionName", f.first->name())
("visibility", functionVisibility(f.first->visibility()))
("stateMutability", functionMutability(f.first->mutability()))
@ -943,7 +989,7 @@ string SolContract::interfaceOverrideStr() const
}
overriddenFunctions << Whiskers(R"(
function <functionName>() external <stateMutability>
override<?multiple>(<baseNames>)</multiple> returns (uint)</isImplemented><body><!isImplemented>;</isImplemented>)")
override<?multiple>(<baseNames>)</multiple> returns (uint)<?isImplemented><body><!isImplemented>;</isImplemented>)")
("functionName", f.first->name())
("stateMutability", functionMutability(f.first->mutability()))
("multiple", f.second.size() > 1)
@ -962,19 +1008,24 @@ string SolContract::str() const
bases << b->str();
ostringstream functions;
// Print overridden functions
functions << interfaceOverrideStr() << contractOverrideStr();
// Print non-overridden functions
for (auto &f: m_contractFunctions)
functions << f->str();
functions << interfaceOverrideStr() << contractOverrideStr();
return Whiskers(R"(
<bases>
<?isAbstract>abstract </isAbstract>contract <contractName> {
<?isAbstract>abstract </isAbstract>contract <contractName><?inheritance> is <baseNames></inheritance> {
<functions>
})")
("bases", bases.str())
("isAbstract", abstract())
("contractName", name())
("inheritance", m_baseContracts.size() > 0)
("baseNames", baseNames())
("functions", functions.str())
.render();
}

View File

@ -253,6 +253,7 @@ struct SolBaseContract
std::variant<std::vector<std::shared_ptr<SolContractFunction>>, std::vector<std::shared_ptr<SolInterfaceFunction>>>
baseFunctions();
BaseType type() const;
std::string name();
std::string str();
std::shared_ptr<SolInterface> interface()
{
@ -262,6 +263,8 @@ struct SolBaseContract
{
return std::get<std::shared_ptr<SolContract>>(m_base);
}
unsigned functionIndex();
std::string lastBaseName();
std::variant<std::shared_ptr<SolInterface>, std::shared_ptr<SolContract>> m_base;
std::string m_baseName;
@ -288,6 +291,7 @@ struct SolContract
);
bool validTest() const;
std::string baseNames() const;
std::tuple<std::string, std::string, std::string> validContractTest();
std::tuple<std::string, std::string, std::string> pseudoRandomTest();

View File

@ -138,447 +138,19 @@ pragma solidity >=0.0;
string ProtoConverter::visit(ContractType const& _contractType)
{
m_mostDerivedAbstractContract = false;
switch (_contractType.contract_type_oneof_case())
{
case ContractType::kC:
m_mostDerivedAbstractContract = _contractType.c().abstract();
m_mostDerivedProgram = MostDerivedProgram::CONTRACT;
return visit(_contractType.c());
case ContractType::kL:
m_mostDerivedProgram = MostDerivedProgram::LIBRARY;
return visit(_contractType.l());
case ContractType::kI:
m_mostDerivedProgram = MostDerivedProgram::INTERFACE;
return visit(_contractType.i());
case ContractType::CONTRACT_TYPE_ONEOF_NOT_SET:
return "";
}
}
#if 0
string ProtoConverter::mostDerivedInterfaceOverrides(Interface const& _interface)
{
ostringstream funcs;
for (auto base = _interface.bases().rbegin(); base != _interface.bases().rend(); base++)
{
for (auto& f: base->funcdef())
{
// An interface may override base interface's function
bool override = pseudoRandomCoinFlip();
if (!override)
continue;
funcs << overrideFunction(&f, false, false);
}
funcs << mostDerivedInterfaceOverrides(*base);
}
return funcs.str();
}
string ProtoConverter::mostDerivedContractOverrides(Interface const& _interface)
{
ostringstream funcs;
for (auto base = _interface.bases().rbegin(); base != _interface.bases().rend(); base++)
{
for (auto& f: base->funcdef())
{
bool override = pseudoRandomCoinFlip() || mostDerivedProgramAbstractContract();
/// We can arrive here from contract through other contracts and interfaces.
/// We define most derived contract (MDC) as the most derived contract in
/// the inheritence chain to which the visited interface belongs to.
/// When MDC is not abstract we must implement (with override) all
/// interface functions that MDC inherits unless it has been implemented
/// by some other contract in the inheritence chain that MDC also derives
/// from.
/// In other words, if an interface function has never been implemented
/// thus far in the inheritence chain, it must be implemented.
/// If it has already been implemented, it may be reimplemented provided
/// it is virtual.
/// When reimplementing, it may be revirtualized.
/// When MDC is abstract, we may or may not redeclare interface function.
/// If interface function has been implemented in the inheritence chain,
/// and we redeclare it, we must reimplement it.
/// If inheritence function has not been implemented and we redeclare it,
/// we may implement it.
/// If we implement it, it may be marked virtual.
/// If we don't implement it, it must be marked virtual.
/// . We may virtualize.
/// We create a pair <interfaceFunction, virtualized> and add it to list of
/// implemented interface functions.
/// When IAC is abstract:
/// - we may redeclare
/// - if redeclared, we may implement
/// - if we do not implement redeclared function then:
/// - we must override and virtualize
/// - add to list of unimplemented interface functions
/// - if we implement then:
/// - we must override
/// - we may virtualize
/// - create a pair <interfaceFunction, virtualized> and add it to list of
/// implemented interface functions.
/// - ancestor contract is not abstract and this interface function
/// has been implemented by some other contract in the traversal path
/// that also virtualizes the function.
/// When we override, we may mark it as virtual.
/// We may override when ancestor contract is abstract
/// If ancestor contract is overriding, we may or may not implement it.
/// If we do not implement it, we must mark it virtual since otherwise
/// we are left with unimplementable function.
/// If we implement it, we may mark it as virtual.
string funcStr = visit(
f,
index++,
override,
programName(&*base)
);
if (override)
funcs << funcStr;
}
funcs << mostDerivedContractOverrides(*base);
}
return funcs.str();
}
pair<bool, bool> ProtoConverter::contractFunctionParams(
Contract const* _contract,
ContractFunction const* _function
)
{
// If contract is abstract, we may implement this function. If contract
// is not abstract, we must implement this function.
bool implement = !_contract->abstract() || pseudoRandomCoinFlip();
// We may mark a non-overridden function as virtual.
// We must mark an unimplemented abstract contract function as
// virtual.
bool virtualFunc = _function->virtualfunc() || (_contract->abstract() && !implement);
return pair(implement, virtualFunc);
}
pair<bool, bool> ProtoConverter::contractFunctionOverrideParams(
Contract const* _base,
Contract const* _derived,
CIFunc _f
)
{
bool baseAbstract = _base->abstract();
bool derivedAbstract = _derived->abstract();
bool implement = false;
bool revirtualize = false;
// There are four possibilities here:
// 1. both base and derived are abstract,
// 2. base abstract, derived not
// 3. base not abstract, derived is
// 4. both base and derived are not abstract
if (baseAbstract && derivedAbstract)
{
// Case 1: Both base and derived are abstract
// virtual base functions may be redeclared (with override)
// if redeclared virtual base function has been implemented, it must be reimplemented but
// may be revirtualized
// if revirtualized, there is nothing to be changed in list of <ContractFunction, bool>
// if not revirtualized, we changed the boolean to false in the list of implemented contract
// functions.
// if redeclared virtual base function has not been implemented, it may be implemented.
// if it is implemented (with override), it may be revirtualized.
// if it is not implemented, it must be marked virtual.
// if virtual base function not redeclared, there is no status change
bool virtualImplemented = contractFunctionImplemented(_base, _f);
implement = virtualImplemented || pseudoRandomCoinFlip();
revirtualize = (!implement && !virtualImplemented) || pseudoRandomCoinFlip();
}
else if (baseAbstract && !derivedAbstract)
{
// Case 2: Base abstract, derived not
// If base function appears in list of unimplemented virtual contract functions, we
// must implement it (with override). Remove from unimplemented virtual contract functions. We may
// revirtualize. Add to list of implemented contract functions.
// Unimplemented virtual functions must be implemented (with override)
// Implemented virtual functions may be implemented (with override)
//
}
else if (!baseAbstract && derivedAbstract)
{
// Case 3: Base not abstract, derived is
// All base functions are implemented. Base functions marked virtual may be overridden.
// If they are overridden they must be reimplemented. They may be revirtualized.
// Base functions that are not virtual may not be redeclared.
}
else
{
// Case 4: Neither base nor derived are abstract
// All base functions are implemented. Base functions marked virtual may be overridden.
// If they are overridden they must be reimplemented. They may be revirtualized.
// Base functions that are not virtual may not be redeclared.
}
return pair(implement, revirtualize);
}
string ProtoConverter::traverseOverrides(Contract const& _contract)
{
ostringstream funcs;
for (auto base = _contract.bases().rbegin(); base != _contract.bases().rend(); base++)
{
if (base->contract_or_interface_oneof_case() == ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET)
continue;
if (base->has_c())
{
for (auto& f: base->c().funcdef())
{
// We may redeclare virtual functions in base contract
bool redeclareVirtual = f.virtualfunc() && pseudoRandomCoinFlip();
// If base function is not virtual or if we choose to not
// redeclare the virtual function, we skip to the next function
// after incrementing the function index.
if (!f.virtualfunc() || !redeclareVirtual)
continue;
// Check if overridden function may be implemented/revirtualized
// by the derived contract.
auto [implement, revirtualize] = contractFunctionOverrideParams(
&base->c(),
&_contract,
&f
);
funcs << overrideFunction(&f, revirtualize, implement);
// Update contract function map
m_contractFunctionMap[&_contract].push_back(CITuple(&f, implement, revirtualize));
}
// Override revirtualized functions.
// TODO: Function that returns all revirtualized functions and their implementation
// status.
for (auto &tuple: m_contractFunctionMap[&base->c()])
{
auto function = get<0>(tuple);
bool overrideRevirtualized = true;
if (holds_alternative<ContractFunction const*>(function))
{
auto contractFunction = get<ContractFunction const*>(function);
auto [implementRevirtualized, revirtualizeRevirtualized] = contractFunctionOverrideParams(
&base->c(),
&_contract,
get<0>(tuple)
);
funcs << visit(
*contractFunction,
index++,
overrideRevirtualized,
revirtualizeRevirtualized,
implementRevirtualized,
programName(&base->c())
);
}
}
// Traverse base
funcs << traverseOverrides(base->c());
}
else if (base->has_i())
{
for (auto& f: base->i().funcdef())
{
bool implement = pseudoRandomCoinFlip();
m_counter += base->i().funcdef_size();
bool override = pseudoRandomCoinFlip();
if (_contract.abstract() && !implement && !override)
continue;
funcs << overrideFunction(
&f,
!_contract.abstract() || implement,
pseudoRandomCoinFlip() || (override && _contract.abstract())
);
}
funcs << mostDerivedContractOverrides(base->i(), !_contract.abstract());
}
}
return funcs.str();
}
/// This function is called when root is interface.
tuple<string, string, string> ProtoConverter::visitMostDerivedInterface(Interface const& _interface)
{
ostringstream bases;
ostringstream baseNames;
ostringstream funcs;
string separator{};
for (auto &base: _interface.bases())
{
string baseStr = visit(base);
if (baseStr.empty())
continue;
bases << baseStr;
baseNames << separator
<< programName(&base);
if (separator.empty())
separator = ", ";
}
// First define overridden functions
bool overrides = _interface.bases_size() > 0 && !baseNames.str().empty();
if (overrides)
funcs << mostDerivedInterfaceOverrides(_interface);
unsigned index = 0;
// Define non-overridden functions
for (auto &f: _interface.funcdef())
funcs << registerAndVisitFunction(
&_interface,
&f,
index++,
false,
false,
false
);
return make_tuple(bases.str(), baseNames.str(), funcs.str());
}
void ProtoConverter::registerFunctionName(CIL _program, CILFunc _function, unsigned _index)
{
string pName = programName(_program);
string fName = createFunctionName(_program, _index);
solAssert(!m_functionNameMap.count(_function), "Sol proto fuzzer: Duplicate function registration");
m_functionNameMap.insert(pair(_function, fName));
}
string ProtoConverter::registerAndVisitFunction(
CIL _program,
CILFunc _function,
unsigned _index,
bool _override,
bool _virtual,
bool _implement
)
{
registerFunctionName(_program, _function, _index);
return visit(_function, _override, _virtual, _implement);
}
tuple<string, string, string> ProtoConverter::visitProgramHelper(CIL _program)
{
ostringstream bases;
ostringstream funcs;
ostringstream baseNames;
string pName = programName(_program);
string separator{};
if (holds_alternative<Contract const*>(_program))
{
Contract const* contract = get<Contract const*>(_program);
for (auto &base: contract->bases())
{
string baseStr = visit(base);
if (baseStr.empty())
continue;
bases << baseStr;
baseNames << separator
<< (base.has_c() ? programName(&base.c()) : programName(&base.i()));
if (separator.empty())
separator = ", ";
}
// First define overridden functions
bool overrides = contract->bases_size() > 0 && !baseNames.str().empty();
if (overrides)
{
funcs << traverseOverrides(*contract);
}
// Declare/define non-overridden functions
unsigned index = 0;
for (auto &f: contract->funcdef())
{
auto [implement, virtualize] = contractFunctionParams(contract, &f);
funcs << registerAndVisitFunction(
_program,
&f,
index++,
false,
virtualize,
implement
);
// Update contract function map
m_contractFunctionMap[contract].push_back(CITuple(&f, implement, virtualize));
}
}
else if (holds_alternative<Interface const*>(_program))
{
// If we are here, it means most derived program is a contract.
auto interface = get<Interface const*>(_program);
for (auto &base: interface->bases())
{
string baseStr = visit(base);
if (baseStr.empty())
continue;
bases << baseStr;
baseNames << separator
<< programName(&base);
if (separator.empty())
separator = ", ";
}
// First define overridden functions
bool overrides = interface->bases_size() > 0 && !baseNames.str().empty();
if (overrides)
{
funcs << mostDerivedContractOverrides(*interface, false);
}
unsigned index = 0;
// Declare non-overridden functions
for (auto &f: interface->funcdef())
funcs << registerAndVisitFunction(
_program,
&f,
index++,
false,
false,
false
);
}
else
{
auto library = get<Library const*>(_program);
unsigned index = 0;
for (auto &f: library->funcdef())
funcs << registerAndVisitFunction(
_program,
&f,
index++,
false,
false,
false
);
}
return make_tuple(bases.str(), baseNames.str(), funcs.str());
}
#endif
string ProtoConverter::visit(Contract const& _contract)
{
if (_contract.funcdef_size() == 0 && _contract.bases_size() == 0)
@ -661,9 +233,6 @@ void ProtoConverter::openProgramScope(CIL _program)
programNamePrefix = "L";
string programName = programNamePrefix + to_string(m_numPrograms++);
m_programNameMap.insert(pair(_program, programName));
if (holds_alternative<Contract const*>(_program))
m_contractFunctionMap.insert(pair(get<Contract const*>(_program), vector<CITuple>{}));
}
string ProtoConverter::programName(CIL _program)
@ -676,12 +245,4 @@ unsigned ProtoConverter::randomNumber()
{
solAssert(m_randomGen, "Sol proto fuzzer: Uninitialized random number generator");
return m_randomGen->operator()();
}
#if 0
string ProtoConverter::functionName(CILFunc _function)
{
solAssert(m_functionNameMap.count(_function), "Sol proto fuzzer: Unregistered function");
return m_functionNameMap[_function];
}
#endif
}

View File

@ -46,32 +46,14 @@ public:
ProtoConverter(ProtoConverter const&) = delete;
ProtoConverter(ProtoConverter&&) = delete;
std::string protoToSolidity(Program const&);
/// @returns true if test calls a library function, false
/// otherwise
bool libraryTest() const;
/// @returns name of the library under test
std::string libraryName() const;
private:
enum MostDerivedProgram {
CONTRACT,
INTERFACE,
LIBRARY
};
/// Variant type that points to one of contract, interface, library protobuf messages
using CIL = std::variant<Contract const*, Interface const*, Library const*>;
/// Variant type that points to one of contract, interface, library function protobuf
/// messages
using CILFunc = std::variant<ContractFunction const*, InterfaceFunction const*, LibraryFunction const*>;
/// Variant type that points to one of contract, interface protobuf messages
using CI = std::variant<Contract const*, Interface const*>;
/// Variant type that points to one of contract, interface function protobuf messages
using CIFunc = std::variant<ContractFunction const*, InterfaceFunction const*>;
/// Tuple of contract or interface function variant and a boolean stating whether
/// the function is implemented (true) or not and a second boolean stating whether
/// the function is virtual (true) or not.
using CITuple = std::tuple<CIFunc, bool, bool>;
/// Protobuf message visitors that accept a const reference to a protobuf message
/// type and return its solidity translation.
std::string visit(Program const&);
@ -83,6 +65,8 @@ private:
std::string programName(CIL _program);
std::tuple<std::string, std::string, std::string> pseudoRandomLibraryTest();
std::tuple<std::string, std::string, std::string> pseudoRandomContractTest();
void openProgramScope(CIL _program);
unsigned randomNumber();
bool emptyLibrary(Library const& _library)
{
@ -97,46 +81,10 @@ private:
return m_contractTests.size() == 0;
}
void openProgramScope(CIL _program);
bool pseudoRandomCoinFlip()
{
return m_counter++ % 2 == 0;
}
bool mostDerivedProgramContract()
{
return m_mostDerivedProgram == MostDerivedProgram::CONTRACT;
}
bool mostDerivedProgramInterface()
{
return m_mostDerivedProgram == MostDerivedProgram::INTERFACE;
}
bool mostDerivedProgramAbstractContract()
{
return m_mostDerivedAbstractContract;
}
unsigned randomNumber();
unsigned m_numPrograms = 0;
unsigned m_counter = 0;
bool m_mostDerivedAbstractContract = false;
bool m_libraryTest = false;
std::shared_ptr<SolRandomNumGenerator> m_randomGen;
MostDerivedProgram m_mostDerivedProgram = MostDerivedProgram::CONTRACT;
/// Map whose key is a const pointer to protobuf contract
/// message and whose value is a list of 3-tuples that
/// store a const pointer to a protobuf interface or contract
/// function belonging to the keyed contract, a boolean flag that is
/// true when the function is implemented, false otherwise, and
/// a second boolean flag that is true when the function is virtualized
/// false otherwise.
std::map<Contract const*, std::vector<CITuple>> m_contractFunctionMap;
/// Map whose key is a const pointer to protobuf contract, interface or
/// library function message type and whose value is the function name
/// assigned to it.
std::map<CILFunc, std::string> m_functionNameMap;
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::string m_libraryName;