solidity/libsolidity/formal/SMTEncoder.cpp
2023-08-15 14:40:27 +02:00

3244 lines
102 KiB
C++

/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/formal/SMTEncoder.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libyul/AST.h>
#include <libyul/optimiser/Semantics.h>
#include <libsmtutil/SMTPortfolio.h>
#include <libsmtutil/Helpers.h>
#include <liblangutil/CharStreamProvider.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/FunctionSelector.h>
#include <range/v3/view.hpp>
#include <limits>
#include <deque>
using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;
using namespace std::string_literals;
SMTEncoder::SMTEncoder(
smt::EncodingContext& _context,
ModelCheckerSettings _settings,
UniqueErrorReporter& _errorReporter,
UniqueErrorReporter& _unsupportedErrorReporter,
langutil::CharStreamProvider const& _charStreamProvider
):
m_errorReporter(_errorReporter),
m_unsupportedErrors(_unsupportedErrorReporter),
m_context(_context),
m_settings(std::move(_settings)),
m_charStreamProvider(_charStreamProvider)
{
}
void SMTEncoder::resetSourceAnalysis()
{
m_freeFunctions.clear();
}
bool SMTEncoder::visit(ContractDefinition const& _contract)
{
solAssert(m_currentContract, "");
for (auto const& node: _contract.subNodes())
if (
!std::dynamic_pointer_cast<FunctionDefinition>(node) &&
!std::dynamic_pointer_cast<VariableDeclaration>(node)
)
node->accept(*this);
for (auto const& base: _contract.annotation().linearizedBaseContracts)
{
// Look for all the constructor invocations bottom up.
if (auto const& constructor = base->constructor())
for (auto const& invocation: constructor->modifiers())
{
auto refDecl = invocation->name().annotation().referencedDeclaration;
if (auto const& baseContract = dynamic_cast<ContractDefinition const*>(refDecl))
{
solAssert(!m_baseConstructorCalls.count(baseContract), "");
m_baseConstructorCalls[baseContract] = invocation.get();
}
}
}
// Functions are visited first since they might be used
// for state variable initialization which is part of
// the constructor.
// Constructors are visited as part of the constructor
// hierarchy inlining.
for (auto const* function: contractFunctionsWithoutVirtual(_contract) + allFreeFunctions())
if (!function->isConstructor())
function->accept(*this);
// Constructors need to be handled by the engines separately.
return false;
}
void SMTEncoder::endVisit(ContractDefinition const& _contract)
{
m_context.resetAllVariables();
m_baseConstructorCalls.clear();
solAssert(m_currentContract == &_contract, "");
m_currentContract = nullptr;
if (m_callStack.empty())
m_context.popSolver();
}
bool SMTEncoder::visit(ImportDirective const&)
{
// do not visit because the identifier therein will confuse us.
return false;
}
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
{
// State variables are handled by the constructor.
if (_varDecl.isLocalVariable() &&_varDecl.value())
assignment(_varDecl, *_varDecl.value());
}
bool SMTEncoder::visit(ModifierDefinition const&)
{
return false;
}
bool SMTEncoder::visit(FunctionDefinition const& _function)
{
solAssert(m_currentContract, "");
m_modifierDepthStack.push_back(-1);
initializeLocalVariables(_function);
_function.parameterList().accept(*this);
if (_function.returnParameterList())
_function.returnParameterList()->accept(*this);
visitFunctionOrModifier();
return false;
}
void SMTEncoder::visitFunctionOrModifier()
{
solAssert(!m_callStack.empty(), "");
solAssert(!m_modifierDepthStack.empty(), "");
++m_modifierDepthStack.back();
FunctionDefinition const& function = dynamic_cast<FunctionDefinition const&>(*m_callStack.back().first);
if (m_modifierDepthStack.back() == static_cast<int>(function.modifiers().size()))
{
if (function.isImplemented())
{
pushInlineFrame(function);
function.body().accept(*this);
popInlineFrame(function);
}
}
else
{
solAssert(m_modifierDepthStack.back() < static_cast<int>(function.modifiers().size()), "");
ASTPointer<ModifierInvocation> const& modifierInvocation =
function.modifiers()[static_cast<size_t>(m_modifierDepthStack.back())];
solAssert(modifierInvocation, "");
auto refDecl = modifierInvocation->name().annotation().referencedDeclaration;
if (dynamic_cast<ContractDefinition const*>(refDecl))
visitFunctionOrModifier();
else if (auto modifier = resolveModifierInvocation(*modifierInvocation, m_currentContract))
{
m_scopes.push_back(modifier);
inlineModifierInvocation(modifierInvocation.get(), modifier);
solAssert(m_scopes.back() == modifier, "");
m_scopes.pop_back();
}
else
solAssert(false, "");
}
--m_modifierDepthStack.back();
}
void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation, CallableDeclaration const* _definition)
{
solAssert(_invocation, "");
_invocation->accept(*this);
std::vector<smtutil::Expression> args;
if (auto const* arguments = _invocation->arguments())
{
auto const& modifierParams = _definition->parameters();
solAssert(modifierParams.size() == arguments->size(), "");
for (unsigned i = 0; i < arguments->size(); ++i)
args.push_back(expr(*arguments->at(i), modifierParams.at(i)->type()));
}
initializeFunctionCallParameters(*_definition, args);
pushCallStack({_definition, _invocation});
pushInlineFrame(*_definition);
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
{
if (modifier->isImplemented())
modifier->body().accept(*this);
popCallStack();
}
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
{
if (function->isImplemented())
function->accept(*this);
// Functions are popped from the callstack in endVisit(FunctionDefinition)
}
popInlineFrame(*_definition);
}
void SMTEncoder::inlineConstructorHierarchy(ContractDefinition const& _contract)
{
auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
auto it = find(begin(hierarchy), end(hierarchy), &_contract);
solAssert(it != end(hierarchy), "");
auto nextBase = it + 1;
// Initialize the base contracts here as long as their constructors are implicit,
// stop when the first explicit constructor is found.
while (nextBase != end(hierarchy))
{
if (auto baseConstructor = (*nextBase)->constructor())
{
createLocalVariables(*baseConstructor);
// If any subcontract explicitly called baseConstructor, use those arguments.
if (m_baseConstructorCalls.count(*nextBase))
inlineModifierInvocation(m_baseConstructorCalls.at(*nextBase), baseConstructor);
else if (baseConstructor->isImplemented())
{
// The first constructor found is handled like a function
// and its pushed into the callstack there.
// This if avoids duplication in the callstack.
if (!m_callStack.empty())
pushCallStack({baseConstructor, nullptr});
baseConstructor->accept(*this);
// popped by endVisit(FunctionDefinition)
}
break;
}
else
{
initializeStateVariables(**nextBase);
++nextBase;
}
}
initializeStateVariables(_contract);
}
bool SMTEncoder::visit(PlaceholderStatement const&)
{
solAssert(!m_callStack.empty(), "");
auto lastCall = popCallStack();
visitFunctionOrModifier();
pushCallStack(lastCall);
return true;
}
void SMTEncoder::endVisit(FunctionDefinition const&)
{
solAssert(m_currentContract, "");
popCallStack();
solAssert(m_modifierDepthStack.back() == -1, "");
m_modifierDepthStack.pop_back();
if (m_callStack.empty())
m_context.popSolver();
}
bool SMTEncoder::visit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_checked, "");
m_checked = false;
}
return true;
}
void SMTEncoder::endVisit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(!m_checked, "");
m_checked = true;
}
}
bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{
/// This is very similar to `yul::Assignments`, except I need to collect `Identifier`s and not just names as `YulString`s.
struct AssignedExternalsCollector: public yul::ASTWalker
{
AssignedExternalsCollector(InlineAssembly const& _inlineAsm): externalReferences(_inlineAsm.annotation().externalReferences)
{
this->operator()(_inlineAsm.operations());
}
std::map<yul::Identifier const*, InlineAssemblyAnnotation::ExternalIdentifierInfo> const& externalReferences;
std::set<VariableDeclaration const*> assignedVars;
using yul::ASTWalker::operator();
void operator()(yul::Assignment const& _assignment)
{
auto const& vars = _assignment.variableNames;
for (auto const& identifier: vars)
if (auto externalInfo = util::valueOrNullptr(externalReferences, &identifier))
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(externalInfo->declaration))
assignedVars.insert(varDecl);
}
};
yul::SideEffectsCollector sideEffectsCollector(_inlineAsm.dialect(), _inlineAsm.operations());
if (sideEffectsCollector.invalidatesMemory())
resetMemoryVariables();
if (sideEffectsCollector.invalidatesStorage())
resetStorageVariables();
auto assignedVars = AssignedExternalsCollector(_inlineAsm).assignedVars;
for (auto const* var: assignedVars)
{
solAssert(var, "");
solAssert(var->isLocalVariable(), "Non-local variable assigned in inlined assembly");
m_context.resetVariable(*var);
}
m_unsupportedErrors.warning(
7737_error,
_inlineAsm.location(),
"Inline assembly may cause SMTChecker to produce spurious warnings (false positives)."
);
return false;
}
void SMTEncoder::pushInlineFrame(CallableDeclaration const&)
{
pushPathCondition(currentPathConditions());
}
void SMTEncoder::popInlineFrame(CallableDeclaration const&)
{
popPathCondition();
}
void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
{
if (auto init = _varDecl.initialValue())
expressionToTupleAssignment(_varDecl.declarations(), *init);
}
bool SMTEncoder::visit(Assignment const& _assignment)
{
// RHS must be visited before LHS; as opposed to what Assignment::accept does
_assignment.rightHandSide().accept(*this);
_assignment.leftHandSide().accept(*this);
return false;
}
void SMTEncoder::endVisit(Assignment const& _assignment)
{
createExpr(_assignment);
Token op = _assignment.assignmentOperator();
solAssert(TokenTraits::isAssignmentOp(op), "");
if (!isSupportedType(*_assignment.annotation().type))
{
// Give it a new index anyway to keep the SSA scheme sound.
Expression const* base = &_assignment.leftHandSide();
if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(base))
base = leftmostBase(*indexAccess);
if (auto varDecl = identifierToVariable(*base))
m_context.newValue(*varDecl);
}
else
{
if (dynamic_cast<TupleType const*>(_assignment.rightHandSide().annotation().type))
tupleAssignment(_assignment.leftHandSide(), _assignment.rightHandSide());
else
{
auto const& type = _assignment.annotation().type;
auto rightHandSide = op == Token::Assign ?
expr(_assignment.rightHandSide(), type) :
compoundAssignment(_assignment);
defineExpr(_assignment, rightHandSide);
assignment(
_assignment.leftHandSide(),
expr(_assignment, type),
type
);
}
}
}
void SMTEncoder::endVisit(TupleExpression const& _tuple)
{
if (_tuple.annotation().type->category() == Type::Category::Function)
return;
if (_tuple.annotation().type->category() == Type::Category::TypeType)
return;
createExpr(_tuple);
if (_tuple.isInlineArray())
{
// Add constraints for the length and values as it is known.
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_tuple));
solAssert(symbArray, "");
addArrayLiteralAssertions(*symbArray, applyMap(_tuple.components(), [&](auto const& c) { return expr(*c); }));
}
else
{
auto values = applyMap(_tuple.components(), [this](auto const& component) -> std::optional<smtutil::Expression> {
if (component)
{
if (!m_context.knownExpression(*component))
createExpr(*component);
return expr(*component);
}
return {};
});
defineExpr(_tuple, values);
}
}
void SMTEncoder::endVisit(UnaryOperation const& _op)
{
/// We need to shortcut here due to potentially unknown
/// rational number sizes.
if (_op.annotation().type->category() == Type::Category::RationalNumber)
return;
if (TokenTraits::isBitOp(_op.getOperator()) && !*_op.annotation().userDefinedFunction)
return bitwiseNotOperation(_op);
createExpr(_op);
// User-defined operators are essentially function calls.
if (*_op.annotation().userDefinedFunction)
{
// TODO: Implement user-defined operators properly.
m_unsupportedErrors.warning(
6156_error,
_op.location(),
"User-defined operators are not yet supported by SMTChecker. "s +
"This invocation of operator " + TokenTraits::friendlyName(_op.getOperator()) +
" has been ignored, which may lead to incorrect results."
);
return;
}
auto const* subExpr = innermostTuple(_op.subExpression());
auto type = _op.annotation().type;
switch (_op.getOperator())
{
case Token::Not: // !
{
solAssert(smt::isBool(*type), "");
defineExpr(_op, !expr(*subExpr));
break;
}
case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix)
{
solAssert(smt::isInteger(*type) || smt::isFixedPoint(*type), "");
solAssert(subExpr->annotation().willBeWrittenTo, "");
auto innerValue = expr(*subExpr);
auto newValue = arithmeticOperation(
_op.getOperator() == Token::Inc ? Token::Add : Token::Sub,
innerValue,
smtutil::Expression(size_t(1)),
_op.annotation().type,
_op
).first;
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
assignment(*subExpr, newValue);
break;
}
case Token::Sub: // -
{
defineExpr(_op, 0 - expr(*subExpr));
break;
}
case Token::Delete:
{
if (auto decl = identifierToVariable(*subExpr))
{
m_context.newValue(*decl);
m_context.setZeroValue(*decl);
}
else
{
solAssert(m_context.knownExpression(*subExpr), "");
auto const& symbVar = m_context.expression(*subExpr);
symbVar->increaseIndex();
m_context.setZeroValue(*symbVar);
if (
dynamic_cast<IndexAccess const*>(subExpr) ||
dynamic_cast<MemberAccess const*>(subExpr)
)
indexOrMemberAssignment(*subExpr, symbVar->currentValue());
// Empty push added a zero value anyway, so no need to delete extra.
else if (!isEmptyPush(*subExpr))
solAssert(false, "");
}
break;
}
default:
solAssert(false, "");
}
}
bool SMTEncoder::visit(UnaryOperation const& _op)
{
return !shortcutRationalNumber(_op);
}
bool SMTEncoder::visit(BinaryOperation const& _op)
{
if (shortcutRationalNumber(_op))
return false;
if (TokenTraits::isBooleanOp(_op.getOperator()) && !*_op.annotation().userDefinedFunction)
{
booleanOperation(_op);
return false;
}
return true;
}
void SMTEncoder::endVisit(BinaryOperation const& _op)
{
/// If _op is const evaluated the RationalNumber shortcut was taken.
if (isConstant(_op))
return;
if (TokenTraits::isBooleanOp(_op.getOperator()) && !*_op.annotation().userDefinedFunction)
return;
createExpr(_op);
// User-defined operators are essentially function calls.
if (*_op.annotation().userDefinedFunction)
{
// TODO: Implement user-defined operators properly.
m_unsupportedErrors.warning(
6756_error,
_op.location(),
"User-defined operators are not yet supported by SMTChecker. "s +
"This invocation of operator " + TokenTraits::friendlyName(_op.getOperator()) +
" has been ignored, which may lead to incorrect results."
);
return;
}
if (TokenTraits::isArithmeticOp(_op.getOperator()))
arithmeticOperation(_op);
else if (TokenTraits::isCompareOp(_op.getOperator()))
compareOperation(_op);
else if (TokenTraits::isBitOp(_op.getOperator()) || TokenTraits::isShiftOp(_op.getOperator()))
bitwiseOperation(_op);
else
solAssert(false, "");
}
bool SMTEncoder::visit(Conditional const& _op)
{
_op.condition().accept(*this);
auto indicesEndTrue = visitBranch(&_op.trueExpression(), expr(_op.condition())).first;
auto indicesEndFalse = visitBranch(&_op.falseExpression(), !expr(_op.condition())).first;
mergeVariables(expr(_op.condition()), indicesEndTrue, indicesEndFalse);
defineExpr(_op, smtutil::Expression::ite(
expr(_op.condition()),
expr(_op.trueExpression(), _op.annotation().type),
expr(_op.falseExpression(), _op.annotation().type)
));
return false;
}
bool SMTEncoder::visit(FunctionCall const& _funCall)
{
auto functionCallKind = *_funCall.annotation().kind;
if (functionCallKind != FunctionCallKind::FunctionCall)
return true;
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
// We do not want to visit the TypeTypes in the second argument of `abi.decode`.
// Those types are checked/used in SymbolicState::buildABIFunctions.
if (funType.kind() == FunctionType::Kind::ABIDecode)
{
if (auto arg = _funCall.arguments().front())
arg->accept(*this);
return false;
}
// We do not really need to visit the expression in a wrap/unwrap no-op call,
// so we just ignore the function call expression to avoid "unsupported" warnings.
else if (
funType.kind() == FunctionType::Kind::Wrap ||
funType.kind() == FunctionType::Kind::Unwrap
)
{
if (auto arg = _funCall.arguments().front())
arg->accept(*this);
return false;
}
return true;
}
void SMTEncoder::endVisit(FunctionCall const& _funCall)
{
auto functionCallKind = *_funCall.annotation().kind;
createExpr(_funCall);
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
visitStructConstructorCall(_funCall);
return;
}
if (functionCallKind == FunctionCallKind::TypeConversion)
{
visitTypeConversion(_funCall);
return;
}
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
std::vector<ASTPointer<Expression const>> const args = _funCall.arguments();
switch (funType.kind())
{
case FunctionType::Kind::Assert:
visitAssert(_funCall);
break;
case FunctionType::Kind::Require:
visitRequire(_funCall);
break;
case FunctionType::Kind::Revert:
// Revert is a special case of require and equals to `require(false)`
addPathImpliedExpression(smtutil::Expression(false));
break;
case FunctionType::Kind::GasLeft:
visitGasLeft(_funCall);
break;
case FunctionType::Kind::External:
if (publicGetter(_funCall.expression()))
visitPublicGetter(_funCall);
break;
case FunctionType::Kind::ABIDecode:
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature:
visitABIFunction(_funCall);
break;
case FunctionType::Kind::Internal:
case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::BareCall:
break;
case FunctionType::Kind::KECCAK256:
case FunctionType::Kind::ECRecover:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
visitCryptoFunction(_funCall);
break;
case FunctionType::Kind::BlockHash:
defineExpr(_funCall, state().blockhash(expr(*_funCall.arguments().at(0))));
break;
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
visitAddMulMod(_funCall);
break;
case FunctionType::Kind::Unwrap:
case FunctionType::Kind::Wrap:
visitWrapUnwrap(_funCall);
break;
case FunctionType::Kind::Send:
case FunctionType::Kind::Transfer:
{
auto const& memberAccess = dynamic_cast<MemberAccess const&>(_funCall.expression());
auto const& address = memberAccess.expression();
auto const& value = args.front();
solAssert(value, "");
smtutil::Expression thisBalance = state().balance();
setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context);
state().transfer(state().thisAddress(), expr(address), expr(*value));
break;
}
case FunctionType::Kind::ArrayPush:
arrayPush(_funCall);
break;
case FunctionType::Kind::ArrayPop:
arrayPop(_funCall);
break;
case FunctionType::Kind::Event:
case FunctionType::Kind::Error:
// This can be safely ignored.
break;
case FunctionType::Kind::ObjectCreation:
visitObjectCreation(_funCall);
return;
case FunctionType::Kind::Creation:
if (!m_settings.engine.chc || !m_settings.externalCalls.isTrusted())
m_unsupportedErrors.warning(
8729_error,
_funCall.location(),
"Contract deployment is only supported in the trusted mode for external calls"
" with the CHC engine."
);
break;
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
default:
m_unsupportedErrors.warning(
4588_error,
_funCall.location(),
"Assertion checker does not yet implement this type of function call."
);
}
}
bool SMTEncoder::visit(ModifierInvocation const& _node)
{
if (auto const* args = _node.arguments())
for (auto const& arg: *args)
if (arg)
arg->accept(*this);
return false;
}
void SMTEncoder::initContract(ContractDefinition const& _contract)
{
solAssert(m_currentContract == nullptr, "");
m_currentContract = &_contract;
m_context.reset();
m_context.pushSolver();
createStateVariables(_contract);
clearIndices(m_currentContract, nullptr);
m_variableUsage.setCurrentContract(_contract);
m_checked = true;
}
void SMTEncoder::initFunction(FunctionDefinition const& _function)
{
solAssert(m_callStack.empty(), "");
solAssert(m_currentContract, "");
m_context.pushSolver();
m_pathConditions.clear();
pushCallStack({&_function, nullptr});
m_uninterpretedTerms.clear();
createStateVariables(*m_currentContract);
createLocalVariables(_function);
m_arrayAssignmentHappened = false;
clearIndices(m_currentContract, &_function);
m_checked = true;
}
void SMTEncoder::visitAssert(FunctionCall const& _funCall)
{
auto const& args = _funCall.arguments();
solAssert(args.size() == 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
}
void SMTEncoder::visitRequire(FunctionCall const& _funCall)
{
auto const& args = _funCall.arguments();
solAssert(args.size() >= 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
addPathImpliedExpression(expr(*args.front()));
}
void SMTEncoder::visitABIFunction(FunctionCall const& _funCall)
{
auto symbFunction = state().abiFunction(&_funCall);
auto const& [name, inTypes, outTypes] = state().abiFunctionTypes(&_funCall);
auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
auto const& args = _funCall.sortedArguments();
auto argsActualLength = kind == FunctionType::Kind::ABIDecode ? 1 : args.size();
solAssert(inTypes.size() == argsActualLength, "");
if (argsActualLength == 0)
{
defineExpr(_funCall, smt::zeroValue(TypeProvider::bytesMemory()));
return;
}
std::vector<smtutil::Expression> symbArgs;
for (unsigned i = 0; i < argsActualLength; ++i)
if (args.at(i))
symbArgs.emplace_back(expr(*args.at(i), inTypes.at(i)));
std::optional<smtutil::Expression> arg;
if (inTypes.size() == 1)
arg = expr(*args.at(0), inTypes.at(0));
else
{
auto inputSort = dynamic_cast<smtutil::ArraySort&>(*symbFunction.sort).domain;
arg = smtutil::Expression::tuple_constructor(
smtutil::Expression(std::make_shared<smtutil::SortSort>(inputSort), ""),
symbArgs
);
}
auto out = smtutil::Expression::select(symbFunction, *arg);
if (outTypes.size() == 1)
defineExpr(_funCall, out);
else
{
auto symbTuple = std::dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_funCall));
solAssert(symbTuple, "");
solAssert(symbTuple->components().size() == outTypes.size(), "");
solAssert(out.sort->kind == smtutil::Kind::Tuple, "");
symbTuple->increaseIndex();
for (unsigned i = 0; i < symbTuple->components().size(); ++i)
m_context.addAssertion(symbTuple->component(i) == smtutil::Expression::tuple_get(out, i));
}
}
void SMTEncoder::visitCryptoFunction(FunctionCall const& _funCall)
{
auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
auto arg0 = expr(*_funCall.arguments().at(0));
std::optional<smtutil::Expression> result;
if (kind == FunctionType::Kind::KECCAK256)
result = smtutil::Expression::select(state().cryptoFunction("keccak256"), arg0);
else if (kind == FunctionType::Kind::SHA256)
result = smtutil::Expression::select(state().cryptoFunction("sha256"), arg0);
else if (kind == FunctionType::Kind::RIPEMD160)
result = smtutil::Expression::select(state().cryptoFunction("ripemd160"), arg0);
else if (kind == FunctionType::Kind::ECRecover)
{
auto e = state().cryptoFunction("ecrecover");
auto arg0 = expr(*_funCall.arguments().at(0));
auto arg1 = expr(*_funCall.arguments().at(1));
auto arg2 = expr(*_funCall.arguments().at(2));
auto arg3 = expr(*_funCall.arguments().at(3));
auto inputSort = dynamic_cast<smtutil::ArraySort&>(*e.sort).domain;
auto ecrecoverInput = smtutil::Expression::tuple_constructor(
smtutil::Expression(std::make_shared<smtutil::SortSort>(inputSort), ""),
{arg0, arg1, arg2, arg3}
);
result = smtutil::Expression::select(e, ecrecoverInput);
}
else
solAssert(false, "");
defineExpr(_funCall, *result);
}
void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
{
std::string gasLeft = "gasleft";
// We increase the variable index since gasleft changes
// inside a tx.
defineGlobalVariable(gasLeft, _funCall, true);
auto const& symbolicVar = m_context.globalSymbol(gasLeft);
unsigned index = symbolicVar->index();
// We set the current value to unknown anyway to add type constraints.
m_context.setUnknownValue(*symbolicVar);
if (index > 0)
m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1));
}
void SMTEncoder::visitAddMulMod(FunctionCall const& _funCall)
{
auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
solAssert(kind == FunctionType::Kind::AddMod || kind == FunctionType::Kind::MulMod, "");
auto const& args = _funCall.arguments();
solAssert(args.at(0) && args.at(1) && args.at(2), "");
auto x = expr(*args.at(0));
auto y = expr(*args.at(1));
auto k = expr(*args.at(2));
auto const& intType = dynamic_cast<IntegerType const&>(*_funCall.annotation().type);
if (kind == FunctionType::Kind::AddMod)
defineExpr(_funCall, divModWithSlacks(x + y, k, intType).second);
else
defineExpr(_funCall, divModWithSlacks(x * y, k, intType).second);
}
void SMTEncoder::visitWrapUnwrap(FunctionCall const& _funCall)
{
auto const& args = _funCall.arguments();
solAssert(args.size() == 1, "");
defineExpr(_funCall, expr(*args.front()));
}
void SMTEncoder::visitObjectCreation(FunctionCall const& _funCall)
{
auto const& args = _funCall.arguments();
solAssert(args.size() >= 1, "");
auto argType = args.front()->annotation().type->category();
solAssert(argType == Type::Category::Integer || argType == Type::Category::RationalNumber, "");
smtutil::Expression arraySize = expr(*args.front());
setSymbolicUnknownValue(arraySize, TypeProvider::uint256(), m_context);
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_funCall));
solAssert(symbArray, "");
smt::setSymbolicZeroValue(*symbArray, m_context);
auto zeroElements = symbArray->elements();
symbArray->increaseIndex();
m_context.addAssertion(symbArray->length() == arraySize);
m_context.addAssertion(symbArray->elements() == zeroElements);
}
void SMTEncoder::endVisit(Identifier const& _identifier)
{
if (auto decl = identifierToVariable(_identifier))
{
if (decl->isConstant())
defineExpr(_identifier, constantExpr(_identifier, *decl));
else
defineExpr(_identifier, currentValue(*decl));
}
else if (_identifier.annotation().type->category() == Type::Category::Function)
visitFunctionIdentifier(_identifier);
else if (_identifier.name() == "now")
defineGlobalVariable(_identifier.name(), _identifier);
else if (_identifier.name() == "this")
{
defineExpr(_identifier, state().thisAddress());
m_uninterpretedTerms.insert(&_identifier);
}
// Ignore type identifiers
else if (dynamic_cast<TypeType const*>(_identifier.annotation().type))
return;
// Ignore module identifiers
else if (dynamic_cast<ModuleType const*>(_identifier.annotation().type))
return;
// Ignore user defined value type identifiers
else if (dynamic_cast<UserDefinedValueType const*>(_identifier.annotation().type))
return;
// Ignore the builtin abi, it is handled in FunctionCall.
// TODO: ignore MagicType in general (abi, block, msg, tx, type)
else if (auto magicType = dynamic_cast<MagicType const*>(_identifier.annotation().type); magicType && magicType->kind() == MagicType::Kind::ABI)
{
solAssert(_identifier.name() == "abi", "");
return;
}
else
createExpr(_identifier);
}
void SMTEncoder::endVisit(ElementaryTypeNameExpression const& _typeName)
{
auto const& typeType = dynamic_cast<TypeType const&>(*_typeName.annotation().type);
auto result = smt::newSymbolicVariable(
*TypeProvider::uint256(),
typeType.actualType()->toString(false),
m_context
);
solAssert(!result.first && result.second, "");
m_context.createExpression(_typeName, result.second);
}
namespace // helpers for SMTEncoder::visitPublicGetter
{
bool isReturnedFromStructGetter(Type const* _type)
{
// So far it seems that only Mappings and ordinary Arrays are not returned.
auto category = _type->category();
if (category == Type::Category::Mapping)
return false;
if (category == Type::Category::Array)
return dynamic_cast<ArrayType const&>(*_type).isByteArrayOrString();
// default
return true;
}
std::vector<std::string> structGetterReturnedMembers(StructType const& _structType)
{
std::vector<std::string> returnedMembers;
for (auto const& member: _structType.nativeMembers(nullptr))
if (isReturnedFromStructGetter(member.type))
returnedMembers.push_back(member.name);
return returnedMembers;
}
}
void SMTEncoder::visitPublicGetter(FunctionCall const& _funCall)
{
auto var = publicGetter(_funCall.expression());
solAssert(var && var->isStateVariable(), "");
solAssert(m_context.knownExpression(_funCall), "");
auto paramExpectedTypes = replaceUserTypes(FunctionType(*var).parameterTypes());
auto actualArguments = _funCall.arguments();
solAssert(actualArguments.size() == paramExpectedTypes.size(), "");
std::deque<smtutil::Expression> symbArguments;
for (unsigned i = 0; i < paramExpectedTypes.size(); ++i)
symbArguments.push_back(expr(*actualArguments[i], paramExpectedTypes[i]));
// See FunctionType::FunctionType(VariableDeclaration const& _varDecl)
// to understand the return types of public getters.
Type const* type = var->type();
smtutil::Expression currentExpr = currentValue(*var);
while (true)
{
if (
type->isValueType() ||
(type->category() == Type::Category::Array && dynamic_cast<ArrayType const&>(*type).isByteArrayOrString())
)
{
solAssert(symbArguments.empty(), "");
defineExpr(_funCall, currentExpr);
return;
}
switch (type->category())
{
case Type::Category::Array:
case Type::Category::Mapping:
{
solAssert(!symbArguments.empty(), "");
// For nested arrays/mappings, each argument in the call is an index to the next layer.
// We mirror this with `select` after unpacking the SMT-LIB array expression.
currentExpr = smtutil::Expression::select(smtutil::Expression::tuple_get(currentExpr, 0), symbArguments.front());
symbArguments.pop_front();
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
type = arrayType->baseType();
else if (auto mappingType = dynamic_cast<MappingType const*>(type))
type = mappingType->valueType();
else
solAssert(false, "");
break;
}
case Type::Category::Struct:
{
solAssert(symbArguments.empty(), "");
smt::SymbolicStructVariable structVar(dynamic_cast<StructType const*>(type), "struct_temp_" + std::to_string(_funCall.id()), m_context);
m_context.addAssertion(structVar.currentValue() == currentExpr);
auto returnedMembers = structGetterReturnedMembers(dynamic_cast<StructType const&>(*structVar.type()));
solAssert(!returnedMembers.empty(), "");
auto returnedValues = applyMap(returnedMembers, [&](std::string const& memberName) -> std::optional<smtutil::Expression> { return structVar.member(memberName); });
defineExpr(_funCall, returnedValues);
return;
}
default:
{
// Unsupported cases, do nothing and the getter will be abstracted.
return;
}
}
}
}
bool SMTEncoder::shouldAnalyze(SourceUnit const& _source) const
{
return m_settings.contracts.isDefault() ||
m_settings.contracts.has(*_source.annotation().path);
}
bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const
{
if (!_contract.canBeDeployed())
return false;
return m_settings.contracts.isDefault() ||
m_settings.contracts.has(_contract.sourceUnitName());
}
void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
{
solAssert(*_funCall.annotation().kind == FunctionCallKind::TypeConversion, "");
solAssert(_funCall.arguments().size() == 1, "");
auto argument = _funCall.arguments().front();
auto const argType = argument->annotation().type;
auto const funCallType = _funCall.annotation().type;
auto symbArg = expr(*argument, funCallType);
if (smt::isStringLiteral(*argType) && smt::isFixedBytes(*funCallType))
{
defineExpr(_funCall, symbArg);
return;
}
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(argType);
if (auto sliceType = dynamic_cast<ArraySliceType const*>(argType))
arrayType = &sliceType->arrayType();
if (arrayType && arrayType->isByteArrayOrString() && smt::isFixedBytes(*funCallType))
{
auto array = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*argument));
bytesToFixedBytesAssertions(*array, _funCall);
return;
}
// TODO Simplify this whole thing for 0.8.0 where weird casts are disallowed.
unsigned argSize = argType->storageBytes();
unsigned castSize = funCallType->storageBytes();
bool castIsSigned = smt::isNumber(*funCallType) && smt::isSigned(funCallType);
bool argIsSigned = smt::isNumber(*argType) && smt::isSigned(argType);
std::optional<smtutil::Expression> symbMin;
std::optional<smtutil::Expression> symbMax;
if (smt::isNumber(*funCallType))
{
symbMin = smt::minValue(funCallType);
symbMax = smt::maxValue(funCallType);
}
if (argSize == castSize)
{
// If sizes are the same, it's possible that the signs are different.
if (smt::isNumber(*funCallType) && smt::isNumber(*argType))
{
// castIsSigned && !argIsSigned => might overflow if arg > castType.max
// !castIsSigned && argIsSigned => might underflow if arg < castType.min
// !castIsSigned && !argIsSigned => ok
// castIsSigned && argIsSigned => ok
if (castIsSigned && !argIsSigned)
{
auto wrap = smtutil::Expression::ite(
symbArg > *symbMax,
symbArg - (*symbMax - *symbMin + 1),
symbArg
);
defineExpr(_funCall, wrap);
}
else if (!castIsSigned && argIsSigned)
{
auto wrap = smtutil::Expression::ite(
symbArg < *symbMin,
symbArg + (*symbMax + 1),
symbArg
);
defineExpr(_funCall, wrap);
}
else
defineExpr(_funCall, symbArg);
}
else
defineExpr(_funCall, symbArg);
}
else if (castSize > argSize)
{
solAssert(smt::isNumber(*funCallType), "");
// RationalNumbers have size 32.
solAssert(argType->category() != Type::Category::RationalNumber, "");
// castIsSigned && !argIsSigned => ok
// castIsSigned && argIsSigned => ok
// !castIsSigned && !argIsSigned => ok except for FixedBytesType, need to adjust padding
// !castIsSigned && argIsSigned => might underflow if arg < castType.min
if (!castIsSigned && argIsSigned)
{
auto wrap = smtutil::Expression::ite(
symbArg < *symbMin,
symbArg + (*symbMax + 1),
symbArg
);
defineExpr(_funCall, wrap);
}
else if (!castIsSigned && !argIsSigned)
{
if (auto const* fixedCast = dynamic_cast<FixedBytesType const*>(funCallType))
{
auto const* fixedArg = dynamic_cast<FixedBytesType const*>(argType);
solAssert(fixedArg, "");
auto diff = fixedCast->numBytes() - fixedArg->numBytes();
solAssert(diff > 0, "");
auto bvSize = fixedCast->numBytes() * 8;
defineExpr(
_funCall,
smtutil::Expression::bv2int(smtutil::Expression::int2bv(symbArg, bvSize) << smtutil::Expression::int2bv(diff * 8, bvSize))
);
}
else
defineExpr(_funCall, symbArg);
}
else
defineExpr(_funCall, symbArg);
}
else // castSize < argSize
{
solAssert(smt::isNumber(*funCallType), "");
RationalNumberType const* rationalType = isConstant(*argument);
if (rationalType)
{
// The TypeChecker guarantees that a constant fits in the cast size.
defineExpr(_funCall, symbArg);
return;
}
auto const* fixedCast = dynamic_cast<FixedBytesType const*>(funCallType);
auto const* fixedArg = dynamic_cast<FixedBytesType const*>(argType);
if (fixedCast && fixedArg)
{
createExpr(_funCall);
auto diff = argSize - castSize;
solAssert(fixedArg->numBytes() - fixedCast->numBytes() == diff, "");
auto argValueBV = smtutil::Expression::int2bv(symbArg, argSize * 8);
auto shr = smtutil::Expression::int2bv(diff * 8, argSize * 8);
solAssert(!castIsSigned, "");
defineExpr(_funCall, smtutil::Expression::bv2int(argValueBV >> shr));
}
else
{
auto argValueBV = smtutil::Expression::int2bv(symbArg, castSize * 8);
defineExpr(_funCall, smtutil::Expression::bv2int(argValueBV, castIsSigned));
}
}
}
void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier)
{
auto const& fType = dynamic_cast<FunctionType const&>(*_identifier.annotation().type);
if (replaceUserTypes(fType.returnParameterTypes()).size() == 1)
{
defineGlobalVariable(fType.identifier(), _identifier);
m_context.createExpression(_identifier, m_context.globalSymbol(fType.identifier()));
}
}
void SMTEncoder::visitStructConstructorCall(FunctionCall const& _funCall)
{
solAssert(*_funCall.annotation().kind == FunctionCallKind::StructConstructorCall, "");
if (smt::isNonRecursiveStruct(*_funCall.annotation().type))
{
auto& structSymbolicVar = dynamic_cast<smt::SymbolicStructVariable&>(*m_context.expression(_funCall));
auto structType = dynamic_cast<StructType const*>(structSymbolicVar.type());
solAssert(structType, "");
auto const& structMembers = structType->structDefinition().members();
solAssert(structMembers.size() == _funCall.sortedArguments().size(), "");
auto args = _funCall.sortedArguments();
structSymbolicVar.assignAllMembers(applyMap(
ranges::views::zip(args, structMembers),
[this] (auto const& argMemberPair) { return expr(*argMemberPair.first, argMemberPair.second->type()); }
));
}
}
void SMTEncoder::endVisit(Literal const& _literal)
{
solAssert(_literal.annotation().type, "Expected type for AST node");
Type const& type = *_literal.annotation().type;
if (smt::isNumber(type))
defineExpr(_literal, smtutil::Expression(type.literalValue(&_literal)));
else if (smt::isBool(type))
defineExpr(_literal, smtutil::Expression(_literal.token() == Token::TrueLiteral ? true : false));
else if (smt::isStringLiteral(type))
{
createExpr(_literal);
// Add constraints for the length and values as it is known.
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_literal));
solAssert(symbArray, "");
addArrayLiteralAssertions(
*symbArray,
applyMap(_literal.value(), [](unsigned char c) { return smtutil::Expression{size_t(c)}; })
);
}
else
solAssert(false, "");
}
void SMTEncoder::addArrayLiteralAssertions(
smt::SymbolicArrayVariable& _symArray,
std::vector<smtutil::Expression> const& _elementValues
)
{
m_context.addAssertion(_symArray.length() == _elementValues.size());
for (size_t i = 0; i < _elementValues.size(); i++)
m_context.addAssertion(smtutil::Expression::select(_symArray.elements(), i) == _elementValues[i]);
}
void SMTEncoder::bytesToFixedBytesAssertions(
smt::SymbolicArrayVariable& _symArray,
Expression const& _fixedBytes
)
{
auto const& fixed = dynamic_cast<FixedBytesType const&>(*_fixedBytes.annotation().type);
auto intType = TypeProvider::uint256();
std::string suffix = std::to_string(_fixedBytes.id()) + "_" + std::to_string(m_context.newUniqueId());
smt::SymbolicIntVariable k(intType, intType, "k_" + suffix, m_context);
m_context.addAssertion(k.currentValue() == 0);
size_t n = fixed.numBytes();
for (size_t i = 0; i < n; i++)
{
auto kPrev = k.currentValue();
m_context.addAssertion((smtutil::Expression::select(_symArray.elements(), i) * (u256(1) << ((n - i - 1) * 8))) + kPrev == k.increaseIndex());
}
m_context.addAssertion(expr(_fixedBytes) == k.currentValue());
}
void SMTEncoder::endVisit(Return const& _return)
{
if (_return.expression() && m_context.knownExpression(*_return.expression()))
{
auto returnParams = m_callStack.back().first->returnParameters();
if (returnParams.size() > 1)
{
auto const& symbTuple = std::dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
solAssert(symbTuple, "");
solAssert(symbTuple->components().size() == returnParams.size(), "");
auto const* tupleType = dynamic_cast<TupleType const*>(_return.expression()->annotation().type);
solAssert(tupleType, "");
auto const& types = tupleType->components();
solAssert(types.size() == returnParams.size(), "");
for (unsigned i = 0; i < returnParams.size(); ++i)
assignment(*returnParams.at(i), symbTuple->component(i, types.at(i), returnParams.at(i)->type()));
}
else if (returnParams.size() == 1)
assignment(*returnParams.front(), expr(*_return.expression(), returnParams.front()->type()));
}
}
bool SMTEncoder::visit(MemberAccess const& _memberAccess)
{
createExpr(_memberAccess);
auto const& accessType = _memberAccess.annotation().type;
if (accessType->category() == Type::Category::Function)
{
auto const* functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
if (functionType && functionType->hasDeclaration())
defineExpr(
_memberAccess,
util::selectorFromSignatureU32(functionType->richIdentifier())
);
return true;
}
Expression const* memberExpr = innermostTuple(_memberAccess.expression());
auto const& exprType = memberExpr->annotation().type;
solAssert(exprType, "");
if (exprType->category() == Type::Category::Magic)
{
if (auto const* identifier = dynamic_cast<Identifier const*>(memberExpr))
{
auto const& name = identifier->name();
solAssert(name == "block" || name == "msg" || name == "tx", "");
auto memberName = _memberAccess.memberName();
// TODO remove this for 0.9.0
if (name == "block" && memberName == "difficulty")
memberName = "prevrandao";
defineExpr(_memberAccess, state().txMember(name + "." + memberName));
}
else if (auto magicType = dynamic_cast<MagicType const*>(exprType))
{
if (magicType->kind() == MagicType::Kind::Block)
defineExpr(_memberAccess, state().txMember("block." + _memberAccess.memberName()));
else if (magicType->kind() == MagicType::Kind::Message)
defineExpr(_memberAccess, state().txMember("msg." + _memberAccess.memberName()));
else if (magicType->kind() == MagicType::Kind::Transaction)
defineExpr(_memberAccess, state().txMember("tx." + _memberAccess.memberName()));
else if (magicType->kind() == MagicType::Kind::MetaType)
{
auto const& memberName = _memberAccess.memberName();
if (memberName == "min" || memberName == "max")
{
if (IntegerType const* integerType = dynamic_cast<IntegerType const*>(magicType->typeArgument()))
defineExpr(_memberAccess, memberName == "min" ? integerType->minValue() : integerType->maxValue());
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(magicType->typeArgument()))
defineExpr(_memberAccess, memberName == "min" ? enumType->minValue() : enumType->maxValue());
}
else if (memberName == "interfaceId")
{
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition();
defineExpr(_memberAccess, contract.interfaceId());
}
else
// NOTE: supporting name, creationCode, runtimeCode would be easy enough, but the bytes/string they return are not
// at all usable in the SMT checker currently
m_unsupportedErrors.warning(
7507_error,
_memberAccess.location(),
"Assertion checker does not yet support this expression."
);
}
}
else
solAssert(false, "");
return false;
}
else if (smt::isNonRecursiveStruct(*exprType))
{
memberExpr->accept(*this);
auto const& symbStruct = std::dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(*memberExpr));
defineExpr(_memberAccess, symbStruct->member(_memberAccess.memberName()));
return false;
}
else if (exprType->category() == Type::Category::TypeType)
{
auto const* decl = expressionToDeclaration(*memberExpr);
if (dynamic_cast<EnumDefinition const*>(decl))
{
auto enumType = dynamic_cast<EnumType const*>(accessType);
solAssert(enumType, "");
defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName()));
return false;
}
else if (dynamic_cast<ContractDefinition const*>(decl))
{
if (auto const* var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
{
if (var->isConstant())
defineExpr(_memberAccess, constantExpr(_memberAccess, *var));
else
defineExpr(_memberAccess, currentValue(*var));
return false;
}
}
}
else if (exprType->category() == Type::Category::Address)
{
memberExpr->accept(*this);
if (_memberAccess.memberName() == "balance")
{
defineExpr(_memberAccess, state().balance(expr(*memberExpr)));
setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context);
m_uninterpretedTerms.insert(&_memberAccess);
return false;
}
}
else if (exprType->category() == Type::Category::Array)
{
memberExpr->accept(*this);
if (_memberAccess.memberName() == "length")
{
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*memberExpr));
solAssert(symbArray, "");
defineExpr(_memberAccess, symbArray->length());
m_uninterpretedTerms.insert(&_memberAccess);
setSymbolicUnknownValue(
expr(_memberAccess),
_memberAccess.annotation().type,
m_context
);
}
return false;
}
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
functionType &&
_memberAccess.memberName() == "selector" &&
functionType->hasDeclaration()
)
{
defineExpr(_memberAccess, functionType->externalIdentifier());
return false;
}
else if (exprType->category() == Type::Category::Module)
{
if (auto const* var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
{
solAssert(var->isConstant(), "");
defineExpr(_memberAccess, constantExpr(_memberAccess, *var));
return false;
}
}
m_unsupportedErrors.warning(
7650_error,
_memberAccess.location(),
"Assertion checker does not yet support this expression."
);
return true;
}
void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
{
createExpr(_indexAccess);
if (_indexAccess.annotation().type->category() == Type::Category::TypeType)
return;
makeOutOfBoundsVerificationTarget(_indexAccess);
if (auto const* type = dynamic_cast<FixedBytesType const*>(_indexAccess.baseExpression().annotation().type))
{
smtutil::Expression base = expr(_indexAccess.baseExpression());
if (type->numBytes() == 1)
defineExpr(_indexAccess, base);
else
{
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_indexAccess.baseExpression().annotation().type);
solAssert(!isSigned, "");
solAssert(bvSize >= 16, "");
solAssert(bvSize % 8 == 0, "");
smtutil::Expression idx = expr(*_indexAccess.indexExpression());
auto bvBase = smtutil::Expression::int2bv(base, bvSize);
auto bvShl = smtutil::Expression::int2bv(idx * 8, bvSize);
auto bvShr = smtutil::Expression::int2bv(bvSize - 8, bvSize);
auto result = (bvBase << bvShl) >> bvShr;
auto anyValue = expr(_indexAccess);
m_context.expression(_indexAccess)->increaseIndex();
unsigned numBytes = bvSize / 8;
auto withBound = smtutil::Expression::ite(
idx < numBytes,
smtutil::Expression::bv2int(result, false),
anyValue
);
defineExpr(_indexAccess, withBound);
}
return;
}
std::shared_ptr<smt::SymbolicVariable> array;
if (auto const* id = dynamic_cast<Identifier const*>(&_indexAccess.baseExpression()))
{
auto varDecl = identifierToVariable(*id);
solAssert(varDecl, "");
array = m_context.variable(*varDecl);
if (varDecl && varDecl->isConstant())
m_context.addAssertion(currentValue(*varDecl) == expr(*id));
}
else
{
solAssert(m_context.knownExpression(_indexAccess.baseExpression()), "");
array = m_context.expression(_indexAccess.baseExpression());
}
auto arrayVar = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(array);
solAssert(arrayVar, "");
Type const* baseType = _indexAccess.baseExpression().annotation().type;
defineExpr(_indexAccess, smtutil::Expression::select(
arrayVar->elements(),
expr(*_indexAccess.indexExpression(), keyType(baseType))
));
setSymbolicUnknownValue(
expr(_indexAccess),
_indexAccess.annotation().type,
m_context
);
m_uninterpretedTerms.insert(&_indexAccess);
}
void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
createExpr(_indexRangeAccess);
/// The actual slice is created by CHC which also assigns the length.
}
void SMTEncoder::arrayAssignment()
{
m_arrayAssignmentHappened = true;
}
void SMTEncoder::indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide)
{
auto toStore = _rightHandSide;
auto const* lastExpr = &_expr;
while (true)
{
if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(lastExpr))
{
auto const& base = indexAccess->baseExpression();
if (dynamic_cast<Identifier const*>(&base))
base.accept(*this);
Type const* baseType = base.annotation().type;
auto indexExpr = expr(*indexAccess->indexExpression(), keyType(baseType));
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(base));
solAssert(symbArray, "");
toStore = smtutil::Expression::tuple_constructor(
smtutil::Expression(std::make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
{smtutil::Expression::store(symbArray->elements(), indexExpr, toStore), symbArray->length()}
);
defineExpr(*indexAccess, smtutil::Expression::select(
symbArray->elements(),
indexExpr
));
lastExpr = &indexAccess->baseExpression();
}
else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(lastExpr))
{
auto const& base = memberAccess->expression();
if (dynamic_cast<Identifier const*>(&base))
base.accept(*this);
if (
auto const* structType = dynamic_cast<StructType const*>(base.annotation().type);
structType && structType->recursive()
)
{
m_unsupportedErrors.warning(
4375_error,
memberAccess->location(),
"Assertion checker does not support recursive structs."
);
return;
}
if (auto varDecl = identifierToVariable(*memberAccess))
{
if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl);
assignment(*varDecl, toStore);
break;
}
auto symbStruct = std::dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(base));
solAssert(symbStruct, "");
symbStruct->assignMember(memberAccess->memberName(), toStore);
toStore = symbStruct->currentValue();
defineExpr(*memberAccess, symbStruct->member(memberAccess->memberName()));
lastExpr = &memberAccess->expression();
}
else if (auto const& id = dynamic_cast<Identifier const*>(lastExpr))
{
auto varDecl = identifierToVariable(*id);
solAssert(varDecl, "");
if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl);
assignment(*varDecl, toStore);
defineExpr(*id, currentValue(*varDecl));
break;
}
else
{
auto type = lastExpr->annotation().type;
if (
dynamic_cast<ReferenceType const*>(type) ||
dynamic_cast<MappingType const*>(type)
)
resetReferences(type);
assignment(*m_context.expression(*lastExpr), toStore);
break;
}
}
}
void SMTEncoder::arrayPush(FunctionCall const& _funCall)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
solAssert(memberAccess, "");
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
solAssert(symbArray, "");
auto oldLength = symbArray->length();
m_context.addAssertion(oldLength >= 0);
// Real world assumption: the array length is assumed to not overflow.
// This assertion guarantees that both the current and updated lengths have the above property.
m_context.addAssertion(oldLength + 1 < (smt::maxValue(*TypeProvider::uint256()) - 1));
auto const& arguments = _funCall.arguments();
auto arrayType = dynamic_cast<ArrayType const*>(symbArray->type());
solAssert(arrayType, "");
auto elementType = arrayType->baseType();
smtutil::Expression element = arguments.empty() ?
smt::zeroValue(elementType) :
expr(*arguments.front(), elementType);
smtutil::Expression store = smtutil::Expression::store(
symbArray->elements(),
oldLength,
element
);
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == store);
m_context.addAssertion(symbArray->length() == oldLength + 1);
if (arguments.empty())
defineExpr(_funCall, element);
assignment(memberAccess->expression(), symbArray->currentValue());
}
void SMTEncoder::arrayPop(FunctionCall const& _funCall)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(cleanExpression(_funCall.expression()));
solAssert(memberAccess, "");
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
solAssert(symbArray, "");
makeArrayPopVerificationTarget(_funCall);
auto oldElements = symbArray->elements();
auto oldLength = symbArray->length();
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == oldElements);
auto newLength = smtutil::Expression::ite(
oldLength > 0,
oldLength - 1,
0
);
m_context.addAssertion(symbArray->length() == newLength);
assignment(memberAccess->expression(), symbArray->currentValue());
}
void SMTEncoder::defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex)
{
if (!m_context.knownGlobalSymbol(_name))
{
bool abstract = m_context.createGlobalSymbol(_name, _expr);
if (abstract)
m_unsupportedErrors.warning(
1695_error,
_expr.location(),
"Assertion checker does not yet support this global variable."
);
}
else if (_increaseIndex)
m_context.globalSymbol(_name)->increaseIndex();
// The default behavior is not to increase the index since
// most of the global values stay the same throughout a tx.
if (isSupportedType(*_expr.annotation().type))
defineExpr(_expr, m_context.globalSymbol(_name)->currentValue());
}
bool SMTEncoder::shortcutRationalNumber(Expression const& _expr)
{
RationalNumberType const* rationalType = isConstant(_expr);
if (!rationalType)
return false;
if (rationalType->isNegative())
defineExpr(_expr, smtutil::Expression(u2s(rationalType->literalValue(nullptr))));
else
defineExpr(_expr, smtutil::Expression(rationalType->literalValue(nullptr)));
return true;
}
void SMTEncoder::arithmeticOperation(BinaryOperation const& _op)
{
auto type = _op.annotation().commonType;
solAssert(type, "");
solAssert(type->category() == Type::Category::Integer || type->category() == Type::Category::FixedPoint, "");
switch (_op.getOperator())
{
case Token::Add:
case Token::Sub:
case Token::Mul:
case Token::Div:
case Token::Mod:
{
auto values = arithmeticOperation(
_op.getOperator(),
expr(_op.leftExpression()),
expr(_op.rightExpression()),
_op.annotation().commonType,
_op
);
defineExpr(_op, values.first);
break;
}
default:
m_unsupportedErrors.warning(
5188_error,
_op.location(),
"Assertion checker does not yet implement this operator."
);
}
}
std::pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
Token _op,
smtutil::Expression const& _left,
smtutil::Expression const& _right,
Type const* _commonType,
Expression const& _operation
)
{
static std::set<Token> validOperators{
Token::Add,
Token::Sub,
Token::Mul,
Token::Div,
Token::Mod
};
solAssert(validOperators.count(_op), "");
solAssert(_commonType, "");
solAssert(
_commonType->category() == Type::Category::Integer || _commonType->category() == Type::Category::FixedPoint,
""
);
IntegerType const* intType = nullptr;
if (auto type = dynamic_cast<IntegerType const*>(_commonType))
intType = type;
else
intType = TypeProvider::uint256();
auto valueUnbounded = [&]() -> smtutil::Expression {
switch (_op)
{
case Token::Add: return _left + _right;
case Token::Sub: return _left - _right;
case Token::Mul: return _left * _right;
case Token::Div: return divModWithSlacks(_left, _right, *intType).first;
case Token::Mod: return divModWithSlacks(_left, _right, *intType).second;
default: solAssert(false, "");
}
}();
if (m_checked)
return {valueUnbounded, valueUnbounded};
if (_op == Token::Div || _op == Token::Mod)
{
// mod and unsigned division never underflow/overflow
if (_op == Token::Mod || !intType->isSigned())
return {valueUnbounded, valueUnbounded};
// The only case where division overflows is
// - type is signed
// - LHS is type.min
// - RHS is -1
// the result is then -(type.min), which wraps back to type.min
smtutil::Expression maxLeft = _left == smt::minValue(*intType);
smtutil::Expression minusOneRight = _right == std::numeric_limits<size_t >::max();
smtutil::Expression wrap = smtutil::Expression::ite(maxLeft && minusOneRight, smt::minValue(*intType), valueUnbounded);
return {wrap, valueUnbounded};
}
auto symbMin = smt::minValue(*intType);
auto symbMax = smt::maxValue(*intType);
smtutil::Expression intValueRange = (0 - symbMin) + symbMax + 1;
std::string suffix = std::to_string(_operation.id()) + "_" + std::to_string(m_context.newUniqueId());
smt::SymbolicIntVariable k(intType, intType, "k_" + suffix, m_context);
smt::SymbolicIntVariable m(intType, intType, "m_" + suffix, m_context);
// To wrap around valueUnbounded in case of overflow or underflow, we replace it with a k, given:
// 1. k + m * intValueRange = valueUnbounded
// 2. k is in range of the desired integer type
auto wrap = k.currentValue();
m_context.addAssertion(valueUnbounded == (k.currentValue() + intValueRange * m.currentValue()));
m_context.addAssertion(k.currentValue() >= symbMin);
m_context.addAssertion(k.currentValue() <= symbMax);
// TODO this could be refined:
// for unsigned types it's enough to check only the upper bound.
auto value = smtutil::Expression::ite(
valueUnbounded > symbMax,
wrap,
smtutil::Expression::ite(
valueUnbounded < symbMin,
wrap,
valueUnbounded
)
);
return {value, valueUnbounded};
}
smtutil::Expression SMTEncoder::bitwiseOperation(
Token _op,
smtutil::Expression const& _left,
smtutil::Expression const& _right,
Type const* _commonType
)
{
static std::set<Token> validOperators{
Token::BitAnd,
Token::BitOr,
Token::BitXor,
Token::SHL,
Token::SHR,
Token::SAR
};
solAssert(validOperators.count(_op), "");
solAssert(_commonType, "");
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_commonType);
auto bvLeft = smtutil::Expression::int2bv(_left, bvSize);
auto bvRight = smtutil::Expression::int2bv(_right, bvSize);
std::optional<smtutil::Expression> result;
switch (_op)
{
case Token::BitAnd:
result = bvLeft & bvRight;
break;
case Token::BitOr:
result = bvLeft | bvRight;
break;
case Token::BitXor:
result = bvLeft ^ bvRight;
break;
case Token::SHL:
result = bvLeft << bvRight;
break;
case Token::SHR:
result = bvLeft >> bvRight;
break;
case Token::SAR:
result = isSigned ?
smtutil::Expression::ashr(bvLeft, bvRight) :
bvLeft >> bvRight;
break;
default:
solAssert(false, "");
}
solAssert(result.has_value(), "");
return smtutil::Expression::bv2int(*result, isSigned);
}
void SMTEncoder::compareOperation(BinaryOperation const& _op)
{
auto commonType = _op.annotation().commonType;
solAssert(commonType, "");
if (isSupportedType(*commonType))
{
smtutil::Expression left(expr(_op.leftExpression(), commonType));
smtutil::Expression right(expr(_op.rightExpression(), commonType));
Token op = _op.getOperator();
std::shared_ptr<smtutil::Expression> value;
if (smt::isNumber(*commonType))
{
value = std::make_shared<smtutil::Expression>(
op == Token::Equal ? (left == right) :
op == Token::NotEqual ? (left != right) :
op == Token::LessThan ? (left < right) :
op == Token::LessThanOrEqual ? (left <= right) :
op == Token::GreaterThan ? (left > right) :
/*op == Token::GreaterThanOrEqual*/ (left >= right)
);
}
else // Bool
{
solUnimplementedAssert(smt::isBool(*commonType), "Operation not yet supported");
value = std::make_shared<smtutil::Expression>(
op == Token::Equal ? (left == right) :
/*op == Token::NotEqual*/ (left != right)
);
}
// TODO: check that other values for op are not possible.
defineExpr(_op, *value);
}
else
m_unsupportedErrors.warning(
7229_error,
_op.location(),
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons"
);
}
void SMTEncoder::booleanOperation(BinaryOperation const& _op)
{
solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, "");
solAssert(_op.annotation().commonType, "");
solAssert(_op.annotation().commonType->category() == Type::Category::Bool, "");
// @TODO check that both of them are not constant
_op.leftExpression().accept(*this);
if (_op.getOperator() == Token::And)
{
auto indicesAfterSecond = visitBranch(&_op.rightExpression(), expr(_op.leftExpression())).first;
mergeVariables(!expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond);
defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
}
else
{
auto indicesAfterSecond = visitBranch(&_op.rightExpression(), !expr(_op.leftExpression())).first;
mergeVariables(expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond);
defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression()));
}
}
void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
{
auto op = _op.getOperator();
solAssert(TokenTraits::isBitOp(op) || TokenTraits::isShiftOp(op), "");
auto commonType = _op.annotation().commonType;
solAssert(commonType, "");
defineExpr(_op, bitwiseOperation(
_op.getOperator(),
expr(_op.leftExpression(), commonType),
expr(_op.rightExpression(), commonType),
commonType
));
}
void SMTEncoder::bitwiseNotOperation(UnaryOperation const& _op)
{
solAssert(_op.getOperator() == Token::BitNot, "");
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_op.annotation().type);
auto bvOperand = smtutil::Expression::int2bv(expr(_op.subExpression(), _op.annotation().type), bvSize);
defineExpr(_op, smtutil::Expression::bv2int(~bvOperand, isSigned));
}
std::pair<smtutil::Expression, smtutil::Expression> SMTEncoder::divModWithSlacks(
smtutil::Expression _left,
smtutil::Expression _right,
IntegerType const& _type
)
{
if (m_settings.divModNoSlacks)
return {_left / _right, _left % _right};
IntegerType const* intType = &_type;
std::string suffix = "div_mod_" + std::to_string(m_context.newUniqueId());
smt::SymbolicIntVariable dSymb(intType, intType, "d_" + suffix, m_context);
smt::SymbolicIntVariable rSymb(intType, intType, "r_" + suffix, m_context);
auto d = dSymb.currentValue();
auto r = rSymb.currentValue();
// x / y = d and x % y = r iff d * y + r = x and
// either x >= 0 and 0 <= r < abs(y) (or just 0 <= r < y for unsigned)
// or x < 0 and -abs(y) < r <= 0
m_context.addAssertion(((d * _right) + r) == _left);
if (_type.isSigned())
m_context.addAssertion(
(_left >= 0 && 0 <= r && (_right == 0 || r < smtutil::abs(_right))) ||
(_left < 0 && ((_right == 0 || 0 - smtutil::abs(_right) < r) && r <= 0))
);
else // unsigned version
m_context.addAssertion(0 <= r && (_right == 0 || r < _right));
auto divResult = smtutil::Expression::ite(_right == 0, 0, d);
auto modResult = smtutil::Expression::ite(_right == 0, 0, r);
return {divResult, modResult};
}
void SMTEncoder::assignment(Expression const& _left, smtutil::Expression const& _right)
{
assignment(_left, _right, _left.annotation().type);
}
void SMTEncoder::assignment(
Expression const& _left,
smtutil::Expression const& _right,
Type const* _type
)
{
solAssert(
_left.annotation().type->category() != Type::Category::Tuple,
"Tuple assignments should be handled by tupleAssignment."
);
Expression const* left = cleanExpression(_left);
if (!isSupportedType(*_type))
{
// Give it a new index anyway to keep the SSA scheme sound.
if (auto varDecl = identifierToVariable(*left))
m_context.newValue(*varDecl);
}
else if (auto varDecl = identifierToVariable(*left))
{
if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl);
assignment(*varDecl, _right);
}
else if (
dynamic_cast<IndexAccess const*>(left) ||
dynamic_cast<MemberAccess const*>(left)
)
indexOrMemberAssignment(*left, _right);
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(left))
{
if (auto funType = dynamic_cast<FunctionType const*>(funCall->expression().annotation().type))
{
if (funType->kind() == FunctionType::Kind::ArrayPush)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression());
solAssert(memberAccess, "");
auto symbArray = std::dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
solAssert(symbArray, "");
auto oldLength = symbArray->length();
auto store = smtutil::Expression::store(
symbArray->elements(),
symbArray->length() - 1,
_right
);
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == store);
m_context.addAssertion(symbArray->length() == oldLength);
assignment(memberAccess->expression(), symbArray->currentValue());
}
else if (funType->kind() == FunctionType::Kind::Internal)
{
for (auto type: replaceUserTypes(funType->returnParameterTypes()))
if (type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type))
resetReferences(type);
}
}
}
else
solAssert(false, "");
}
void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _right)
{
auto lTuple = dynamic_cast<TupleExpression const*>(innermostTuple(_left));
solAssert(lTuple, "");
Expression const* right = innermostTuple(_right);
auto const& lComponents = lTuple->components();
// If both sides are tuple expressions, we individually and potentially
// recursively assign each pair of components.
// This is because of potential type conversion.
if (auto rTuple = dynamic_cast<TupleExpression const*>(right))
{
auto const& rComponents = rTuple->components();
solAssert(lComponents.size() == rComponents.size(), "");
for (unsigned i = 0; i < lComponents.size(); ++i)
{
if (!lComponents.at(i) || !rComponents.at(i))
continue;
auto const& lExpr = *lComponents.at(i);
auto const& rExpr = *rComponents.at(i);
if (lExpr.annotation().type->category() == Type::Category::Tuple)
tupleAssignment(lExpr, rExpr);
else
{
auto type = lExpr.annotation().type;
assignment(lExpr, expr(rExpr, type), type);
}
}
}
else
{
auto rType = dynamic_cast<TupleType const*>(right->annotation().type);
solAssert(rType, "");
auto const& rComponents = rType->components();
solAssert(lComponents.size() == rComponents.size(), "");
auto symbRight = expr(*right);
solAssert(symbRight.sort->kind == smtutil::Kind::Tuple, "");
for (unsigned i = 0; i < lComponents.size(); ++i)
if (auto component = lComponents.at(i); component && rComponents.at(i))
assignment(*component, smtutil::Expression::tuple_get(symbRight, i), component->annotation().type);
}
}
smtutil::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment)
{
static std::map<Token, Token> const compoundToArithmetic{
{Token::AssignAdd, Token::Add},
{Token::AssignSub, Token::Sub},
{Token::AssignMul, Token::Mul},
{Token::AssignDiv, Token::Div},
{Token::AssignMod, Token::Mod}
};
static std::map<Token, Token> const compoundToBitwise{
{Token::AssignBitAnd, Token::BitAnd},
{Token::AssignBitOr, Token::BitOr},
{Token::AssignBitXor, Token::BitXor},
{Token::AssignShl, Token::SHL},
{Token::AssignShr, Token::SHR},
{Token::AssignSar, Token::SAR}
};
Token op = _assignment.assignmentOperator();
solAssert(compoundToArithmetic.count(op) || compoundToBitwise.count(op), "");
auto decl = identifierToVariable(_assignment.leftHandSide());
if (compoundToBitwise.count(op))
return bitwiseOperation(
compoundToBitwise.at(op),
decl ? currentValue(*decl) : expr(_assignment.leftHandSide(), _assignment.annotation().type),
expr(_assignment.rightHandSide(), _assignment.annotation().type),
_assignment.annotation().type
);
auto values = arithmeticOperation(
compoundToArithmetic.at(op),
decl ? currentValue(*decl) : expr(_assignment.leftHandSide(), _assignment.annotation().type),
expr(_assignment.rightHandSide(), _assignment.annotation().type),
_assignment.annotation().type,
_assignment
);
return values.first;
}
void SMTEncoder::expressionToTupleAssignment(std::vector<std::shared_ptr<VariableDeclaration>> const& _variables, Expression const& _rhs)
{
auto symbolicVar = m_context.expression(_rhs);
if (_variables.size() > 1)
{
auto symbTuple = std::dynamic_pointer_cast<smt::SymbolicTupleVariable>(symbolicVar);
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
solAssert(symbComponents.size() == _variables.size(), "");
auto tupleType = dynamic_cast<TupleType const*>(_rhs.annotation().type);
solAssert(tupleType, "");
auto const& typeComponents = tupleType->components();
solAssert(typeComponents.size() == symbComponents.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto param = _variables.at(i);
if (param)
{
solAssert(m_context.knownVariable(*param), "");
assignment(*param, symbTuple->component(i, typeComponents[i], param->type()));
}
}
}
else if (_variables.size() == 1)
{
auto const& var = *_variables.front();
solAssert(m_context.knownVariable(var), "");
assignment(var, _rhs);
}
}
void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value)
{
// In general, at this point, the SMT sorts of _variable and _value are the same,
// even if there is implicit conversion.
// This is a special case where the SMT sorts are different.
// For now we are unaware of other cases where this happens, but if they do appear
// we should extract this into an `implicitConversion` function.
assignment(_variable, expr(_value, _variable.type()));
}
void SMTEncoder::assignment(VariableDeclaration const& _variable, smtutil::Expression const& _value)
{
Type const* type = _variable.type();
if (type->category() == Type::Category::Mapping)
arrayAssignment();
assignment(*m_context.variable(_variable), _value);
}
void SMTEncoder::assignment(smt::SymbolicVariable& _symVar, smtutil::Expression const& _value)
{
m_context.addAssertion(_symVar.increaseIndex() == _value);
}
std::pair<SMTEncoder::VariableIndices, smtutil::Expression> SMTEncoder::visitBranch(
ASTNode const* _statement,
smtutil::Expression _condition
)
{
return visitBranch(_statement, &_condition);
}
std::pair<SMTEncoder::VariableIndices, smtutil::Expression> SMTEncoder::visitBranch(
ASTNode const* _statement,
smtutil::Expression const* _condition
)
{
auto indicesBeforeBranch = copyVariableIndices();
if (_condition)
pushPathCondition(*_condition);
_statement->accept(*this);
auto pathConditionOnExit = currentPathConditions();
if (_condition)
popPathCondition();
auto indicesAfterBranch = copyVariableIndices();
resetVariableIndices(indicesBeforeBranch);
return {indicesAfterBranch, pathConditionOnExit};
}
void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smtutil::Expression> const& _callArgs)
{
auto const& funParams = _function.parameters();
solAssert(funParams.size() == _callArgs.size(), "");
for (unsigned i = 0; i < funParams.size(); ++i)
if (createVariable(*funParams[i]))
{
m_context.addAssertion(_callArgs[i] == m_context.newValue(*funParams[i]));
if (funParams[i]->annotation().type->category() == Type::Category::Mapping)
m_arrayAssignmentHappened = true;
}
std::vector<VariableDeclaration const*> localVars;
if (auto const* fun = dynamic_cast<FunctionDefinition const*>(&_function))
localVars = localVariablesIncludingModifiers(*fun, m_currentContract);
else
localVars = _function.localVariables();
for (auto const& variable: localVars)
if (createVariable(*variable))
{
m_context.newValue(*variable);
m_context.setZeroValue(*variable);
}
if (_function.returnParameterList())
for (auto const& retParam: _function.returnParameters())
if (createVariable(*retParam))
{
m_context.newValue(*retParam);
m_context.setZeroValue(*retParam);
}
}
void SMTEncoder::createStateVariables(ContractDefinition const& _contract)
{
for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract))
createVariable(*var);
}
void SMTEncoder::initializeStateVariables(ContractDefinition const& _contract)
{
for (auto var: _contract.stateVariables())
{
solAssert(m_context.knownVariable(*var), "");
m_context.setZeroValue(*var);
}
for (auto var: _contract.stateVariables())
if (var->value())
{
var->value()->accept(*this);
assignment(*var, *var->value());
}
}
void SMTEncoder::createLocalVariables(FunctionDefinition const& _function)
{
for (auto const& variable: localVariablesIncludingModifiers(_function, m_currentContract))
createVariable(*variable);
for (auto const& param: _function.parameters())
createVariable(*param);
if (_function.returnParameterList())
for (auto const& retParam: _function.returnParameters())
createVariable(*retParam);
}
void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function)
{
for (auto const& variable: localVariablesIncludingModifiers(_function, m_currentContract))
{
solAssert(m_context.knownVariable(*variable), "");
m_context.setZeroValue(*variable);
}
for (auto const& param: _function.parameters())
{
solAssert(m_context.knownVariable(*param), "");
m_context.setUnknownValue(*param);
}
if (_function.returnParameterList())
for (auto const& retParam: _function.returnParameters())
{
solAssert(m_context.knownVariable(*retParam), "");
m_context.setZeroValue(*retParam);
}
}
void SMTEncoder::resetStateVariables()
{
m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.isStateVariable(); });
}
void SMTEncoder::resetMemoryVariables()
{
m_context.resetVariables([&](VariableDeclaration const& _variable) {
return _variable.referenceLocation() == VariableDeclaration::Location::Memory;
});
}
void SMTEncoder::resetStorageVariables()
{
m_context.resetVariables([&](VariableDeclaration const& _variable) {
return _variable.referenceLocation() == VariableDeclaration::Location::Storage || _variable.isStateVariable();
});
}
void SMTEncoder::resetBalances()
{
state().newBalances();
}
void SMTEncoder::resetReferences(VariableDeclaration const& _varDecl)
{
m_context.resetVariables([&](VariableDeclaration const& _var) {
if (_var == _varDecl)
return false;
// If both are state variables no need to clear knowledge.
if (_var.isStateVariable() && _varDecl.isStateVariable())
return false;
return sameTypeOrSubtype(_var.type(), _varDecl.type());
});
}
void SMTEncoder::resetReferences(Type const* _type)
{
m_context.resetVariables([&](VariableDeclaration const& _var) {
return sameTypeOrSubtype(_var.type(), _type);
});
}
bool SMTEncoder::sameTypeOrSubtype(Type const* _a, Type const* _b)
{
bool foundSame = false;
solidity::util::BreadthFirstSearch<Type const*> bfs{{_a}};
bfs.run([&](auto _type, auto&& _addChild) {
if (*typeWithoutPointer(_b) == *typeWithoutPointer(_type))
{
foundSame = true;
bfs.abort();
}
if (auto const* mapType = dynamic_cast<MappingType const*>(_type))
_addChild(mapType->valueType());
else if (auto const* arrayType = dynamic_cast<ArrayType const*>(_type))
_addChild(arrayType->baseType());
else if (auto const* structType = dynamic_cast<StructType const*>(_type))
for (auto const& member: structType->nativeMembers(nullptr))
_addChild(member.type);
});
return foundSame;
}
bool SMTEncoder::isSupportedType(Type const& _type) const
{
return smt::isSupportedType(*underlyingType(&_type));
}
Type const* SMTEncoder::typeWithoutPointer(Type const* _type)
{
if (auto refType = dynamic_cast<ReferenceType const*>(_type))
return TypeProvider::withLocationIfReference(refType->location(), _type);
return _type;
}
void SMTEncoder::mergeVariables(smtutil::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse)
{
for (auto const& entry: _indicesEndTrue)
{
VariableDeclaration const* var = entry.first;
auto trueIndex = entry.second;
if (_indicesEndFalse.count(var) && _indicesEndFalse.at(var) != trueIndex)
{
m_context.addAssertion(m_context.newValue(*var) == smtutil::Expression::ite(
_condition,
valueAtIndex(*var, trueIndex),
valueAtIndex(*var, _indicesEndFalse.at(var)))
);
}
}
}
smtutil::Expression SMTEncoder::currentValue(VariableDeclaration const& _decl) const
{
solAssert(m_context.knownVariable(_decl), "");
return m_context.variable(_decl)->currentValue();
}
smtutil::Expression SMTEncoder::valueAtIndex(VariableDeclaration const& _decl, unsigned _index) const
{
solAssert(m_context.knownVariable(_decl), "");
return m_context.variable(_decl)->valueAtIndex(_index);
}
bool SMTEncoder::createVariable(VariableDeclaration const& _varDecl)
{
if (m_context.knownVariable(_varDecl))
return true;
bool abstract = m_context.createVariable(_varDecl);
if (abstract)
{
m_unsupportedErrors.warning(
8115_error,
_varDecl.location(),
"Assertion checker does not yet support the type of this variable."
);
return false;
}
return true;
}
smtutil::Expression SMTEncoder::expr(Expression const& _e, Type const* _targetType)
{
if (!m_context.knownExpression(_e))
{
m_unsupportedErrors.warning(6031_error, _e.location(), "Internal error: Expression undefined for SMT solver." );
createExpr(_e);
}
return m_context.expression(_e)->currentValue(underlyingType(_targetType));
}
void SMTEncoder::createExpr(Expression const& _e)
{
bool abstract = m_context.createExpression(_e);
if (abstract)
m_unsupportedErrors.warning(
8364_error,
_e.location(),
"Assertion checker does not yet implement type " + _e.annotation().type->toString()
);
}
void SMTEncoder::defineExpr(Expression const& _e, smtutil::Expression _value)
{
auto type = _e.annotation().type;
createExpr(_e);
solAssert(_value.sort->kind != smtutil::Kind::Function, "Equality operator applied to type that is not fully supported");
if (!smt::isInaccessibleDynamic(*type))
m_context.addAssertion(expr(_e) == _value);
if (m_checked && smt::isNumber(*type))
m_context.addAssertion(smtutil::Expression::implies(
currentPathConditions(),
smt::symbolicUnknownConstraints(expr(_e), type)
));
}
void SMTEncoder::defineExpr(Expression const& _e, std::vector<std::optional<smtutil::Expression>> const& _values)
{
if (_values.size() == 1 && _values.front())
{
defineExpr(_e, *_values.front());
return;
}
auto const& symbTuple = std::dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_e));
solAssert(symbTuple, "");
symbTuple->increaseIndex();
auto const& symbComponents = symbTuple->components();
solAssert(symbComponents.size() == _values.size(), "");
auto tupleType = dynamic_cast<TupleType const*>(_e.annotation().type);
solAssert(tupleType, "");
solAssert(tupleType->components().size() == symbComponents.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
if (_values[i] && !smt::isInaccessibleDynamic(*tupleType->components()[i]))
m_context.addAssertion(symbTuple->component(i) == *_values[i]);
}
void SMTEncoder::popPathCondition()
{
solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty.");
m_pathConditions.pop_back();
}
void SMTEncoder::pushPathCondition(smtutil::Expression const& _e)
{
m_pathConditions.push_back(currentPathConditions() && _e);
}
void SMTEncoder::setPathCondition(smtutil::Expression const& _e)
{
if (m_pathConditions.empty())
m_pathConditions.push_back(_e);
else
m_pathConditions.back() = _e;
}
smtutil::Expression SMTEncoder::currentPathConditions()
{
if (m_pathConditions.empty())
return smtutil::Expression(true);
return m_pathConditions.back();
}
SecondarySourceLocation SMTEncoder::callStackMessage(std::vector<CallStackEntry> const& _callStack)
{
SecondarySourceLocation callStackLocation;
solAssert(!_callStack.empty(), "");
callStackLocation.append("Callstack:", SourceLocation());
for (auto const& call: _callStack | ranges::views::reverse)
if (call.second)
callStackLocation.append("", call.second->location());
return callStackLocation;
}
std::pair<CallableDeclaration const*, ASTNode const*> SMTEncoder::popCallStack()
{
solAssert(!m_callStack.empty(), "");
auto lastCalled = m_callStack.back();
m_callStack.pop_back();
return lastCalled;
}
void SMTEncoder::pushCallStack(CallStackEntry _entry)
{
m_callStack.push_back(_entry);
}
void SMTEncoder::addPathImpliedExpression(smtutil::Expression const& _e)
{
m_context.addAssertion(smtutil::Expression::implies(currentPathConditions(), _e));
}
bool SMTEncoder::isRootFunction()
{
return m_callStack.size() == 1;
}
bool SMTEncoder::visitedFunction(FunctionDefinition const* _funDef)
{
for (auto const& call: m_callStack)
if (call.first == _funDef)
return true;
return false;
}
ContractDefinition const* SMTEncoder::currentScopeContract()
{
for (auto&& f: m_callStack | ranges::views::reverse | ranges::views::keys)
if (auto fun = dynamic_cast<FunctionDefinition const*>(f))
return fun->annotation().contract;
return nullptr;
}
SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices()
{
VariableIndices indices;
for (auto const& var: m_context.variables())
indices.emplace(var.first, var.second->index());
return indices;
}
void SMTEncoder::resetVariableIndices(VariableIndices const& _indices)
{
for (auto const& var: _indices)
m_context.variable(*var.first)->setIndex(var.second);
}
void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function)
{
solAssert(_contract, "");
for (auto var: stateVariablesIncludingInheritedAndPrivate(*_contract))
m_context.variable(*var)->resetIndex();
if (_function)
{
for (auto const& var: _function->parameters() + _function->returnParameters())
m_context.variable(*var)->resetIndex();
for (auto const& var: localVariablesIncludingModifiers(*_function, _contract))
m_context.variable(*var)->resetIndex();
}
state().reset();
}
Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess)
{
Expression const* base = &_indexAccess.baseExpression();
while (auto access = dynamic_cast<IndexAccess const*>(base))
base = &access->baseExpression();
return base;
}
Type const* SMTEncoder::keyType(Type const* _type)
{
if (auto const* mappingType = dynamic_cast<MappingType const*>(_type))
return mappingType->keyType();
if (
dynamic_cast<ArrayType const*>(_type) ||
dynamic_cast<ArraySliceType const*>(_type)
)
return TypeProvider::uint256();
else
solAssert(false, "");
}
Expression const* SMTEncoder::innermostTuple(Expression const& _expr)
{
auto const* tuple = dynamic_cast<TupleExpression const*>(&_expr);
if (!tuple || tuple->isInlineArray())
return &_expr;
Expression const* expr = tuple;
while (tuple && !tuple->isInlineArray() && tuple->components().size() == 1)
{
expr = tuple->components().front().get();
tuple = dynamic_cast<TupleExpression const*>(expr);
}
solAssert(expr, "");
return expr;
}
Type const* SMTEncoder::underlyingType(Type const* _type)
{
if (auto userType = dynamic_cast<UserDefinedValueType const*>(_type))
_type = &userType->underlyingType();
return _type;
}
TypePointers SMTEncoder::replaceUserTypes(TypePointers const& _types)
{
return applyMap(_types, [](auto _type) {
if (auto userType = dynamic_cast<UserDefinedValueType const*>(_type))
return &userType->underlyingType();
return _type;
});
}
std::pair<Expression const*, FunctionCallOptions const*> SMTEncoder::functionCallExpression(FunctionCall const& _funCall)
{
Expression const* callExpr = &_funCall.expression();
auto const* callOptions = dynamic_cast<FunctionCallOptions const*>(callExpr);
if (callOptions)
callExpr = &callOptions->expression();
return {callExpr, callOptions};
}
Expression const* SMTEncoder::cleanExpression(Expression const& _expr)
{
auto const* expr = &_expr;
if (auto const* tuple = dynamic_cast<TupleExpression const*>(expr))
return cleanExpression(*innermostTuple(*tuple));
if (auto const* functionCall = dynamic_cast<FunctionCall const*>(expr))
if (*functionCall->annotation().kind == FunctionCallKind::TypeConversion)
{
auto typeType = dynamic_cast<TypeType const*>(functionCall->expression().annotation().type);
solAssert(typeType, "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(typeType->actualType()))
if (arrayType->isByteArrayOrString())
{
// this is a cast to `bytes`
solAssert(functionCall->arguments().size() == 1, "");
Expression const& arg = *functionCall->arguments()[0];
if (
auto const* argArrayType = dynamic_cast<ArrayType const*>(arg.annotation().type);
argArrayType && argArrayType->isByteArrayOrString()
)
return cleanExpression(arg);
}
}
solAssert(expr, "");
return expr;
}
std::set<VariableDeclaration const*> SMTEncoder::touchedVariables(ASTNode const& _node)
{
std::vector<CallableDeclaration const*> callStack;
for (auto const& call: m_callStack)
callStack.push_back(call.first);
return m_variableUsage.touchedVariables(_node, callStack);
}
Declaration const* SMTEncoder::expressionToDeclaration(Expression const& _expr) const
{
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expr))
return identifier->annotation().referencedDeclaration;
if (auto const* outerMemberAccess = dynamic_cast<MemberAccess const*>(&_expr))
return outerMemberAccess->annotation().referencedDeclaration;
return nullptr;
}
VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr) const
{
// We do not use `expressionToDeclaration` here because we are not interested in
// struct.field, for example.
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expr))
if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
{
solAssert(m_context.knownVariable(*varDecl), "");
return varDecl;
}
// But we are interested in "contract.var", because that is the same as just "var".
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_expr))
if (dynamic_cast<ContractDefinition const*>(expressionToDeclaration(
*cleanExpression(memberAccess->expression())
)))
if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration))
{
solAssert(m_context.knownVariable(*varDecl), "");
return varDecl;
}
return nullptr;
}
MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
{
if (
auto const* funCall = dynamic_cast<FunctionCall const*>(&_expr);
funCall && funCall->arguments().empty()
)
{
auto const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
if (funType.kind() == FunctionType::Kind::ArrayPush)
return &dynamic_cast<MemberAccess const&>(funCall->expression());
}
return nullptr;
}
smtutil::Expression SMTEncoder::contractAddressValue(FunctionCall const& _f)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_f.expression().annotation().type);
if (funType.kind() == FunctionType::Kind::Internal)
return state().thisAddress();
auto [funExpr, funOptions] = functionCallExpression(_f);
if (MemberAccess const* callBase = dynamic_cast<MemberAccess const*>(funExpr))
return expr(callBase->expression());
solAssert(false, "Unreachable!");
}
VariableDeclaration const* SMTEncoder::publicGetter(Expression const& _expr) const {
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expr))
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration))
return variableDeclaration->isStateVariable() ? variableDeclaration : nullptr;
return nullptr;
}
bool SMTEncoder::isExternalCallToThis(Expression const* _expr) {
auto memberAccess = dynamic_cast<MemberAccess const*>(_expr);
if (!memberAccess)
return false;
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
return identifier &&
identifier->name() == "this" &&
identifier->annotation().referencedDeclaration &&
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
;
}
std::string SMTEncoder::extraComment()
{
std::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;
}
FunctionDefinition const* SMTEncoder::functionCallToDefinition(
FunctionCall const& _funCall,
ContractDefinition const* _scopeContract,
ContractDefinition const* _contextContract
)
{
if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall)
return {};
auto [calledExpr, callOptions] = functionCallExpression(_funCall);
if (TupleExpression const* fun = dynamic_cast<TupleExpression const*>(calledExpr))
{
solAssert(fun->components().size() == 1, "");
calledExpr = innermostTuple(*calledExpr);
}
auto resolveVirtual = [&](auto const* _ref) -> FunctionDefinition const* {
VirtualLookup lookup = *_ref->annotation().requiredLookup;
solAssert(_contextContract || lookup == VirtualLookup::Static, "No contract context provided for function lookup resolution!");
auto funDef = dynamic_cast<FunctionDefinition const*>(_ref->annotation().referencedDeclaration);
if (!funDef)
return funDef;
switch (lookup)
{
case VirtualLookup::Virtual:
return &(funDef->resolveVirtual(*_contextContract));
case VirtualLookup::Super:
{
solAssert(_scopeContract, "");
auto super = _scopeContract->superContract(*_contextContract);
solAssert(super, "Super contract not available.");
return &funDef->resolveVirtual(*_contextContract, super);
}
case VirtualLookup::Static:
return funDef;
}
solAssert(false, "");
};
if (Identifier const* fun = dynamic_cast<Identifier const*>(calledExpr))
return resolveVirtual(fun);
else if (MemberAccess const* fun = dynamic_cast<MemberAccess const*>(calledExpr))
return resolveVirtual(fun);
return {};
}
std::vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
return fold(
_contract.annotation().linearizedBaseContracts,
std::vector<VariableDeclaration const*>{},
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
);
}
std::vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
{
if (auto contract = dynamic_cast<ContractDefinition const*>(_function.scope()))
return stateVariablesIncludingInheritedAndPrivate(*contract);
return {};
}
std::vector<VariableDeclaration const*> SMTEncoder::localVariablesIncludingModifiers(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
return _function.localVariables() + tryCatchVariables(_function) + modifiersVariables(_function, _contract);
}
std::vector<VariableDeclaration const*> SMTEncoder::tryCatchVariables(FunctionDefinition const& _function)
{
struct TryCatchVarsVisitor : public ASTConstVisitor
{
bool visit(TryCatchClause const& _catchClause) override
{
if (_catchClause.parameters())
{
auto const& params = _catchClause.parameters()->parameters();
for (auto param: params)
vars.push_back(param.get());
}
return true;
}
std::vector<VariableDeclaration const*> vars;
} tryCatchVarsVisitor;
_function.accept(tryCatchVarsVisitor);
return tryCatchVarsVisitor.vars;
}
std::vector<VariableDeclaration const*> SMTEncoder::modifiersVariables(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
struct BlockVars: ASTConstVisitor
{
BlockVars(Block const& _block) { _block.accept(*this); }
void endVisit(VariableDeclaration const& _var) { vars.push_back(&_var); }
std::vector<VariableDeclaration const*> vars;
};
std::vector<VariableDeclaration const*> vars;
std::set<ModifierDefinition const*> visited;
for (auto invok: _function.modifiers())
{
if (!invok)
continue;
auto const* modifier = resolveModifierInvocation(*invok, _contract);
if (!modifier || visited.count(modifier))
continue;
visited.insert(modifier);
if (modifier->isImplemented())
{
vars += applyMap(modifier->parameters(), [](auto _var) { return _var.get(); });
vars += BlockVars(modifier->body()).vars;
}
}
return vars;
}
ModifierDefinition const* SMTEncoder::resolveModifierInvocation(ModifierInvocation const& _invocation, ContractDefinition const* _contract)
{
auto const* modifier = dynamic_cast<ModifierDefinition const*>(_invocation.name().annotation().referencedDeclaration);
if (modifier)
{
VirtualLookup lookup = *_invocation.name().annotation().requiredLookup;
solAssert(lookup == VirtualLookup::Virtual || lookup == VirtualLookup::Static, "");
solAssert(_contract || lookup == VirtualLookup::Static, "No contract context provided for modifier lookup resolution!");
if (lookup == VirtualLookup::Virtual)
modifier = &modifier->resolveVirtual(*_contract);
}
return modifier;
}
std::set<FunctionDefinition const*, ASTNode::CompareByID> const& SMTEncoder::contractFunctions(ContractDefinition const& _contract)
{
if (!m_contractFunctions.count(&_contract))
{
auto const& functions = _contract.definedFunctions();
std::set<FunctionDefinition const*, ASTNode::CompareByID> resolvedFunctions(begin(functions), end(functions));
for (auto const* base: _contract.annotation().linearizedBaseContracts)
{
if (base == &_contract)
continue;
for (auto const* baseFunction: base->definedFunctions())
{
if (baseFunction->isConstructor()) // We don't want to include constructors of parent contracts
continue;
bool overridden = false;
for (auto const* function: resolvedFunctions)
if (
function->name() == baseFunction->name() &&
function->kind() == baseFunction->kind() &&
FunctionType(*function).asExternallyCallableFunction(false)->
hasEqualParameterTypes(*FunctionType(*baseFunction).asExternallyCallableFunction(false))
)
{
overridden = true;
break;
}
if (!overridden)
resolvedFunctions.insert(baseFunction);
}
}
m_contractFunctions.emplace(&_contract, std::move(resolvedFunctions));
}
return m_contractFunctions.at(&_contract);
}
std::set<FunctionDefinition const*, ASTNode::CompareByID> const& SMTEncoder::contractFunctionsWithoutVirtual(ContractDefinition const& _contract)
{
if (!m_contractFunctionsWithoutVirtual.count(&_contract))
{
auto allFunctions = contractFunctions(_contract);
for (auto const* base: _contract.annotation().linearizedBaseContracts)
for (auto const* baseFun: base->definedFunctions())
allFunctions.insert(baseFun);
m_contractFunctionsWithoutVirtual.emplace(&_contract, std::move(allFunctions));
}
return m_contractFunctionsWithoutVirtual.at(&_contract);
}
std::map<ContractDefinition const*, std::vector<ASTPointer<frontend::Expression>>> SMTEncoder::baseArguments(ContractDefinition const& _contract)
{
std::map<ContractDefinition const*, std::vector<ASTPointer<Expression>>> baseArgs;
for (auto contract: _contract.annotation().linearizedBaseContracts)
{
/// Collect base contracts and potential constructor arguments.
for (auto specifier: contract->baseContracts())
{
solAssert(specifier, "");
auto const& base = dynamic_cast<ContractDefinition const&>(*specifier->name().annotation().referencedDeclaration);
if (auto args = specifier->arguments())
baseArgs[&base] = *args;
}
/// Collect base constructor arguments given as constructor modifiers.
if (auto constructor = contract->constructor())
for (auto mod: constructor->modifiers())
{
auto decl = mod->name().annotation().referencedDeclaration;
if (auto base = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(!baseArgs.count(base), "");
if (auto args = mod->arguments())
baseArgs[base] = *args;
}
}
}
return baseArgs;
}
RationalNumberType const* SMTEncoder::isConstant(Expression const& _expr)
{
if (auto type = dynamic_cast<RationalNumberType const*>(_expr.annotation().type))
return type;
// _expr may not be constant evaluable.
// In that case we ignore any warnings emitted by the constant evaluator,
// as it will return nullptr in case of failure.
ErrorList l;
ErrorReporter e(l);
if (auto t = ConstantEvaluator::evaluate(e, _expr))
return TypeProvider::rationalNumber(t->value);
return nullptr;
}
std::set<FunctionCall const*, ASTCompareByID<FunctionCall>> SMTEncoder::collectABICalls(ASTNode const* _node)
{
struct ABIFunctions: public ASTConstVisitor
{
ABIFunctions(ASTNode const* _node) { _node->accept(*this); }
void endVisit(FunctionCall const& _funCall)
{
if (*_funCall.annotation().kind == FunctionCallKind::FunctionCall)
switch (dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type).kind())
{
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature:
case FunctionType::Kind::ABIDecode:
abiCalls.insert(&_funCall);
break;
default: {}
}
}
std::set<FunctionCall const*, ASTCompareByID<FunctionCall>> abiCalls;
};
return ABIFunctions(_node).abiCalls;
}
std::set<SourceUnit const*, ASTNode::CompareByID> SMTEncoder::sourceDependencies(SourceUnit const& _source)
{
std::set<SourceUnit const*, ASTNode::CompareByID> sources;
sources.insert(&_source);
for (auto const& source: _source.referencedSourceUnits(true))
sources.insert(source);
return sources;
}
void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall, ContractDefinition const* _contextContract)
{
auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), _contextContract);
if (!funDef)
return;
auto const& returnParams = funDef->returnParameters();
for (auto param: returnParams)
createVariable(*param);
auto returnValues = applyMap(returnParams, [this](auto const& param) -> std::optional<smtutil::Expression> {
solAssert(param && m_context.knownVariable(*param), "");
return currentValue(*param);
});
defineExpr(_funCall, returnValues);
}
std::vector<smtutil::Expression> SMTEncoder::symbolicArguments(FunctionCall const& _funCall, ContractDefinition const* _contextContract)
{
auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), _contextContract);
solAssert(funDef, "");
std::vector<smtutil::Expression> args;
Expression const* calledExpr = &_funCall.expression();
auto funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
solAssert(funType, "");
std::vector<ASTPointer<Expression const>> arguments = _funCall.sortedArguments();
auto functionParams = funDef->parameters();
unsigned firstParam = 0;
if (funType->hasBoundFirstArgument())
{
calledExpr = innermostTuple(*calledExpr);
auto const& attachedFunction = dynamic_cast<MemberAccess const*>(calledExpr);
solAssert(attachedFunction, "");
args.push_back(expr(attachedFunction->expression(), functionParams.front()->type()));
firstParam = 1;
}
solAssert((arguments.size() + firstParam) == functionParams.size(), "");
for (unsigned i = 0; i < arguments.size(); ++i)
args.push_back(expr(*arguments.at(i), functionParams.at(i + firstParam)->type()));
return args;
}
smtutil::Expression SMTEncoder::constantExpr(Expression const& _expr, VariableDeclaration const& _var)
{
if (RationalNumberType const* rationalType = isConstant(_expr))
{
if (rationalType->isNegative())
return smtutil::Expression(u2s(rationalType->literalValue(nullptr)));
else
return smtutil::Expression(rationalType->literalValue(nullptr));
}
else
{
solAssert(_var.value(), "");
_var.value()->accept(*this);
return expr(*_var.value(), _expr.annotation().type);
}
solAssert(false, "");
}
void SMTEncoder::collectFreeFunctions(std::set<SourceUnit const*, ASTNode::CompareByID> const& _sources)
{
for (auto source: _sources)
for (auto node: source->nodes())
if (auto function = dynamic_cast<FunctionDefinition const*>(node.get()))
m_freeFunctions.insert(function);
else if (
auto contract = dynamic_cast<ContractDefinition const*>(node.get());
contract && contract->isLibrary()
)
for (auto function: contract->definedFunctions())
// We need to add public library functions too because they can be called
// internally by internal library functions that are considered free functions.
m_freeFunctions.insert(function);
}
void SMTEncoder::createFreeConstants(std::set<SourceUnit const*, ASTNode::CompareByID> const& _sources)
{
for (auto source: _sources)
for (auto node: source->nodes())
if (auto var = dynamic_cast<VariableDeclaration const*>(node.get()))
createVariable(*var);
else if (
auto contract = dynamic_cast<ContractDefinition const*>(node.get());
contract && contract->isLibrary()
)
for (auto var: contract->stateVariables())
{
solAssert(var->isConstant(), "");
createVariable(*var);
}
}
smt::SymbolicState& SMTEncoder::state()
{
return m_context.state();
}