diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index 7bc38aeaa..22d841bed 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -404,9 +404,11 @@ string AssignmentStmtGenerator::visit() { ExpressionGenerator exprGen{state}; auto lhs = exprGen.randomLValueExpression(); + exprGen.resetNestingDepth(); if (!lhs.has_value()) return "\n"; - auto rhs = exprGen.expression(lhs.value()); + auto rhs = exprGen.rOrLValueExpression(lhs.value()); + exprGen.resetNestingDepth(); if (!rhs.has_value()) return "\n"; auto operation = assignOp(lhs.value().first); @@ -540,33 +542,57 @@ void FunctionGenerator::endVisit() mutator->generator()->resetNestingDepth(); } +vector> ExpressionGenerator::liveVariables() +{ + auto liveVariables = state->currentFunctionState()->inputs | + ranges::views::transform([](auto& _item) { return _item; }) | + ranges::to>>(); + liveVariables += state->currentFunctionState()->outputs | + ranges::views::transform([](auto& _item) { return _item; }) | + ranges::to>>(); + for (auto const& scope: state->currentFunctionState()->scopes) + liveVariables += scope->variables | + ranges::views::transform([](auto& _item) { return _item; }) | + ranges::to>>(); + return liveVariables; +} + +vector> ExpressionGenerator::liveVariables( + pair _typeName +) +{ + auto liveTypedVariables = state->currentFunctionState()->inputs | + ranges::views::filter([&_typeName](auto& _item) { + return _item.first.index() == _typeName.first.index() && + _item.second != _typeName.second && + visit(TypeComparator{}, _item.first, _typeName.first); + }) | + ranges::to>>(); + liveTypedVariables += state->currentFunctionState()->outputs | + ranges::views::filter([&_typeName](auto& _item) { + return _item.first.index() == _typeName.first.index() && + _item.second != _typeName.second && + visit(TypeComparator{}, _item.first, _typeName.first); + }) | + ranges::to>>(); + for (auto const& scope: state->currentFunctionState()->scopes) + liveTypedVariables += scope->variables | + ranges::views::filter([&_typeName](auto& _item) { + return _item.first.index() == _typeName.first.index() && + _item.second != _typeName.second && + visit(TypeComparator{}, _item.first, _typeName.first); + }) | + ranges::to>>(); + return liveTypedVariables; +} + optional> ExpressionGenerator::randomLValueExpression() { - LValueExpr exprType = static_cast( - state->uRandDist->distributionOneToN(static_cast(LValueExpr::TYPEMAX) - 1) - ); - switch (exprType) - { - case LValueExpr::VARREF: - { - auto liveVariables = state->currentFunctionState()->inputs | - ranges::views::transform([](auto& _item) { return _item; }) | - ranges::to>>(); - liveVariables += state->currentFunctionState()->outputs | - ranges::views::transform([](auto& _item) { return _item; }) | - ranges::to>>(); - for (auto const& scope: state->currentFunctionState()->scopes) - liveVariables += scope->variables | - ranges::views::transform([](auto& _item) { return _item; }) | - ranges::to>>(); - if (liveVariables.empty()) - return nullopt; - else - return liveVariables[state->uRandDist->distributionOneToN(liveVariables.size()) - 1]; - } - default: - solAssert(false, ""); - } + auto liveVars = liveVariables(); + if (liveVars.empty()) + return nullopt; + auto randomLValue = liveVars[state->uRandDist->distributionOneToN(liveVars.size()) - 1]; + return lValueExpression(randomLValue); } optional> ExpressionGenerator::lValueExpression( @@ -574,45 +600,395 @@ optional> ExpressionGenerator::lValueExpression( ) { // Filter non-identical variables of the same type. - auto liveTypedVariables = state->currentFunctionState()->inputs | - ranges::views::filter([&_typeName](auto& _item) { - return _item.first.index() == _typeName.first.index() && - _item.second != _typeName.second && - visit(TypeComparator{}, _item.first, _typeName.first); - }) | - ranges::to>>(); - liveTypedVariables += state->currentFunctionState()->outputs | - ranges::views::filter([&_typeName](auto& _item) { - return _item.first.index() == _typeName.first.index() && - _item.second != _typeName.second && - visit(TypeComparator{}, _item.first, _typeName.first); - }) | - ranges::to>>(); - if (liveTypedVariables.empty()) + auto typedLiveVars = liveVariables(_typeName); + + if (typedLiveVars.empty()) return nullopt; else - return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1]; + return typedLiveVars[state->uRandDist->distributionOneToN(typedLiveVars.size()) - 1]; } -pair ExpressionGenerator::literal(SolidityTypePtr _type) +optional> ExpressionGenerator::literal(SolidityTypePtr _type) { - string literalValue = visit(LiteralGenerator{state}, _type); - return pair(_type, literalValue); -} - -optional> ExpressionGenerator::expression(pair _typeName) -{ - auto varRef = lValueExpression(_typeName); - if (!varRef.has_value()) + bool functionType = holds_alternative>(_type); + bool contractType = holds_alternative>(_type); + // TODO: Generate literals for contract and function types. + if (functionType || contractType) + return nullopt; + else { - // TODO: Generate literals for contract and function types. - if (!(holds_alternative>(_typeName.first) || holds_alternative>(_typeName.first))) - return literal(_typeName.first); - else - return nullopt; + string literalValue = visit(LiteralGenerator{state}, _type); + return pair(_type, literalValue); + } +} + +optional> ExpressionGenerator::rLValueOrLiteral( + pair& _typeName +) +{ + optional> rLValue; + // Try to obtain an RLValue failing which a typed literal. + rLValue = rOrLValueExpression(_typeName); + if (!rLValue.has_value()) + rLValue = literal(_typeName.first); + return rLValue; +} + +optional> ExpressionGenerator::unaryExpression( + pair& _typeName, + string const& _op +) +{ + optional> rLValue = rLValueOrLiteral(_typeName); + pair result; + if (rLValue.has_value()) + { + result = rLValue.value(); + result.second = _op + "(" + result.second + ")"; + return result; } else - return varRef.value(); + return nullopt; +} + +optional> ExpressionGenerator::binaryExpression( + pair& _typeName, + string const& _op +) +{ + auto left = rLValueOrLiteral(_typeName); + auto right = rLValueOrLiteral(_typeName); + if (left.has_value() && right.has_value()) + { + auto leftResult = left.value(); + auto rightResult = right.value(); + leftResult.second = "(" + + leftResult.second + + " " + + _op + + " " + + rightResult.second + + ")"; + return leftResult; + } + else + return nullopt; +} + +optional> ExpressionGenerator::incDecOperation( + pair& _typeName, + string const& _op, + bool _prefixOp +) +{ + if (!holds_alternative>(_typeName.first)) + return nullopt; + + auto lValue = lValueExpression(_typeName); + if (!lValue.has_value()) + return nullopt; + + auto lResult = lValue.value(); + if (_prefixOp) + lResult.second = _op + lResult.second; + else + lResult.second += _op; + return lResult; +} + +optional> ExpressionGenerator::rOrLValueExpression(pair _typeName) +{ + RLValueExpr exprType = static_cast( + state->uRandDist->distributionOneToN(static_cast(RLValueExpr::RLMAX) - 1) + ); + + if (deeplyNested()) + return literal(_typeName.first); + + incrementNestingDepth(); + + string op; + switch (exprType) + { + case RLValueExpr::VARREF: + return lValueExpression(_typeName); + case RLValueExpr::PINC: + return incDecOperation(_typeName, "++", true); + case RLValueExpr::PDEC: + return incDecOperation(_typeName, "--", true); + case RLValueExpr::SINC: + return incDecOperation(_typeName, "++", false); + case RLValueExpr::SDEC: + return incDecOperation(_typeName, "--", false); + case RLValueExpr::NOT: + { + // Logical not may only be applied to expressions of boolean type. + if (!holds_alternative>(_typeName.first)) + return nullopt; + op = "!"; + return unaryExpression(_typeName, op); + } + case RLValueExpr::BITNOT: + { + // Bitwise not may only be applied to integer types. + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "~"; + return unaryExpression(_typeName, op); + } + case RLValueExpr::USUB: + { + // Unary sub may only be applied to signed integer types + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + bool signedType = get>(_typeName.first)->signedType; + if (!signedType) + return nullopt; + op = "-"; + return unaryExpression(_typeName, op); + } + case RLValueExpr::EXP: + { + // Exponentiation may only be applied to unsigned integer types + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + bool signedType = get>(_typeName.first)->signedType; + if (signedType) + return nullopt; + op = "**"; + return binaryExpression(_typeName, op); + } + // Arithmetic ops only be applied to integer types + case RLValueExpr::MUL: + { + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "*"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::DIV: + { + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "/"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::MOD: + { + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "%"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::ADD: + { + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "+"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::BSUB: + { + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + op = "-"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::SHL: + { + // Left shift may only be applied to unsigned integer types. + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + bool signedType = get>(_typeName.first)->signedType; + if (signedType) + return nullopt; + op = "<<"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::SHR: + { + // Left shift may only be applied to unsigned integer types. + bool integerType = holds_alternative>(_typeName.first); + if (!integerType) + return nullopt; + bool signedType = get>(_typeName.first)->signedType; + if (signedType) + return nullopt; + op = ">>"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::BITAND: + { + // Bitwise ops may only be applied to integer and fixed bytes types. + bool integerType = holds_alternative>(_typeName.first); + bool fixedBytesType = holds_alternative>(_typeName.first); + if (!(integerType || fixedBytesType)) + return nullopt; + op = "&"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::BITOR: + { + // Bitwise ops may only be applied to integer and fixed bytes types. + bool integerType = holds_alternative>(_typeName.first); + bool fixedBytesType = holds_alternative>(_typeName.first); + if (!(integerType || fixedBytesType)) + return nullopt; + op = "|"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::BITXOR: + { + // Bitwise ops may only be applied to integer and fixed bytes types. + bool integerType = holds_alternative>(_typeName.first); + bool fixedBytesType = holds_alternative>(_typeName.first); + if (!(integerType || fixedBytesType)) + return nullopt; + op = "^"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::LT: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = "<"; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::GT: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = ">"; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::LTE: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = "<="; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::GTE: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = ">="; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::EQ: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = "=="; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::NEQ: + { + // Comparison ops may be applied only if LHS type is boolean. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + // Types being compared could be integer, fixed bytes, address, or contract. + auto operandType = TypeProvider{state}.type(); + bool opFunctionType = holds_alternative>(operandType); + bool opBoolType = holds_alternative>(operandType); + bool opBytesType = holds_alternative>(operandType); + if (opFunctionType || opBoolType || opBytesType) + return nullopt; + op = "!="; + pair operandTypeName = {operandType, {}}; + return binaryExpression(operandTypeName, op); + } + case RLValueExpr::AND: + { + // Logical ops may be applied only to boolean types. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + op = "&&"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::OR: + { + // Logical ops may be applied only to boolean types. + bool boolType = holds_alternative>(_typeName.first); + if (!boolType) + return nullopt; + + op = "||"; + return binaryExpression(_typeName, op); + } + case RLValueExpr::LIT: + return literal(_typeName.first); + default: + solAssert(false, ""); + } } string LiteralGenerator::operator()(shared_ptr const&) @@ -684,7 +1060,7 @@ optional TypeProvider::type(SolidityTypePtr _type) { vector matchingTypes = state->currentFunctionState()->inputs | ranges::views::filter([&_type](auto& _item) { - return _item.first == _type; + return _item.first >= _type; }) | ranges::views::transform([](auto& _item) { return _item.first; }) | ranges::to>(); @@ -744,6 +1120,7 @@ string FunctionCallGenerator::lhs(vector> _functio auto assignToVars = _functionReturnTypeNames | ranges::views::transform([&exprGen](auto const& _item) -> pair>> { auto e = exprGen.lValueExpression(_item); + exprGen.resetNestingDepth(); if (e.has_value()) return {true, e.value()}; else @@ -792,12 +1169,14 @@ optional FunctionCallGenerator::rhs(vector auto inputArguments = _functionInputTypeNames | ranges::views::transform([&exprGen](auto const& _item) -> pair>> { - auto e = exprGen.expression(_item); + auto e = exprGen.rOrLValueExpression(_item); + exprGen.resetNestingDepth(); if (e.has_value()) return {true, e.value()}; else return {false, nullopt}; - }); + }) | + ranges::to>>>>(); bool inputArgsValid = ranges::all_of( inputArguments, [](auto const& _item) -> bool { return _item.first; } @@ -806,7 +1185,10 @@ optional FunctionCallGenerator::rhs(vector if (inputArgsValid) { auto vars = inputArguments | - ranges::views::transform([](auto const& _item) { return _item.second.value().second; }) | + ranges::views::transform([](auto const& _item) { + solAssert(_item.second.has_value(), ""); + return _item.second.value().second; + }) | ranges::to>(); callStmtRhs << boost::algorithm::join(vars, ","); return callStmtRhs.str(); diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index d738592f7..ea01e0db6 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -165,10 +165,10 @@ public: signedType(_signed), numBits(static_cast(_bits) * 8) {} - bool operator==(IntegerType const& _rhs) + bool operator>=(IntegerType const& _rhs) { return this->signedType == _rhs.signedType && - this->numBits == _rhs.numBits; + this->numBits >= _rhs.numBits; } std::string toString() override { @@ -185,7 +185,7 @@ public: { return "bool"; } - bool operator==(BoolType const&) + bool operator>=(BoolType const&) { return true; } @@ -199,7 +199,7 @@ public: { return "address"; } - bool operator==(AddressType const&) + bool operator>=(AddressType const&) { return true; } @@ -246,7 +246,7 @@ public: FixedBytesType(Bytes _width): numBytes(static_cast(_width)) {} - bool operator==(FixedBytesType const& _rhs) + bool operator>=(FixedBytesType const& _rhs) { return this->numBytes == _rhs.numBytes; } @@ -264,7 +264,7 @@ public: { return "bytes memory"; } - bool operator==(BytesType const&) + bool operator>=(BytesType const&) { return true; } @@ -283,7 +283,7 @@ public: { return contractName; } - bool operator==(ContractType const& _rhs) + bool operator>=(ContractType const& _rhs) { return _rhs.name() == this->name(); } @@ -319,7 +319,7 @@ public: } std::string toString() override; - bool operator==(FunctionType const& _rhs) + bool operator>=(FunctionType const& _rhs) { if (_rhs.inputs.size() != this->inputs.size() || _rhs.outputs.size() != this->outputs.size()) return false; @@ -451,13 +451,13 @@ struct FunctionState } void addInput(SolidityTypePtr _input) { - inputs.emplace_back(_input, "i" + std::to_string(numInputs++)); type->addInput(_input); + inputs.emplace_back(_input, "i" + std::to_string(numInputs++)); } void addOutput(SolidityTypePtr _output) { - outputs.emplace_back(_output, "o" + std::to_string(numOutpus++)); type->addOutput(_output); + outputs.emplace_back(_output, "o" + std::to_string(numOutpus++)); } void addLocal(SolidityTypePtr _local) { @@ -714,7 +714,7 @@ struct TypeComparator template bool operator()(T _i1, T _i2) { - return *_i1 == *_i2; + return *_i1 >= *_i2; } template bool operator()(T1 _i1, T2 _i2) @@ -745,22 +745,84 @@ struct ExpressionGenerator ExpressionGenerator(std::shared_ptr _state): state(std::move(_state)) {} - enum class LValueExpr: size_t + enum class RLValueExpr: size_t { VARREF = 1, - TYPEMAX + PINC, + PDEC, + SINC, + SDEC, + NOT, + BITNOT, + USUB, + EXP, + MUL, + DIV, + MOD, + ADD, + BSUB, + SHL, + SHR, + BITAND, + BITXOR, + BITOR, + LT, + GT, + LTE, + GTE, + EQ, + NEQ, + AND, + OR, + LIT, + RLMAX }; - std::optional> expression( + std::optional> rOrLValueExpression( std::pair _typeName ); - std::pair literal(SolidityTypePtr _type); + std::optional> literal(SolidityTypePtr _type); std::optional> randomLValueExpression(); std::optional> lValueExpression( std::pair _typeName ); + std::vector> liveVariables(); + std::vector> liveVariables( + std::pair _typeName + ); + std::optional> unaryExpression( + std::pair& _typeName, + std::string const& _op + ); + std::optional> binaryExpression( + std::pair& _typeName, + std::string const& _op + ); + std::optional> incDecOperation( + std::pair& _typeName, + std::string const& _op, + bool _prefixOp + ); + std::optional> rLValueOrLiteral( + std::pair& _typeName + ); + + void incrementNestingDepth() + { + nestingDepth++; + } + void resetNestingDepth() + { + nestingDepth = 0; + } + bool deeplyNested() + { + return nestingDepth > s_maxNestingDepth; + } std::shared_ptr state; + unsigned nestingDepth; + static constexpr unsigned s_maxNestingDepth = 30; }; struct GeneratorBase