From 3cb4ed83c17ed65e68dbdc75ac89dfef4cb6b635 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 25 Jun 2019 12:46:17 +0200 Subject: [PATCH] [SMTChecker] Split SMTChecker into SMTEncoder and BMC --- libsolidity/CMakeLists.txt | 8 +- libsolidity/formal/BMC.cpp | 888 +++++++++++++++++ libsolidity/formal/BMC.h | 180 ++++ libsolidity/formal/ModelChecker.cpp | 42 + libsolidity/formal/ModelChecker.h | 64 ++ .../formal/{SMTChecker.cpp => SMTEncoder.cpp} | 921 +++--------------- .../formal/{SMTChecker.h => SMTEncoder.h} | 115 +-- libsolidity/formal/VariableUsage.cpp | 8 +- libsolidity/interface/CompilerStack.cpp | 8 +- .../complex/slither/const_state_variables.sol | 1 + .../modifier_code_after_placeholder.sol | 4 +- 11 files changed, 1319 insertions(+), 920 deletions(-) create mode 100644 libsolidity/formal/BMC.cpp create mode 100644 libsolidity/formal/BMC.h create mode 100644 libsolidity/formal/ModelChecker.cpp create mode 100644 libsolidity/formal/ModelChecker.h rename libsolidity/formal/{SMTChecker.cpp => SMTEncoder.cpp} (58%) rename libsolidity/formal/{SMTChecker.h => SMTEncoder.h} (68%) diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 0360dba29..fdd7f616f 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -75,10 +75,14 @@ set(sources codegen/ir/IRGenerationContext.h codegen/ir/IRLValue.cpp codegen/ir/IRLValue.h + formal/BMC.cpp + formal/BMC.h formal/EncodingContext.cpp formal/EncodingContext.h - formal/SMTChecker.cpp - formal/SMTChecker.h + formal/ModelChecker.cpp + formal/ModelChecker.h + formal/SMTEncoder.cpp + formal/SMTEncoder.h formal/SMTLib2Interface.cpp formal/SMTLib2Interface.h formal/SMTPortfolio.cpp diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp new file mode 100644 index 000000000..a691e2e9f --- /dev/null +++ b/libsolidity/formal/BMC.cpp @@ -0,0 +1,888 @@ +/* + 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 . +*/ + +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +BMC::BMC(smt::EncodingContext& _context, ErrorReporter& _errorReporter, map const& _smtlib2Responses): + SMTEncoder(_context, _smtlib2Responses), + m_outerErrorReporter(_errorReporter) +{ +#if defined (HAVE_Z3) || defined (HAVE_CVC4) + if (!_smtlib2Responses.empty()) + m_errorReporter.warning( + "SMT-LIB2 query responses were given in the auxiliary input, " + "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." + "These responses will be ignored." + "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." + ); +#endif +} + +void BMC::analyze(SourceUnit const& _source, shared_ptr const& _scanner) +{ + solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), ""); + + m_scanner = _scanner; + + _source.accept(*this); + + solAssert(m_interface->solvers() > 0, ""); + // If this check is true, Z3 and CVC4 are not available + // and the query answers were not provided, since SMTPortfolio + // guarantees that SmtLib2Interface is the first solver. + if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1) + { + if (!m_noSolverWarning) + { + m_noSolverWarning = true; + m_outerErrorReporter.warning( + SourceLocation(), + "BMC analysis was not possible since no integrated SMT solver (Z3 or CVC4) was found." + ); + } + } + else + m_outerErrorReporter.append(m_errorReporter.errors()); + + m_errorReporter.clear(); +} + +FunctionDefinition const* BMC::inlinedFunctionCallToDefinition(FunctionCall const& _funCall) +{ + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + return nullptr; + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (funType.kind() == FunctionType::Kind::External) + { + auto memberAccess = dynamic_cast(&_funCall.expression()); + auto identifier = memberAccess ? + dynamic_cast(&memberAccess->expression()) : + nullptr; + if (!( + identifier && + identifier->name() == "this" && + identifier->annotation().referencedDeclaration && + dynamic_cast(identifier->annotation().referencedDeclaration) + )) + return nullptr; + } + else if (funType.kind() != FunctionType::Kind::Internal) + return nullptr; + + FunctionDefinition const* funDef = nullptr; + Expression const* calledExpr = &_funCall.expression(); + + if (TupleExpression const* fun = dynamic_cast(&_funCall.expression())) + { + solAssert(fun->components().size() == 1, ""); + calledExpr = fun->components().front().get(); + } + + if (Identifier const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + else if (MemberAccess const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + + if (funDef && funDef->isImplemented()) + return funDef; + + return nullptr; +} + +/// AST visitors. + +bool BMC::visit(ContractDefinition const& _contract) +{ + SMTEncoder::visit(_contract); + + /// Check targets created by state variable initialization. + smt::Expression constraints = m_context.assertions(); + checkVerificationTargets(constraints); + m_verificationTargets.clear(); + + return true; +} + +bool BMC::visit(FunctionDefinition const& _function) +{ + if (m_callStack.empty()) + reset(); + + /// Already visits the children. + SMTEncoder::visit(_function); + + return false; +} + +void BMC::endVisit(FunctionDefinition const& _function) +{ + if (isRootFunction()) + { + smt::Expression constraints = m_context.assertions(); + checkVerificationTargets(constraints); + m_verificationTargets.clear(); + } + + SMTEncoder::endVisit(_function); +} + +bool BMC::visit(IfStatement const& _node) +{ + // This check needs to be done in its own context otherwise + // constraints from the If body might influence it. + m_context.pushSolver(); + _node.condition().accept(*this); + + // We ignore called functions here because they have + // specific input values. + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + m_context.popSolver(); + + SMTEncoder::visit(_node); + + return false; +} + +// Here we consider the execution of two branches: +// Branch 1 assumes the loop condition to be true and executes the loop once, +// after resetting touched variables. +// Branch 2 assumes the loop condition to be false and skips the loop after +// visiting the condition (it might contain side-effects, they need to be considered) +// and does not erase knowledge. +// If the loop is a do-while, condition side-effects are lost since the body, +// executed once before the condition, might reassign variables. +// Variables touched by the loop are merged with Branch 2. +bool BMC::visit(WhileStatement const& _node) +{ + auto indicesBeforeLoop = copyVariableIndices(); + auto touchedVars = touchedVariables(_node); + m_context.resetVariables(touchedVars); + decltype(indicesBeforeLoop) indicesAfterLoop; + if (_node.isDoWhile()) + { + indicesAfterLoop = visitBranch(&_node.body()); + // TODO the assertions generated in the body should still be active in the condition + _node.condition().accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + } + else + { + _node.condition().accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + + indicesAfterLoop = visitBranch(&_node.body(), expr(_node.condition())); + } + + // We reset the execution to before the loop + // and visit the condition in case it's not a do-while. + // A do-while's body might have non-precise information + // in its first run about variables that are touched. + resetVariableIndices(indicesBeforeLoop); + if (!_node.isDoWhile()) + _node.condition().accept(*this); + + mergeVariables(touchedVars, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); + + m_loopExecutionHappened = true; + return false; +} + +// Here we consider the execution of two branches similar to WhileStatement. +bool BMC::visit(ForStatement const& _node) +{ + if (_node.initializationExpression()) + _node.initializationExpression()->accept(*this); + + auto indicesBeforeLoop = copyVariableIndices(); + + // Do not reset the init expression part. + auto touchedVars = touchedVariables(_node.body()); + if (_node.condition()) + touchedVars += touchedVariables(*_node.condition()); + if (_node.loopExpression()) + touchedVars += touchedVariables(*_node.loopExpression()); + + m_context.resetVariables(touchedVars); + + if (_node.condition()) + { + _node.condition()->accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(*_node.condition()), + _node.condition() + ); + } + + m_context.pushSolver(); + if (_node.condition()) + m_context.addAssertion(expr(*_node.condition())); + _node.body().accept(*this); + if (_node.loopExpression()) + _node.loopExpression()->accept(*this); + m_context.popSolver(); + + auto indicesAfterLoop = copyVariableIndices(); + // We reset the execution to before the loop + // and visit the condition. + resetVariableIndices(indicesBeforeLoop); + if (_node.condition()) + _node.condition()->accept(*this); + + auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true); + mergeVariables(touchedVars, forCondition, indicesAfterLoop, copyVariableIndices()); + + m_loopExecutionHappened = true; + return false; +} + +void BMC::endVisit(UnaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + + switch (_op.getOperator()) + { + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + expr(_op), + &_op + ); + break; + case Token::Sub: // - + if (_op.annotation().type->category() == Type::Category::Integer) + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + expr(_op), + &_op + ); + break; + default: + break; + } +} + +void BMC::endVisit(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + { + SMTEncoder::endVisit(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Require: + visitRequire(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + SMTEncoder::endVisit(_funCall); + internalOrExternalFunctionCall(_funCall); + break; + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + SMTEncoder::endVisit(_funCall); + abstractFunctionCall(_funCall); + break; + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: + { + SMTEncoder::endVisit(_funCall); + auto value = _funCall.arguments().front(); + solAssert(value, ""); + smt::Expression thisBalance = m_context.balance(); + + addVerificationTarget( + VerificationTarget::Type::Balance, + thisBalance < expr(*value), + &_funCall + ); + break; + } + default: + SMTEncoder::endVisit(_funCall); + break; + } +} + +/// Visitor helpers. + +void BMC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + addVerificationTarget( + VerificationTarget::Type::Assert, + expr(*args.front()), + &_funCall + ); +} + +void BMC::visitRequire(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(*args.front()), + args.front().get() + ); +} + +void BMC::inlineFunctionCall(FunctionCall const& _funCall) +{ + FunctionDefinition const* funDef = inlinedFunctionCallToDefinition(_funCall); + solAssert(funDef, ""); + if (visitedFunction(funDef)) + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not support recursive function calls.", + SecondarySourceLocation().append("Starting from function:", funDef->location()) + ); + else + { + vector funArgs; + Expression const* calledExpr = &_funCall.expression(); + auto const& funType = dynamic_cast(calledExpr->annotation().type); + solAssert(funType, ""); + + if (funType->bound()) + { + auto const& boundFunction = dynamic_cast(calledExpr); + solAssert(boundFunction, ""); + funArgs.push_back(expr(boundFunction->expression())); + } + + for (auto arg: _funCall.arguments()) + funArgs.push_back(expr(*arg)); + initializeFunctionCallParameters(*funDef, funArgs); + + // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) + // is that there we don't have `_funCall`. + pushCallStack({funDef, &_funCall}); + // If an internal function is called to initialize + // a state variable. + if (m_callStack.empty()) + initFunction(*funDef); + funDef->accept(*this); + + auto const& returnParams = funDef->returnParameters(); + if (returnParams.size() > 1) + { + vector> components; + for (auto param: returnParams) + { + solAssert(m_context.variable(*param), ""); + components.push_back(m_context.variable(*param)); + } + auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_funCall)); + solAssert(symbTuple, ""); + symbTuple->setComponents(move(components)); + } + else if (returnParams.size() == 1) + defineExpr(_funCall, currentValue(*returnParams.front())); + } +} + +void BMC::abstractFunctionCall(FunctionCall const& _funCall) +{ + vector smtArguments; + for (auto const& arg: _funCall.arguments()) + smtArguments.push_back(expr(*arg)); + defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments)); + m_uninterpretedTerms.insert(&_funCall); + setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, *m_context.solver()); +} + +void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall) +{ + auto funDef = inlinedFunctionCallToDefinition(_funCall); + auto const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (funDef) + inlineFunctionCall(_funCall); + else if (funType.kind() == FunctionType::Kind::Internal) + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not yet implement this type of function call." + ); + else + { + m_externalFunctionCallHappened = true; + resetStateVariables(); + resetStorageReferences(); + } +} + +pair BMC::arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& _expression +) +{ + if (_op == Token::Div || _op == Token::Mod) + addVerificationTarget( + VerificationTarget::Type::DivByZero, + _right, + &_expression + ); + + auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); + + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + values.second, + &_expression + ); + return values; +} + +void BMC::resetStorageReferences() +{ + m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); +} + +void BMC::reset() +{ + m_externalFunctionCallHappened = false; + m_loopExecutionHappened = false; +} + +pair, vector> BMC::modelExpressions() +{ + vector expressionsToEvaluate; + vector expressionNames; + for (auto const& var: m_context.variables()) + if (var.first->type()->isValueType()) + { + expressionsToEvaluate.emplace_back(currentValue(*var.first)); + expressionNames.push_back(var.first->name()); + } + for (auto const& var: m_context.globalSymbols()) + { + auto const& type = var.second->type(); + if ( + type->isValueType() && + smt::smtKind(type->category()) != smt::Kind::Function + ) + { + expressionsToEvaluate.emplace_back(var.second->currentValue()); + expressionNames.push_back(var.first); + } + } + for (auto const& uf: m_uninterpretedTerms) + if (uf->annotation().type->isValueType()) + { + expressionsToEvaluate.emplace_back(expr(*uf)); + expressionNames.push_back(m_scanner->sourceAt(uf->location())); + } + + return {expressionsToEvaluate, expressionNames}; +} + +/// Verification targets. + +void BMC::checkVerificationTargets(smt::Expression const& _constraints) +{ + for (auto& target: m_verificationTargets) + checkVerificationTarget(target, _constraints); +} + +void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints) +{ + switch (_target.type) + { + case VerificationTarget::Type::ConstantCondition: + checkConstantCondition(_target); + break; + case VerificationTarget::Type::Underflow: + checkUnderflow(_target, _constraints); + break; + case VerificationTarget::Type::Overflow: + checkOverflow(_target, _constraints); + break; + case VerificationTarget::Type::UnderOverflow: + checkUnderflow(_target, _constraints); + checkOverflow(_target, _constraints); + break; + case VerificationTarget::Type::DivByZero: + checkDivByZero(_target); + break; + case VerificationTarget::Type::Balance: + checkBalance(_target); + break; + case VerificationTarget::Type::Assert: + checkAssert(_target); + break; + default: + solAssert(false, ""); + } +} + +void BMC::checkConstantCondition(VerificationTarget& _target) +{ + checkBooleanNotConstant( + *_target.expression, + _target.constraints, + _target.value, + _target.callStack, + "Condition is always $VALUE." + ); +} + +void BMC::checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints) +{ + solAssert( + _target.type == VerificationTarget::Type::Underflow || + _target.type == VerificationTarget::Type::UnderOverflow, + "" + ); + auto intType = dynamic_cast(_target.expression->annotation().type); + solAssert(intType, ""); + checkCondition( + _target.constraints && _constraints && _target.value < smt::minValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")", + "", + &_target.value + ); +} + +void BMC::checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints) +{ + solAssert( + _target.type == VerificationTarget::Type::Overflow || + _target.type == VerificationTarget::Type::UnderOverflow, + "" + ); + auto intType = dynamic_cast(_target.expression->annotation().type); + solAssert(intType, ""); + checkCondition( + _target.constraints && _constraints && _target.value > smt::maxValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")", + "", + &_target.value + ); +} + +void BMC::checkDivByZero(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::DivByZero, ""); + checkCondition( + _target.constraints && (_target.value == 0), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Division by zero", + "", + &_target.value + ); +} + +void BMC::checkBalance(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::Balance, ""); + checkCondition( + _target.constraints && _target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Insufficient funds", + "address(this).balance" + ); +} + +void BMC::checkAssert(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::Assert, ""); + checkCondition( + _target.constraints && !_target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Assertion violation" + ); +} + +void BMC::addVerificationTarget( + VerificationTarget::Type _type, + smt::Expression const& _value, + Expression const* _expression +) +{ + VerificationTarget target{ + _type, + _value, + currentPathConditions() && m_context.assertions(), + _expression, + m_callStack, + modelExpressions() + }; + if (_type == VerificationTarget::Type::ConstantCondition) + checkVerificationTarget(target); + else + m_verificationTargets.emplace_back(move(target)); +} + +/// Solving. + +void BMC::checkCondition( + smt::Expression _condition, + vector const& callStack, + pair, vector> const& _modelExpressions, + SourceLocation const& _location, + string const& _description, + string const& _additionalValueName, + smt::Expression const* _additionalValue +) +{ + m_interface->push(); + m_interface->addAssertion(_condition); + + vector expressionsToEvaluate; + vector expressionNames; + tie(expressionsToEvaluate, expressionNames) = _modelExpressions; + if (callStack.size()) + { + solAssert(m_scanner, ""); + if (_additionalValue) + { + expressionsToEvaluate.emplace_back(*_additionalValue); + expressionNames.push_back(_additionalValueName); + } + } + smt::CheckResult result; + vector values; + tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); + + string extraComment = SMTEncoder::extraComment(); + if (m_loopExecutionHappened) + extraComment += + "\nNote that some information is erased after the execution of loops.\n" + "You can re-introduce information using require()."; + if (m_externalFunctionCallHappened) + extraComment+= + "\nNote that external function calls are not inlined," + " even if the source code of the function is available." + " This is due to the possibility that the actual called contract" + " has the same ABI but implements the function differently."; + + SecondarySourceLocation secondaryLocation{}; + secondaryLocation.append(extraComment, SourceLocation{}); + + switch (result) + { + case smt::CheckResult::SATISFIABLE: + { + std::ostringstream message; + message << _description << " happens here"; + if (callStack.size()) + { + std::ostringstream modelMessage; + modelMessage << " for:\n"; + solAssert(values.size() == expressionNames.size(), ""); + map sortedModel; + for (size_t i = 0; i < values.size(); ++i) + if (expressionsToEvaluate.at(i).name != values.at(i)) + sortedModel[expressionNames.at(i)] = values.at(i); + + for (auto const& eval: sortedModel) + modelMessage << " " << eval.first << " = " << eval.second << "\n"; + m_errorReporter.warning( + _location, + message.str(), + SecondarySourceLocation().append(modelMessage.str(), SourceLocation{}) + .append(SMTEncoder::callStackMessage(callStack)) + .append(move(secondaryLocation)) + ); + } + else + { + message << "."; + m_errorReporter.warning(_location, message.str(), secondaryLocation); + } + break; + } + case smt::CheckResult::UNSATISFIABLE: + break; + case smt::CheckResult::UNKNOWN: + m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation); + break; + case smt::CheckResult::CONFLICTING: + m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); + break; + case smt::CheckResult::ERROR: + m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); + break; + } + + m_interface->pop(); +} + +void BMC::checkBooleanNotConstant( + Expression const& _condition, + smt::Expression const& _constraints, + smt::Expression const& _value, + vector const& _callStack, + string const& _description +) +{ + // Do not check for const-ness if this is a constant. + if (dynamic_cast(&_condition)) + return; + + m_interface->push(); + m_interface->addAssertion(_constraints && _value); + auto positiveResult = checkSatisfiable(); + m_interface->pop(); + + m_interface->push(); + m_interface->addAssertion(_constraints && !_value); + auto negatedResult = checkSatisfiable(); + m_interface->pop(); + + if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) + m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); + else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING) + m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound."); + else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE) + { + // everything fine. + } + else if (positiveResult == smt::CheckResult::UNKNOWN || negatedResult == smt::CheckResult::UNKNOWN) + { + // can't do anything. + } + else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE) + m_errorReporter.warning(_condition.location(), "Condition unreachable.", SMTEncoder::callStackMessage(_callStack)); + else + { + string value; + if (positiveResult == smt::CheckResult::SATISFIABLE) + { + solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, ""); + value = "true"; + } + else + { + solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, ""); + solAssert(negatedResult == smt::CheckResult::SATISFIABLE, ""); + value = "false"; + } + m_errorReporter.warning( + _condition.location(), + boost::algorithm::replace_all_copy(_description, "$VALUE", value), + SMTEncoder::callStackMessage(_callStack) + ); + } +} + +pair> +BMC::checkSatisfiableAndGenerateModel(vector const& _expressionsToEvaluate) +{ + smt::CheckResult result; + vector values; + try + { + tie(result, values) = m_interface->check(_expressionsToEvaluate); + } + catch (smt::SolverError const& _e) + { + string description("Error querying SMT solver"); + if (_e.comment()) + description += ": " + *_e.comment(); + m_errorReporter.warning(description); + result = smt::CheckResult::ERROR; + } + + for (string& value: values) + { + try + { + // Parse and re-format nicely + value = formatNumberReadable(bigint(value)); + } + catch (...) { } + } + + return make_pair(result, values); +} + +smt::CheckResult BMC::checkSatisfiable() +{ + return checkSatisfiableAndGenerateModel({}).first; +} + diff --git a/libsolidity/formal/BMC.h b/libsolidity/formal/BMC.h new file mode 100644 index 000000000..d25cc5fdc --- /dev/null +++ b/libsolidity/formal/BMC.h @@ -0,0 +1,180 @@ +/* + 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 . +*/ +/** + * Class that implements an SMT-based Bounded Model Checker (BMC). + * Traverses the AST such that: + * - Loops are unrolled + * - Internal function calls are inlined + * Creates verification targets for: + * - Underflow/Overflow + * - Constant conditions + * - Assertions + */ + +#pragma once + + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ + +class BMC: public SMTEncoder +{ +public: + BMC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + + void analyze(SourceUnit const& _sources, std::shared_ptr const& _scanner); + + /// This is used if the SMT solver is not directly linked into this binary. + /// @returns a list of inputs to the SMT solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries() { return m_interface->unhandledQueries(); } + + /// @returns the FunctionDefinition of a called function if possible and should inline, + /// otherwise nullptr. + static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall); + + std::shared_ptr solver() { return m_interface; } + +private: + /// AST visitors. + /// Only nodes that lead to verification targets being built + /// or checked are visited. + //@{ + bool visit(ContractDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const& _node) override; + bool visit(ForStatement const& _node) override; + void endVisit(UnaryOperation const& _node) override; + void endVisit(FunctionCall const& _node) override; + //@} + + /// Visitor helpers. + //@{ + void visitAssert(FunctionCall const& _funCall); + void visitRequire(FunctionCall const& _funCall); + /// Visits the FunctionDefinition of the called function + /// if available and inlines the return value. + void inlineFunctionCall(FunctionCall const& _funCall); + /// Creates an uninterpreted function call. + void abstractFunctionCall(FunctionCall const& _funCall); + /// Inlines if the function call is internal or external to `this`. + /// Erases knowledge about state variables if external. + void internalOrExternalFunctionCall(FunctionCall const& _funCall); + + /// Creates underflow/overflow verification targets. + std::pair arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& _expression + ) override; + + void resetStorageReferences(); + void reset(); + + std::pair, std::vector> modelExpressions(); + //@} + + /// Verification targets. + //@{ + struct VerificationTarget + { + enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type; + smt::Expression value; + smt::Expression constraints; + Expression const* expression; + std::vector callStack; + std::pair, std::vector> modelExpressions; + }; + + void checkVerificationTargets(smt::Expression const& _constraints); + void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true)); + void checkConstantCondition(VerificationTarget& _target); + void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints); + void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints); + void checkDivByZero(VerificationTarget& _target); + void checkBalance(VerificationTarget& _target); + void checkAssert(VerificationTarget& _target); + void addVerificationTarget( + VerificationTarget::Type _type, + smt::Expression const& _value, + Expression const* _expression + ); + //@} + + /// Solver related. + //@{ + /// Check that a condition can be satisfied. + void checkCondition( + smt::Expression _condition, + std::vector const& callStack, + std::pair, std::vector> const& _modelExpressions, + langutil::SourceLocation const& _location, + std::string const& _description, + std::string const& _additionalValueName = "", + smt::Expression const* _additionalValue = nullptr + ); + /// Checks that a boolean condition is not constant. Do not warn if the expression + /// is a literal constant. + /// @param _description the warning string, $VALUE will be replaced by the constant value. + void checkBooleanNotConstant( + Expression const& _condition, + smt::Expression const& _constraints, + smt::Expression const& _value, + std::vector const& _callStack, + std::string const& _description + ); + std::pair> + checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate); + + smt::CheckResult checkSatisfiable(); + //@} + + /// Flags used for better warning messages. + bool m_loopExecutionHappened = false; + bool m_externalFunctionCallHappened = false; + + /// ErrorReporter that comes from CompilerStack. + langutil::ErrorReporter& m_outerErrorReporter; + + std::vector m_verificationTargets; +}; + +} +} diff --git a/libsolidity/formal/ModelChecker.cpp b/libsolidity/formal/ModelChecker.cpp new file mode 100644 index 000000000..70c25fb78 --- /dev/null +++ b/libsolidity/formal/ModelChecker.cpp @@ -0,0 +1,42 @@ +/* + 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 . +*/ + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +ModelChecker::ModelChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): + m_bmc(m_context, _errorReporter, _smtlib2Responses), + m_context(m_bmc.solver()) +{ +} + +void ModelChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) +{ + if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) + return; + + m_bmc.analyze(_source, _scanner); +} + +vector ModelChecker::unhandledQueries() +{ + return m_bmc.unhandledQueries(); +} diff --git a/libsolidity/formal/ModelChecker.h b/libsolidity/formal/ModelChecker.h new file mode 100644 index 000000000..96e8be910 --- /dev/null +++ b/libsolidity/formal/ModelChecker.h @@ -0,0 +1,64 @@ +/* + 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 . +*/ +/** + * Entry point to the model checking engines. + * The goal of this class is to make different + * engines share knowledge to boost their proving power. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ + +class ModelChecker +{ +public: + ModelChecker(langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + + void analyze(SourceUnit const& _sources, std::shared_ptr const& _scanner); + + /// This is used if the SMT solver is not directly linked into this binary. + /// @returns a list of inputs to the SMT solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries(); + +private: + /// Bounded Model Checker engine. + BMC m_bmc; + + /// Stores the context of the encoding. + smt::EncodingContext m_context; +}; + +} +} diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTEncoder.cpp similarity index 58% rename from libsolidity/formal/SMTChecker.cpp rename to libsolidity/formal/SMTEncoder.cpp index 4f7929b01..30aa95226 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -15,71 +15,27 @@ along with solidity. If not, see . */ -#include +#include #include #include #include -#include - -#include #include -#include -#include using namespace std; using namespace dev; using namespace langutil; using namespace dev::solidity; -SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): +SMTEncoder::SMTEncoder(smt::EncodingContext& _context, map const& _smtlib2Responses): m_interface(make_shared(_smtlib2Responses)), - m_errorReporterReference(_errorReporter), m_errorReporter(m_smtErrors), - m_context(m_interface) + m_context(_context) { -#if defined (HAVE_Z3) || defined (HAVE_CVC4) - if (!_smtlib2Responses.empty()) - m_errorReporter.warning( - "SMT-LIB2 query responses were given in the auxiliary input, " - "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." - "These responses will be ignored." - "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." - ); -#endif } -void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) -{ - if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) - return; - - m_scanner = _scanner; - - _source.accept(*this); - - solAssert(m_interface->solvers() > 0, ""); - // If this check is true, Z3 and CVC4 are not available - // and the query answers were not provided, since SMTPortfolio - // guarantees that SmtLib2Interface is the first solver. - if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1) - { - if (!m_noSolverWarning) - { - m_noSolverWarning = true; - m_errorReporterReference.warning( - SourceLocation(), - "SMTChecker analysis was not possible since no integrated SMT solver (Z3 or CVC4) was found." - ); - } - } - else - m_errorReporterReference.append(m_errorReporter.errors()); - m_errorReporter.clear(); -} - -bool SMTChecker::visit(ContractDefinition const& _contract) +bool SMTEncoder::visit(ContractDefinition const& _contract) { for (auto const& contract: _contract.annotation().linearizedBaseContracts) for (auto var: contract->stateVariables()) @@ -88,40 +44,28 @@ bool SMTChecker::visit(ContractDefinition const& _contract) return true; } -void SMTChecker::endVisit(ContractDefinition const&) +void SMTEncoder::endVisit(ContractDefinition const&) { m_context.resetAllVariables(); } -void SMTChecker::endVisit(VariableDeclaration const& _varDecl) +void SMTEncoder::endVisit(VariableDeclaration const& _varDecl) { if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) assignment(_varDecl, *_varDecl.value()); } -bool SMTChecker::visit(ModifierDefinition const&) +bool SMTEncoder::visit(ModifierDefinition const&) { return false; } -bool SMTChecker::visit(FunctionDefinition const& _function) +bool SMTEncoder::visit(FunctionDefinition const& _function) { // Not visited by a function call if (m_callStack.empty()) - { - m_interface->reset(); - m_context.reset(); - m_context.pushSolver(); - m_pathConditions.clear(); - pushCallStack({&_function, nullptr}); - m_uninterpretedTerms.clear(); - m_verificationTargets.clear(); - resetStateVariables(); - initializeLocalVariables(_function); - m_loopExecutionHappened = false; - m_arrayAssignmentHappened = false; - m_externalFunctionCallHappened = false; - } + initFunction(_function); + m_modifierDepthStack.push_back(-1); if (_function.isConstructor()) { @@ -140,7 +84,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) return false; } -void SMTChecker::visitFunctionOrModifier() +void SMTEncoder::visitFunctionOrModifier() { solAssert(!m_callStack.empty(), ""); solAssert(!m_modifierDepthStack.empty(), ""); @@ -175,7 +119,7 @@ void SMTChecker::visitFunctionOrModifier() --m_modifierDepthStack.back(); } -bool SMTChecker::visit(PlaceholderStatement const&) +bool SMTEncoder::visit(PlaceholderStatement const&) { solAssert(!m_callStack.empty(), ""); auto lastCall = popCallStack(); @@ -184,20 +128,16 @@ bool SMTChecker::visit(PlaceholderStatement const&) return true; } -void SMTChecker::endVisit(FunctionDefinition const&) +void SMTEncoder::endVisit(FunctionDefinition const&) { popCallStack(); solAssert(m_modifierDepthStack.back() == -1, ""); m_modifierDepthStack.pop_back(); if (m_callStack.empty()) - { - checkVerificationTargets(m_context.assertions()); - m_verificationTargets.clear(); m_context.popSolver(); - } } -bool SMTChecker::visit(InlineAssembly const& _inlineAsm) +bool SMTEncoder::visit(InlineAssembly const& _inlineAsm) { m_errorReporter.warning( _inlineAsm.location(), @@ -206,19 +146,10 @@ bool SMTChecker::visit(InlineAssembly const& _inlineAsm) return false; } -bool SMTChecker::visit(IfStatement const& _node) +bool SMTEncoder::visit(IfStatement const& _node) { _node.condition().accept(*this); - // We ignore called functions here because they have - // specific input values. - if (isRootFunction()) - addVerificationTarget( - VerificationTarget::Type::ConstantCondition, - expr(_node.condition()), - &_node.condition() - ); - auto indicesEndTrue = visitBranch(&_node.trueStatement(), expr(_node.condition())); auto touchedVars = touchedVariables(_node.trueStatement()); decltype(indicesEndTrue) indicesEndFalse; @@ -235,111 +166,7 @@ bool SMTChecker::visit(IfStatement const& _node) return false; } -// Here we consider the execution of two branches: -// Branch 1 assumes the loop condition to be true and executes the loop once, -// after resetting touched variables. -// Branch 2 assumes the loop condition to be false and skips the loop after -// visiting the condition (it might contain side-effects, they need to be considered) -// and does not erase knowledge. -// If the loop is a do-while, condition side-effects are lost since the body, -// executed once before the condition, might reassign variables. -// Variables touched by the loop are merged with Branch 2. -bool SMTChecker::visit(WhileStatement const& _node) -{ - auto indicesBeforeLoop = copyVariableIndices(); - auto touchedVars = touchedVariables(_node); - m_context.resetVariables(touchedVars); - decltype(indicesBeforeLoop) indicesAfterLoop; - if (_node.isDoWhile()) - { - indicesAfterLoop = visitBranch(&_node.body()); - // TODO the assertions generated in the body should still be active in the condition - _node.condition().accept(*this); - if (isRootFunction()) - addVerificationTarget( - VerificationTarget::Type::ConstantCondition, - expr(_node.condition()), - &_node.condition() - ); - } - else - { - _node.condition().accept(*this); - if (isRootFunction()) - addVerificationTarget( - VerificationTarget::Type::ConstantCondition, - expr(_node.condition()), - &_node.condition() - ); - - indicesAfterLoop = visitBranch(&_node.body(), expr(_node.condition())); - } - - // We reset the execution to before the loop - // and visit the condition in case it's not a do-while. - // A do-while's body might have non-precise information - // in its first run about variables that are touched. - resetVariableIndices(indicesBeforeLoop); - if (!_node.isDoWhile()) - _node.condition().accept(*this); - - mergeVariables(touchedVars, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); - - m_loopExecutionHappened = true; - return false; -} - -// Here we consider the execution of two branches similar to WhileStatement. -bool SMTChecker::visit(ForStatement const& _node) -{ - if (_node.initializationExpression()) - _node.initializationExpression()->accept(*this); - - auto indicesBeforeLoop = copyVariableIndices(); - - // Do not reset the init expression part. - auto touchedVars = touchedVariables(_node.body()); - if (_node.condition()) - touchedVars += touchedVariables(*_node.condition()); - if (_node.loopExpression()) - touchedVars += touchedVariables(*_node.loopExpression()); - - m_context.resetVariables(touchedVars); - - if (_node.condition()) - { - _node.condition()->accept(*this); - if (isRootFunction()) - addVerificationTarget( - VerificationTarget::Type::ConstantCondition, - expr(*_node.condition()), - _node.condition() - ); - } - - m_context.pushSolver(); - if (_node.condition()) - m_context.addAssertion(expr(*_node.condition())); - _node.body().accept(*this); - if (_node.loopExpression()) - _node.loopExpression()->accept(*this); - m_context.popSolver(); - - auto indicesAfterLoop = copyVariableIndices(); - // We reset the execution to before the loop - // and visit the condition. - resetVariableIndices(indicesBeforeLoop); - if (_node.condition()) - _node.condition()->accept(*this); - - auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true); - mergeVariables(touchedVars, forCondition, indicesAfterLoop, copyVariableIndices()); - - m_loopExecutionHappened = true; - return false; -} - -void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) +void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl) { if (_varDecl.declarations().size() != 1) { @@ -375,7 +202,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) } -void SMTChecker::endVisit(Assignment const& _assignment) +void SMTEncoder::endVisit(Assignment const& _assignment) { static set const compoundOps{ Token::AssignAdd, @@ -431,7 +258,7 @@ void SMTChecker::endVisit(Assignment const& _assignment) } } -void SMTChecker::endVisit(TupleExpression const& _tuple) +void SMTEncoder::endVisit(TupleExpression const& _tuple) { if (_tuple.isInlineArray()) m_errorReporter.warning( @@ -470,7 +297,7 @@ void SMTChecker::endVisit(TupleExpression const& _tuple) } } -void SMTChecker::endVisit(UnaryOperation const& _op) +void SMTEncoder::endVisit(UnaryOperation const& _op) { if (_op.annotation().type->category() == Type::Category::RationalNumber) return; @@ -511,23 +338,11 @@ void SMTChecker::endVisit(UnaryOperation const& _op) "Assertion checker does not yet implement such increments / decrements." ); - addVerificationTarget( - _op.getOperator() == Token::Inc ? VerificationTarget::Type::Overflow : VerificationTarget::Type::Underflow, - expr(_op), - &_op - ); - break; } case Token::Sub: // - { defineExpr(_op, 0 - expr(_op.subExpression())); - if (_op.annotation().type->category() == Type::Category::Integer) - addVerificationTarget( - VerificationTarget::Type::UnderOverflow, - expr(_op), - &_op - ); break; } case Token::Delete: @@ -562,12 +377,12 @@ void SMTChecker::endVisit(UnaryOperation const& _op) } } -bool SMTChecker::visit(UnaryOperation const& _op) +bool SMTEncoder::visit(UnaryOperation const& _op) { return !shortcutRationalNumber(_op); } -bool SMTChecker::visit(BinaryOperation const& _op) +bool SMTEncoder::visit(BinaryOperation const& _op) { if (shortcutRationalNumber(_op)) return false; @@ -579,7 +394,7 @@ bool SMTChecker::visit(BinaryOperation const& _op) return true; } -void SMTChecker::endVisit(BinaryOperation const& _op) +void SMTEncoder::endVisit(BinaryOperation const& _op) { if (_op.annotation().type->category() == Type::Category::RationalNumber) return; @@ -597,7 +412,7 @@ void SMTChecker::endVisit(BinaryOperation const& _op) ); } -void SMTChecker::endVisit(FunctionCall const& _funCall) +void SMTEncoder::endVisit(FunctionCall const& _funCall) { solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); createExpr(_funCall); @@ -638,7 +453,6 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareStaticCall: case FunctionType::Kind::Creation: - internalOrExternalFunctionCall(_funCall); break; case FunctionType::Kind::KECCAK256: case FunctionType::Kind::ECRecover: @@ -647,7 +461,6 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) case FunctionType::Kind::BlockHash: case FunctionType::Kind::AddMod: case FunctionType::Kind::MulMod: - abstractFunctionCall(_funCall); break; case FunctionType::Kind::Send: case FunctionType::Kind::Transfer: @@ -660,12 +473,6 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) smt::Expression thisBalance = m_context.balance(); setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), *m_interface); - addVerificationTarget( - VerificationTarget::Type::Balance, - thisBalance < expr(*value), - &_funCall - ); - m_context.transfer(m_context.thisAddress(), expr(address), expr(*value)); break; } @@ -677,34 +484,36 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) } } -void SMTChecker::visitAssert(FunctionCall const& _funCall) +void SMTEncoder::initFunction(FunctionDefinition const& _function) +{ + solAssert(m_callStack.empty(), ""); + m_context.reset(); + m_context.pushSolver(); + m_pathConditions.clear(); + pushCallStack({&_function, nullptr}); + m_uninterpretedTerms.clear(); + resetStateVariables(); + initializeLocalVariables(_function); + m_arrayAssignmentHappened = false; +} + +void SMTEncoder::visitAssert(FunctionCall const& _funCall) { auto const& args = _funCall.arguments(); solAssert(args.size() == 1, ""); solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); - addVerificationTarget( - VerificationTarget::Type::Assert, - m_context.expression(*args.front())->currentValue(), - &_funCall - ); addPathImpliedExpression(expr(*args[0])); } -void SMTChecker::visitRequire(FunctionCall const& _funCall) +void SMTEncoder::visitRequire(FunctionCall const& _funCall) { auto const& args = _funCall.arguments(); solAssert(args.size() == 1, ""); solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); - if (isRootFunction()) - addVerificationTarget( - VerificationTarget::Type::ConstantCondition, - m_context.expression(*args.front())->currentValue(), - args.front().get() - ); addPathImpliedExpression(expr(*args[0])); } -void SMTChecker::visitGasLeft(FunctionCall const& _funCall) +void SMTEncoder::visitGasLeft(FunctionCall const& _funCall) { string gasLeft = "gasleft()"; // We increase the variable index since gasleft changes @@ -718,89 +527,7 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } -void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) -{ - FunctionDefinition const* funDef = inlinedFunctionCallToDefinition(_funCall); - solAssert(funDef, ""); - if (visitedFunction(funDef)) - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not support recursive function calls.", - SecondarySourceLocation().append("Starting from function:", funDef->location()) - ); - else - { - vector funArgs; - Expression const* calledExpr = &_funCall.expression(); - auto const& funType = dynamic_cast(calledExpr->annotation().type); - solAssert(funType, ""); - - if (funType->bound()) - { - auto const& boundFunction = dynamic_cast(calledExpr); - solAssert(boundFunction, ""); - funArgs.push_back(expr(boundFunction->expression())); - } - - for (auto arg: _funCall.arguments()) - funArgs.push_back(expr(*arg)); - initializeFunctionCallParameters(*funDef, funArgs); - - // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) - // is that there we don't have `_funCall`. - pushCallStack({funDef, &_funCall}); - funDef->accept(*this); - // The callstack entry is popped only in endVisit(FunctionDefinition) instead of here - // as well to avoid code duplication (not all entries are from inlined function calls). - - auto const& returnParams = funDef->returnParameters(); - if (returnParams.size() > 1) - { - vector> components; - for (auto param: returnParams) - { - solAssert(m_context.variable(*param), ""); - components.push_back(m_context.variable(*param)); - } - auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_funCall)); - solAssert(symbTuple, ""); - symbTuple->setComponents(move(components)); - } - else if (returnParams.size() == 1) - defineExpr(_funCall, currentValue(*returnParams.front())); - } -} - -void SMTChecker::internalOrExternalFunctionCall(FunctionCall const& _funCall) -{ - auto funDef = inlinedFunctionCallToDefinition(_funCall); - auto const& funType = dynamic_cast(*_funCall.expression().annotation().type); - if (funDef) - inlineFunctionCall(_funCall); - else if (funType.kind() == FunctionType::Kind::Internal) - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not yet implement this type of function call." - ); - else - { - m_externalFunctionCallHappened = true; - resetStateVariables(); - resetStorageReferences(); - } -} - -void SMTChecker::abstractFunctionCall(FunctionCall const& _funCall) -{ - vector smtArguments; - for (auto const& arg: _funCall.arguments()) - smtArguments.push_back(expr(*arg)); - defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments)); - m_uninterpretedTerms.insert(&_funCall); - setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, *m_interface); -} - -void SMTChecker::endVisit(Identifier const& _identifier) +void SMTEncoder::endVisit(Identifier const& _identifier) { if (_identifier.annotation().lValueRequested) { @@ -828,7 +555,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) } } -void SMTChecker::visitTypeConversion(FunctionCall const& _funCall) +void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall) { solAssert(_funCall.annotation().kind == FunctionCallKind::TypeConversion, ""); solAssert(_funCall.arguments().size() == 1, ""); @@ -864,7 +591,7 @@ void SMTChecker::visitTypeConversion(FunctionCall const& _funCall) } } -void SMTChecker::visitFunctionIdentifier(Identifier const& _identifier) +void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier) { auto const& fType = dynamic_cast(*_identifier.annotation().type); if (fType.returnParameterTypes().size() == 1) @@ -874,7 +601,7 @@ void SMTChecker::visitFunctionIdentifier(Identifier const& _identifier) } } -void SMTChecker::endVisit(Literal const& _literal) +void SMTEncoder::endVisit(Literal const& _literal) { solAssert(_literal.annotation().type, "Expected type for AST node"); Type const& type = *_literal.annotation().type; @@ -901,7 +628,7 @@ void SMTChecker::endVisit(Literal const& _literal) } } -void SMTChecker::endVisit(Return const& _return) +void SMTEncoder::endVisit(Return const& _return) { if (_return.expression() && m_context.knownExpression(*_return.expression())) { @@ -921,7 +648,7 @@ void SMTChecker::endVisit(Return const& _return) } } -bool SMTChecker::visit(MemberAccess const& _memberAccess) +bool SMTEncoder::visit(MemberAccess const& _memberAccess) { auto const& accessType = _memberAccess.annotation().type; if (accessType->category() == Type::Category::Function) @@ -974,7 +701,7 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) return true; } -void SMTChecker::endVisit(IndexAccess const& _indexAccess) +void SMTEncoder::endVisit(IndexAccess const& _indexAccess) { shared_ptr array; if (auto const& id = dynamic_cast(&_indexAccess.baseExpression())) @@ -1011,12 +738,12 @@ void SMTChecker::endVisit(IndexAccess const& _indexAccess) m_uninterpretedTerms.insert(&_indexAccess); } -void SMTChecker::arrayAssignment() +void SMTEncoder::arrayAssignment() { m_arrayAssignmentHappened = true; } -void SMTChecker::arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide) +void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide) { auto const& indexAccess = dynamic_cast(_expr); if (auto const& id = dynamic_cast(&indexAccess.baseExpression())) @@ -1087,7 +814,7 @@ void SMTChecker::arrayIndexAssignment(Expression const& _expr, smt::Expression c ); } -void SMTChecker::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) +void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) { if (!m_context.knownGlobalSymbol(_name)) { @@ -1106,7 +833,7 @@ void SMTChecker::defineGlobalVariable(string const& _name, Expression const& _ex defineExpr(_expr, m_context.globalSymbol(_name)->currentValue()); } -bool SMTChecker::shortcutRationalNumber(Expression const& _expr) +bool SMTEncoder::shortcutRationalNumber(Expression const& _expr) { if (_expr.annotation().type->category() == Type::Category::RationalNumber) { @@ -1121,7 +848,7 @@ bool SMTChecker::shortcutRationalNumber(Expression const& _expr) return false; } -void SMTChecker::arithmeticOperation(BinaryOperation const& _op) +void SMTEncoder::arithmeticOperation(BinaryOperation const& _op) { auto type = _op.annotation().commonType; solAssert(type, ""); @@ -1135,13 +862,14 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) case Token::Div: case Token::Mod: { - defineExpr(_op, arithmeticOperation( + auto values = arithmeticOperation( _op.getOperator(), expr(_op.leftExpression()), expr(_op.rightExpression()), _op.annotation().commonType, _op - )); + ); + defineExpr(_op, values.first); break; } default: @@ -1158,12 +886,12 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) ); } -smt::Expression SMTChecker::arithmeticOperation( +pair SMTEncoder::arithmeticOperation( Token _op, smt::Expression const& _left, smt::Expression const& _right, TypePointer const& _commonType, - Expression const& _expression + Expression const& ) { static set validOperators{ @@ -1178,7 +906,7 @@ smt::Expression SMTChecker::arithmeticOperation( solAssert(_commonType->category() == Type::Category::Integer, ""); auto const& intType = dynamic_cast(*_commonType); - smt::Expression value( + smt::Expression valueNoMod( _op == Token::Add ? _left + _right : _op == Token::Sub ? _left - _right : _op == Token::Div ? division(_left, _right, intType) : @@ -1187,26 +915,13 @@ smt::Expression SMTChecker::arithmeticOperation( ); if (_op == Token::Div || _op == Token::Mod) - { - addVerificationTarget( - VerificationTarget::Type::DivByZero, - _right, - &_expression - ); m_context.addAssertion(_right != 0); - } - - addVerificationTarget( - VerificationTarget::Type::UnderOverflow, - value, - &_expression - ); smt::Expression intValueRange = (0 - smt::minValue(intType)) + smt::maxValue(intType) + 1; - value = smt::Expression::ite( - value > smt::maxValue(intType) || value < smt::minValue(intType), - value % intValueRange, - value + auto value = smt::Expression::ite( + valueNoMod > smt::maxValue(intType) || valueNoMod < smt::minValue(intType), + valueNoMod % intValueRange, + valueNoMod ); if (intType.isSigned()) { @@ -1217,10 +932,10 @@ smt::Expression SMTChecker::arithmeticOperation( ); } - return value; + return {value, valueNoMod}; } -void SMTChecker::compareOperation(BinaryOperation const& _op) +void SMTEncoder::compareOperation(BinaryOperation const& _op) { solAssert(_op.annotation().commonType, ""); if (smt::isSupportedType(_op.annotation().commonType->category())) @@ -1258,7 +973,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op) ); } -void SMTChecker::booleanOperation(BinaryOperation const& _op) +void SMTEncoder::booleanOperation(BinaryOperation const& _op) { solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, ""); solAssert(_op.annotation().commonType, ""); @@ -1286,7 +1001,7 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) ); } -smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) +smt::Expression SMTEncoder::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) { // Signed division in SMTLIB2 rounds differently for negative division. if (_type.isSigned()) @@ -1299,7 +1014,7 @@ smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _rig return _left / _right; } -void SMTChecker::assignment( +void SMTEncoder::assignment( Expression const& _left, vector const& _right, TypePointer const& _type, @@ -1339,7 +1054,7 @@ void SMTChecker::assignment( ); } -smt::Expression SMTChecker::compoundAssignment(Assignment const& _assignment) +smt::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment) { static map const compoundToArithmetic{ {Token::AssignAdd, Token::Add}, @@ -1351,21 +1066,22 @@ smt::Expression SMTChecker::compoundAssignment(Assignment const& _assignment) Token op = _assignment.assignmentOperator(); solAssert(compoundToArithmetic.count(op), ""); auto decl = identifierToVariable(_assignment.leftHandSide()); - return arithmeticOperation( + auto values = arithmeticOperation( compoundToArithmetic.at(op), decl ? currentValue(*decl) : expr(_assignment.leftHandSide()), expr(_assignment.rightHandSide()), _assignment.annotation().type, _assignment ); + return values.first; } -void SMTChecker::assignment(VariableDeclaration const& _variable, Expression const& _value) +void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value) { assignment(_variable, expr(_value)); } -void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expression const& _value) +void SMTEncoder::assignment(VariableDeclaration const& _variable, smt::Expression const& _value) { TypePointer type = _variable.type(); if (type->category() == Type::Category::Mapping) @@ -1373,12 +1089,12 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio m_context.addAssertion(m_context.newValue(_variable) == _value); } -SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression _condition) +SMTEncoder::VariableIndices SMTEncoder::visitBranch(ASTNode const* _statement, smt::Expression _condition) { return visitBranch(_statement, &_condition); } -SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression const* _condition) +SMTEncoder::VariableIndices SMTEncoder::visitBranch(ASTNode const* _statement, smt::Expression const* _condition) { auto indicesBeforeBranch = copyVariableIndices(); if (_condition) @@ -1391,383 +1107,7 @@ SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, s return indicesAfterBranch; } -/// Verification targets. - -void SMTChecker::checkVerificationTargets(smt::Expression const& _constraints) -{ - for (auto& target: m_verificationTargets) - checkVerificationTarget(target, _constraints); -} - -void SMTChecker::checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints) -{ - switch (_target.type) - { - case VerificationTarget::Type::ConstantCondition: - checkConstantCondition(_target); - break; - case VerificationTarget::Type::Underflow: - checkUnderflow(_target, _constraints); - break; - case VerificationTarget::Type::Overflow: - checkOverflow(_target, _constraints); - break; - case VerificationTarget::Type::UnderOverflow: - checkUnderflow(_target, _constraints); - checkOverflow(_target, _constraints); - break; - case VerificationTarget::Type::DivByZero: - checkDivByZero(_target); - break; - case VerificationTarget::Type::Balance: - checkBalance(_target); - break; - case VerificationTarget::Type::Assert: - checkAssert(_target); - break; - default: - solAssert(false, ""); - } -} - -void SMTChecker::checkConstantCondition(VerificationTarget& _target) -{ - checkBooleanNotConstant( - *_target.expression, - _target.constraints, - _target.value, - _target.callStack, - "Condition is always $VALUE." - ); -} - -void SMTChecker::checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints) -{ - solAssert( - _target.type == VerificationTarget::Type::Underflow || - _target.type == VerificationTarget::Type::UnderOverflow, - "" - ); - auto intType = dynamic_cast(_target.expression->annotation().type); - solAssert(intType, ""); - checkCondition( - _target.constraints && _constraints && _target.value < smt::minValue(*intType), - _target.callStack, - _target.modelExpressions, - _target.expression->location(), - "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")", - "", - &_target.value - ); -} - -void SMTChecker::checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints) -{ - solAssert( - _target.type == VerificationTarget::Type::Overflow || - _target.type == VerificationTarget::Type::UnderOverflow, - "" - ); - auto intType = dynamic_cast(_target.expression->annotation().type); - solAssert(intType, ""); - checkCondition( - _target.constraints && _constraints && _target.value > smt::maxValue(*intType), - _target.callStack, - _target.modelExpressions, - _target.expression->location(), - "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")", - "", - &_target.value - ); -} - -void SMTChecker::checkDivByZero(VerificationTarget& _target) -{ - solAssert(_target.type == VerificationTarget::Type::DivByZero, ""); - checkCondition( - _target.constraints && (_target.value == 0), - _target.callStack, - _target.modelExpressions, - _target.expression->location(), - "Division by zero", - "", - &_target.value - ); -} - -void SMTChecker::checkBalance(VerificationTarget& _target) -{ - solAssert(_target.type == VerificationTarget::Type::Balance, ""); - checkCondition( - _target.constraints && _target.value, - _target.callStack, - _target.modelExpressions, - _target.expression->location(), - "Insufficient funds", - "address(this).balance" - ); -} - -void SMTChecker::checkAssert(VerificationTarget& _target) -{ - solAssert(_target.type == VerificationTarget::Type::Assert, ""); - checkCondition( - _target.constraints && !_target.value, - _target.callStack, - _target.modelExpressions, - _target.expression->location(), - "Assertion violation" - ); -} - -void SMTChecker::addVerificationTarget( - VerificationTarget::Type _type, - smt::Expression const& _value, - Expression const* _expression -) -{ - VerificationTarget target{ - _type, - _value, - currentPathConditions() && m_context.assertions(), - _expression, - m_callStack, - modelExpressions() - }; - if (_type == VerificationTarget::Type::ConstantCondition) - checkVerificationTarget(target); - else - m_verificationTargets.emplace_back(move(target)); -} - -pair, vector> SMTChecker::modelExpressions() -{ - vector expressionsToEvaluate; - vector expressionNames; - for (auto const& var: m_context.variables()) - { - if (var.first->type()->isValueType()) - { - expressionsToEvaluate.emplace_back(m_context.variable(*var.first)->currentValue()); - expressionNames.push_back(var.first->name()); - } - } - for (auto const& var: m_context.globalSymbols()) - { - auto const& type = var.second->type(); - if ( - type->isValueType() && - smt::smtKind(type->category()) != smt::Kind::Function - ) - { - expressionsToEvaluate.emplace_back(var.second->currentValue()); - expressionNames.push_back(var.first); - } - } - solAssert(m_scanner, ""); - for (auto const& uf: m_uninterpretedTerms) - { - if (uf->annotation().type->isValueType()) - { - expressionsToEvaluate.emplace_back(expr(*uf)); - expressionNames.push_back(m_scanner->sourceAt(uf->location())); - } - } - - return {expressionsToEvaluate, expressionNames}; -} - -void SMTChecker::checkCondition( - smt::Expression const& _condition, - vector const& callStack, - pair, vector> const& _modelExpressions, - SourceLocation const& _location, - string const& _description, - string const& _additionalValueName, - smt::Expression const* _additionalValue -) -{ - m_interface->push(); - m_interface->addAssertion(_condition); - - vector expressionsToEvaluate; - vector expressionNames; - tie(expressionsToEvaluate, expressionNames) = _modelExpressions; - if (callStack.size()) - if (_additionalValue) - { - expressionsToEvaluate.emplace_back(*_additionalValue); - expressionNames.push_back(_additionalValueName); - } - smt::CheckResult result; - vector values; - tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); - - string extraComment; - if (m_loopExecutionHappened) - extraComment = - "\nNote that some information is erased after the execution of loops.\n" - "You can re-introduce information using require()."; - if (m_arrayAssignmentHappened) - extraComment += - "\nNote that array aliasing is not supported," - " therefore all mapping information is erased after" - " a mapping local variable/parameter is assigned.\n" - "You can re-introduce information using require()."; - if (m_externalFunctionCallHappened) - extraComment += - "\nNote that external function calls are not inlined," - " even if the source code of the function is available." - " This is due to the possibility that the actual called contract" - " has the same ABI but implements the function differently."; - - SecondarySourceLocation secondaryLocation{}; - secondaryLocation.append(extraComment, SourceLocation{}); - - switch (result) - { - case smt::CheckResult::SATISFIABLE: - { - std::ostringstream message; - message << _description << " happens here"; - if (callStack.size()) - { - std::ostringstream modelMessage; - modelMessage << " for:\n"; - solAssert(values.size() == expressionNames.size(), ""); - map sortedModel; - for (size_t i = 0; i < values.size(); ++i) - if (expressionsToEvaluate.at(i).name != values.at(i)) - sortedModel[expressionNames.at(i)] = values.at(i); - - for (auto const& eval: sortedModel) - modelMessage << " " << eval.first << " = " << eval.second << "\n"; - m_errorReporter.warning( - _location, - message.str(), - SecondarySourceLocation().append(modelMessage.str(), SourceLocation{}) - .append(callStackMessage(callStack)) - .append(move(secondaryLocation)) - ); - } - else - { - message << "."; - m_errorReporter.warning(_location, message.str(), secondaryLocation); - } - break; - } - case smt::CheckResult::UNSATISFIABLE: - break; - case smt::CheckResult::UNKNOWN: - m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation); - break; - case smt::CheckResult::CONFLICTING: - m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); - break; - case smt::CheckResult::ERROR: - m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); - break; - } - - m_interface->pop(); -} - -void SMTChecker::checkBooleanNotConstant( - Expression const& _condition, - smt::Expression const& _constraints, - smt::Expression const& _value, - vector const& _callStack, - string const& _description -) -{ - // Do not check for const-ness if this is a constant. - if (dynamic_cast(&_condition)) - return; - - m_interface->push(); - m_interface->addAssertion(_constraints && _value); - auto positiveResult = checkSatisfiable(); - m_interface->pop(); - - m_interface->push(); - m_interface->addAssertion(_constraints && !_value); - auto negatedResult = checkSatisfiable(); - m_interface->pop(); - - if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) - m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); - else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING) - m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound."); - else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE) - { - // everything fine. - } - else if (positiveResult == smt::CheckResult::UNKNOWN || negatedResult == smt::CheckResult::UNKNOWN) - { - // can't do anything. - } - else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE) - m_errorReporter.warning(_condition.location(), "Condition unreachable.", callStackMessage(_callStack)); - else - { - string value; - if (positiveResult == smt::CheckResult::SATISFIABLE) - { - solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, ""); - value = "true"; - } - else - { - solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, ""); - solAssert(negatedResult == smt::CheckResult::SATISFIABLE, ""); - value = "false"; - } - m_errorReporter.warning( - _condition.location(), - boost::algorithm::replace_all_copy(_description, "$VALUE", value), - callStackMessage(_callStack) - ); - } -} - -pair> -SMTChecker::checkSatisfiableAndGenerateModel(vector const& _expressionsToEvaluate) -{ - smt::CheckResult result; - vector values; - try - { - tie(result, values) = m_interface->check(_expressionsToEvaluate); - } - catch (smt::SolverError const& _e) - { - string description("Error querying SMT solver"); - if (_e.comment()) - description += ": " + *_e.comment(); - m_errorReporter.warning(description); - result = smt::CheckResult::ERROR; - } - - for (string& value: values) - { - try - { - // Parse and re-format nicely - value = formatNumberReadable(bigint(value)); - } - catch (...) { } - } - - return make_pair(result, values); -} - -smt::CheckResult SMTChecker::checkSatisfiable() -{ - return checkSatisfiableAndGenerateModel({}).first; -} - -void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _function, vector const& _callArgs) +void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _function, vector const& _callArgs) { auto const& funParams = _function.parameters(); solAssert(funParams.size() == _callArgs.size(), ""); @@ -1795,7 +1135,7 @@ void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _fu } } -void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) +void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function) { for (auto const& variable: _function.localVariables()) if (createVariable(*variable)) @@ -1811,24 +1151,19 @@ void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) m_context.setZeroValue(*retParam); } -void SMTChecker::resetStateVariables() +void SMTEncoder::resetStateVariables() { m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.isStateVariable(); }); } -void SMTChecker::resetStorageReferences() -{ - m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); -} - -TypePointer SMTChecker::typeWithoutPointer(TypePointer const& _type) +TypePointer SMTEncoder::typeWithoutPointer(TypePointer const& _type) { if (auto refType = dynamic_cast(_type)) return TypeProvider::withLocationIfReference(refType->location(), _type); return _type; } -void SMTChecker::mergeVariables(set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) +void SMTEncoder::mergeVariables(set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) { auto cmp = [] (VariableDeclaration const* var1, VariableDeclaration const* var2) { return var1->id() < var2->id(); @@ -1848,19 +1183,19 @@ void SMTChecker::mergeVariables(set const& _variable } } -smt::Expression SMTChecker::currentValue(VariableDeclaration const& _decl) +smt::Expression SMTEncoder::currentValue(VariableDeclaration const& _decl) { solAssert(m_context.knownVariable(_decl), ""); return m_context.variable(_decl)->currentValue(); } -smt::Expression SMTChecker::valueAtIndex(VariableDeclaration const& _decl, int _index) +smt::Expression SMTEncoder::valueAtIndex(VariableDeclaration const& _decl, int _index) { solAssert(m_context.knownVariable(_decl), ""); return m_context.variable(_decl)->valueAtIndex(_index); } -bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) +bool SMTEncoder::createVariable(VariableDeclaration const& _varDecl) { if (m_context.knownVariable(_varDecl)) return true; @@ -1876,7 +1211,7 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) return true; } -smt::Expression SMTChecker::expr(Expression const& _e) +smt::Expression SMTEncoder::expr(Expression const& _e) { if (!m_context.knownExpression(_e)) { @@ -1886,7 +1221,7 @@ smt::Expression SMTChecker::expr(Expression const& _e) return m_context.expression(_e)->currentValue(); } -void SMTChecker::createExpr(Expression const& _e) +void SMTEncoder::createExpr(Expression const& _e) { bool abstract = m_context.createExpression(_e); if (abstract) @@ -1896,32 +1231,32 @@ void SMTChecker::createExpr(Expression const& _e) ); } -void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) +void SMTEncoder::defineExpr(Expression const& _e, smt::Expression _value) { createExpr(_e); solAssert(smt::smtKind(_e.annotation().type->category()) != smt::Kind::Function, "Equality operator applied to type that is not fully supported"); m_context.addAssertion(expr(_e) == _value); } -void SMTChecker::popPathCondition() +void SMTEncoder::popPathCondition() { solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); m_pathConditions.pop_back(); } -void SMTChecker::pushPathCondition(smt::Expression const& _e) +void SMTEncoder::pushPathCondition(smt::Expression const& _e) { m_pathConditions.push_back(currentPathConditions() && _e); } -smt::Expression SMTChecker::currentPathConditions() +smt::Expression SMTEncoder::currentPathConditions() { if (m_pathConditions.empty()) return smt::Expression(true); return m_pathConditions.back(); } -SecondarySourceLocation SMTChecker::callStackMessage(vector const& _callStack) +SecondarySourceLocation SMTEncoder::callStackMessage(vector const& _callStack) { SecondarySourceLocation callStackLocation; solAssert(!_callStack.empty(), ""); @@ -1934,7 +1269,7 @@ SecondarySourceLocation SMTChecker::callStackMessage(vector cons return callStackLocation; } -pair SMTChecker::popCallStack() +pair SMTEncoder::popCallStack() { solAssert(!m_callStack.empty(), ""); auto lastCalled = m_callStack.back(); @@ -1942,27 +1277,22 @@ pair SMTChecker::popCallStack() return lastCalled; } -void SMTChecker::pushCallStack(CallStackEntry _entry) +void SMTEncoder::pushCallStack(CallStackEntry _entry) { m_callStack.push_back(_entry); } -void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) -{ - m_context.addAssertion(currentPathConditions() && _e); -} - -void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) +void SMTEncoder::addPathImpliedExpression(smt::Expression const& _e) { m_context.addAssertion(smt::Expression::implies(currentPathConditions(), _e)); } -bool SMTChecker::isRootFunction() +bool SMTEncoder::isRootFunction() { return m_callStack.size() == 1; } -bool SMTChecker::visitedFunction(FunctionDefinition const* _funDef) +bool SMTEncoder::visitedFunction(FunctionDefinition const* _funDef) { for (auto const& call: m_callStack) if (call.first == _funDef) @@ -1970,7 +1300,7 @@ bool SMTChecker::visitedFunction(FunctionDefinition const* _funDef) return false; } -SMTChecker::VariableIndices SMTChecker::copyVariableIndices() +SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices() { VariableIndices indices; for (auto const& var: m_context.variables()) @@ -1978,56 +1308,13 @@ SMTChecker::VariableIndices SMTChecker::copyVariableIndices() return indices; } -void SMTChecker::resetVariableIndices(VariableIndices const& _indices) +void SMTEncoder::resetVariableIndices(VariableIndices const& _indices) { for (auto const& var: _indices) m_context.variable(*var.first)->index() = var.second; } -FunctionDefinition const* SMTChecker::inlinedFunctionCallToDefinition(FunctionCall const& _funCall) -{ - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) - return nullptr; - - FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); - if (funType.kind() == FunctionType::Kind::External) - { - auto memberAccess = dynamic_cast(&_funCall.expression()); - auto identifier = memberAccess ? - dynamic_cast(&memberAccess->expression()) : - nullptr; - if (!( - identifier && - identifier->name() == "this" && - identifier->annotation().referencedDeclaration && - dynamic_cast(identifier->annotation().referencedDeclaration) - )) - return nullptr; - } - else if (funType.kind() != FunctionType::Kind::Internal) - return nullptr; - - FunctionDefinition const* funDef = nullptr; - Expression const* calledExpr = &_funCall.expression(); - - if (TupleExpression const* fun = dynamic_cast(&_funCall.expression())) - { - solAssert(fun->components().size() == 1, ""); - calledExpr = fun->components().front().get(); - } - - if (Identifier const* fun = dynamic_cast(calledExpr)) - funDef = dynamic_cast(fun->annotation().referencedDeclaration); - else if (MemberAccess const* fun = dynamic_cast(calledExpr)) - funDef = dynamic_cast(fun->annotation().referencedDeclaration); - - if (funDef && funDef->isImplemented()) - return funDef; - - return nullptr; -} - -Expression const* SMTChecker::leftmostBase(IndexAccess const& _indexAccess) +Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess) { Expression const* base = &_indexAccess.baseExpression(); while (auto access = dynamic_cast(base)) @@ -2035,7 +1322,7 @@ Expression const* SMTChecker::leftmostBase(IndexAccess const& _indexAccess) return base; } -set SMTChecker::touchedVariables(ASTNode const& _node) +set SMTEncoder::touchedVariables(ASTNode const& _node) { solAssert(!m_callStack.empty(), ""); vector callStack; @@ -2044,7 +1331,7 @@ set SMTChecker::touchedVariables(ASTNode const& _nod return m_variableUsage.touchedVariables(_node, callStack); } -VariableDeclaration const* SMTChecker::identifierToVariable(Expression const& _expr) +VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr) { if (auto identifier = dynamic_cast(&_expr)) { @@ -2056,3 +1343,15 @@ VariableDeclaration const* SMTChecker::identifierToVariable(Expression const& _e } return nullptr; } + +string SMTEncoder::extraComment() +{ + string extra; + if (m_arrayAssignmentHappened) + extra += + "\nNote that array aliasing is not supported," + " therefore all mapping information is erased after" + " a mapping local variable/parameter is assigned.\n" + "You can re-introduce information using require()."; + return extra; +} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTEncoder.h similarity index 68% rename from libsolidity/formal/SMTChecker.h rename to libsolidity/formal/SMTEncoder.h index 552406dcc..064a3a436 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTEncoder.h @@ -14,6 +14,11 @@ You should have received a copy of the GNU General Public License along with solidity. If not, see . */ +/** + * Encodes Solidity into SMT expressions without creating + * any verification targets. + * Also implements the SSA scheme for branches. + */ #pragma once @@ -43,25 +48,15 @@ namespace dev namespace solidity { -class SMTChecker: private ASTConstVisitor +class SMTEncoder: public ASTConstVisitor { public: - SMTChecker(langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + SMTEncoder(smt::EncodingContext& _context, std::map const& _smtlib2Responses); - void analyze(SourceUnit const& _sources, std::shared_ptr const& _scanner); - - /// This is used if the SMT solver is not directly linked into this binary. - /// @returns a list of inputs to the SMT solver that were not part of the argument to - /// the constructor. - std::vector unhandledQueries() { return m_interface->unhandledQueries(); } - - /// @returns the FunctionDefinition of a called function if possible and should inline, - /// otherwise nullptr. - static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall); /// @returns the leftmost identifier in a multi-d IndexAccess. static Expression const* leftmostBase(IndexAccess const& _indexAccess); -private: +protected: // TODO: Check that we do not have concurrent reads and writes to a variable, // because the order of expression evaluation is undefined // TODO: or just force a certain order, but people might have a different idea about that. @@ -74,8 +69,8 @@ private: void endVisit(FunctionDefinition const& _node) override; bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; - bool visit(WhileStatement const& _node) override; - bool visit(ForStatement const& _node) override; + bool visit(WhileStatement const&) override { return false; } + bool visit(ForStatement const&) override { return false; } void endVisit(VariableDeclarationStatement const& _node) override; void endVisit(Assignment const& _node) override; void endVisit(TupleExpression const& _node) override; @@ -95,10 +90,10 @@ private: /// Symbolic _expr is the rational literal. bool shortcutRationalNumber(Expression const& _expr); void arithmeticOperation(BinaryOperation const& _op); - /// @returns _op(_left, _right). + /// @returns _op(_left, _right) with and without modular arithmetic. /// Used by the function above, compound assignments and /// unary increment/decrement. - smt::Expression arithmeticOperation( + virtual std::pair arithmeticOperation( Token _op, smt::Expression const& _left, smt::Expression const& _right, @@ -108,18 +103,11 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); + void initFunction(FunctionDefinition const& _function); void visitAssert(FunctionCall const& _funCall); void visitRequire(FunctionCall const& _funCall); void visitGasLeft(FunctionCall const& _funCall); void visitTypeConversion(FunctionCall const& _funCall); - /// Visits the FunctionDefinition of the called function - /// if available and inlines the return value. - void inlineFunctionCall(FunctionCall const& _funCall); - /// Creates an uninterpreted function call. - void abstractFunctionCall(FunctionCall const& _funCall); - /// Inlines if the function call is internal or external to `this`. - /// Erases knowledge about state variables if external. - void internalOrExternalFunctionCall(FunctionCall const& _funCall); void visitFunctionIdentifier(Identifier const& _identifier); /// Encodes a modifier or function body according to the modifier @@ -165,71 +153,9 @@ private: using CallStackEntry = std::pair; - /// Verification targets. - //@{ - struct VerificationTarget - { - enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type; - smt::Expression value; - smt::Expression constraints; - Expression const* expression; - std::vector callStack; - std::pair, std::vector> modelExpressions; - }; - - void checkVerificationTargets(smt::Expression const& _constraints); - void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true)); - void checkConstantCondition(VerificationTarget& _target); - void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints); - void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints); - void checkDivByZero(VerificationTarget& _target); - void checkBalance(VerificationTarget& _target); - void checkAssert(VerificationTarget& _target); - void addVerificationTarget( - VerificationTarget::Type _type, - smt::Expression const& _value, - Expression const* _expression - ); - //@} - - /// Solver related. - //@{ - - std::pair, std::vector> modelExpressions(); - /// Check that a condition can be satisfied. - void checkCondition( - smt::Expression const& _condition, - std::vector const& callStack, - std::pair, std::vector> const& _modelExpressions, - langutil::SourceLocation const& _location, - std::string const& _description, - std::string const& _additionalValueName = "", - smt::Expression const* _additionalValue = nullptr - ); - /// Checks whether a Boolean condition is constant. - /// Do not warn if the expression is a literal constant. - /// @param _condition the Solidity expression, used to check whether it is a Literal and for location. - /// @param _constraints the program constraints, including control-flow. - /// @param _value the Boolean term to be checked. - /// @param _callStack the callStack to be shown with the model if applicable. - /// @param _description the warning string, $VALUE will be replaced by the constant value. - void checkBooleanNotConstant( - Expression const& _condition, - smt::Expression const& _constraints, - smt::Expression const& _value, - std::vector const& _callStack, - std::string const& _description - ); - std::pair> - checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate); - - smt::CheckResult checkSatisfiable(); - //@} - void initializeLocalVariables(FunctionDefinition const& _function); void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector const& _callArgs); void resetStateVariables(); - void resetStorageReferences(); /// @returns the type without storage pointer information if it has it. TypePointer typeWithoutPointer(TypePointer const& _type); @@ -265,8 +191,6 @@ private: CallStackEntry popCallStack(); /// Adds (_definition, _node) to the callstack. void pushCallStack(CallStackEntry _entry); - /// Conjoin the current path conditions with the given parameter and add to the solver - void addPathConjoinedExpression(smt::Expression const& _e); /// Add to the solver: the given expression implied by the current path conditions void addPathImpliedExpression(smt::Expression const& _e); @@ -281,11 +205,12 @@ private: /// @returns the VariableDeclaration referenced by an Identifier or nullptr. VariableDeclaration const* identifierToVariable(Expression const& _expr); + /// @returns a note to be added to warnings. + std::string extraComment(); + std::shared_ptr m_interface; smt::VariableUsage m_variableUsage; - bool m_loopExecutionHappened = false; bool m_arrayAssignmentHappened = false; - bool m_externalFunctionCallHappened = false; // True if the "No SMT solver available" warning was already created. bool m_noSolverWarning = false; @@ -294,9 +219,7 @@ private: /// Used to retrieve models. std::set m_uninterpretedTerms; std::vector m_pathConditions; - /// ErrorReporter that comes from CompilerStack. - langutil::ErrorReporter& m_errorReporterReference; - /// Local SMTChecker ErrorReporter. + /// Local SMTEncoder ErrorReporter. /// This is necessary to show the "No SMT solver available" /// warning before the others in case it's needed. langutil::ErrorReporter m_errorReporter; @@ -311,8 +234,6 @@ private: /// Returns true if _funDef was already visited. bool visitedFunction(FunctionDefinition const* _funDef); - std::vector m_verificationTargets; - /// Depth of visit to modifiers. /// When m_modifierDepth == #modifiers the function can be visited /// when placeholder is visited. @@ -320,7 +241,7 @@ private: std::vector m_modifierDepthStack; /// Stores the context of the encoding. - smt::EncodingContext m_context; + smt::EncodingContext& m_context; }; } diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp index bd695c3cc..3f71d2ccf 100644 --- a/libsolidity/formal/VariableUsage.cpp +++ b/libsolidity/formal/VariableUsage.cpp @@ -17,7 +17,8 @@ #include -#include +#include +#include #include @@ -48,7 +49,7 @@ void VariableUsage::endVisit(IndexAccess const& _indexAccess) { /// identifier.annotation().lValueRequested == false, that's why we /// need to check that before. - auto identifier = dynamic_cast(SMTChecker::leftmostBase(_indexAccess)); + auto identifier = dynamic_cast(SMTEncoder::leftmostBase(_indexAccess)); if (identifier) checkIdentifier(*identifier); } @@ -56,7 +57,8 @@ void VariableUsage::endVisit(IndexAccess const& _indexAccess) void VariableUsage::endVisit(FunctionCall const& _funCall) { - if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall)) + /// TODO this should run only in the BMC case, not for Horn. + if (auto const& funDef = BMC::inlinedFunctionCallToDefinition(_funCall)) if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end()) funDef->accept(*this); } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index f2c7ea590..96e03fe3e 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include #include #include @@ -371,10 +371,10 @@ bool CompilerStack::analyze() if (noErrors) { - SMTChecker smtChecker(m_errorReporter, m_smtlib2Responses); + ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses); for (Source const* source: m_sourceOrder) - smtChecker.analyze(*source->ast, source->scanner); - m_unhandledSMTLib2Queries += smtChecker.unhandledQueries(); + modelChecker.analyze(*source->ast, source->scanner); + m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); } } catch(FatalError const&) diff --git a/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol b/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol index 283461876..e1cd1f0de 100644 --- a/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol +++ b/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol @@ -58,3 +58,4 @@ contract MyConc{ // Warning: (834-839): Assertion checker does not yet support the type of this literal (literal_string "abc"). // Warning: (874-879): Underflow (resulting value less than 0) happens here. // Warning: (874-879): Overflow (resulting value larger than 2**256 - 1) happens here. +// Warning: (985-1002): Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol index d4b486767..5ccaec149 100644 --- a/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol @@ -8,8 +8,6 @@ contract C require(x > 0); _; // Fails because of overflow behavior. - // Overflow is not reported because this assertion prevents - // it from reaching the end of the function. assert(x > 1); } @@ -19,4 +17,4 @@ contract C } } // ---- -// Warning: (245-258): Assertion violation happens here +// Warning: (136-149): Assertion violation happens here