solidity/libsolidity/formal/SMTEncoder.cpp
2020-12-21 13:52:28 +01:00

2846 lines
89 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 <libsmtutil/SMTPortfolio.h>
#include <libsmtutil/Helpers.h>
#include <boost/range/adaptors.hpp>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;
SMTEncoder::SMTEncoder(smt::EncodingContext& _context):
m_errorReporter(m_smtErrors),
m_context(_context)
{
}
bool SMTEncoder::analyze(SourceUnit const& _source)
{
set<SourceUnit const*, smt::EncodingContext::IdCompare> sources;
sources.insert(&_source);
for (auto const& source: _source.referencedSourceUnits(true))
sources.insert(source);
bool analysis = true;
for (auto source: sources)
for (auto node: source->nodes())
if (auto function = dynamic_pointer_cast<FunctionDefinition>(node))
{
m_errorReporter.warning(
6660_error,
function->location(),
"Model checker analysis was not possible because file level functions are not supported."
);
analysis = false;
}
else if (auto var = dynamic_pointer_cast<VariableDeclaration>(node))
{
m_errorReporter.warning(
8195_error,
var->location(),
"Model checker analysis was not possible because file level constants are not supported."
);
analysis = false;
}
if (!analysis)
return false;
m_context.state().prepareForSourceUnit(_source);
return true;
}
bool SMTEncoder::visit(ContractDefinition const& _contract)
{
solAssert(m_currentContract, "");
for (auto const& node: _contract.subNodes())
if (
!dynamic_pointer_cast<FunctionDefinition>(node) &&
!dynamic_pointer_cast<VariableDeclaration>(node)
)
node->accept(*this);
vector<FunctionDefinition const*> resolvedFunctions = _contract.definedFunctions();
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();
}
}
// Check for function overrides.
for (auto const& baseFunction: base->definedFunctions())
{
if (baseFunction->isConstructor())
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.push_back(baseFunction);
}
}
// 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: resolvedFunctions)
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();
}
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)
{
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))
inlineModifierInvocation(modifierInvocation.get(), modifier);
else
solAssert(false, "");
}
--m_modifierDepthStack.back();
}
void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation, CallableDeclaration const* _definition)
{
solAssert(_invocation, "");
_invocation->accept(*this);
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&)
{
popCallStack();
solAssert(m_modifierDepthStack.back() == -1, "");
m_modifierDepthStack.pop_back();
if (m_callStack.empty())
m_context.popSolver();
}
bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{
m_errorReporter.warning(
7737_error,
_inlineAsm.location(),
"Assertion checker does not support inline assembly."
);
return false;
}
bool SMTEncoder::visit(TryCatchClause const& _clause)
{
if (auto params = _clause.parameters())
for (auto const& var: params->parameters())
createVariable(*var);
m_errorReporter.warning(
7645_error,
_clause.location(),
"Assertion checker does not support try/catch clauses."
);
return false;
}
void SMTEncoder::pushInlineFrame(CallableDeclaration const&)
{
pushPathCondition(currentPathConditions());
}
void SMTEncoder::popInlineFrame(CallableDeclaration const&)
{
popPathCondition();
}
void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
{
if (_varDecl.declarations().size() != 1)
{
if (auto init = _varDecl.initialValue())
{
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*init));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
auto tupleType = dynamic_cast<TupleType const*>(init->annotation().type);
solAssert(tupleType, "");
solAssert(tupleType->components().size() == symbTuple->components().size(), "");
auto const& components = tupleType->components();
auto const& declarations = _varDecl.declarations();
solAssert(symbComponents.size() == declarations.size(), "");
for (unsigned i = 0; i < declarations.size(); ++i)
if (
components.at(i) &&
declarations.at(i) &&
m_context.knownVariable(*declarations.at(i))
)
assignment(*declarations.at(i), symbTuple->component(i, components.at(i), declarations.at(i)->type()));
}
}
else
{
solAssert(m_context.knownVariable(*_varDecl.declarations().front()), "");
if (_varDecl.initialValue())
assignment(*_varDecl.declarations().front(), *_varDecl.initialValue());
}
}
bool SMTEncoder::visit(Assignment const& _assignment)
{
auto const& left = _assignment.leftHandSide();
auto const& right = _assignment.rightHandSide();
if (auto const* memberAccess = isEmptyPush(left))
{
right.accept(*this);
left.accept(*this);
auto const& memberExpr = memberAccess->expression();
auto& symbArray = dynamic_cast<smt::SymbolicArrayVariable&>(*m_context.expression(memberExpr));
smtutil::Expression oldElements = symbArray.elements();
smtutil::Expression length = symbArray.length();
symbArray.increaseIndex();
m_context.addAssertion(symbArray.elements() == smtutil::Expression::store(
oldElements,
length - 1,
expr(right)
));
m_context.addAssertion(symbArray.length() == length);
arrayPushPopAssign(memberExpr, symbArray.currentValue());
defineExpr(_assignment, expr(left));
return false;
}
return true;
}
void SMTEncoder::endVisit(Assignment const& _assignment)
{
createExpr(_assignment);
Token op = _assignment.assignmentOperator();
solAssert(TokenTraits::isAssignmentOp(op), "");
if (isEmptyPush(_assignment.leftHandSide()))
return;
if (!smt::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)
{
createExpr(_tuple);
if (_tuple.isInlineArray())
{
// Add constraints for the length and values as it is known.
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_tuple));
solAssert(symbArray, "");
addArrayLiteralAssertions(*symbArray, applyMap(_tuple.components(), [&](auto const& c) { return expr(*c); }));
}
else if (_tuple.components().size() == 1)
defineExpr(_tuple, expr(*_tuple.components().front()));
else
{
solAssert(_tuple.annotation().type->category() == Type::Category::Tuple, "");
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
auto const* tuple = dynamic_cast<TupleExpression const*>(innermostTuple(_tuple));
solAssert(tuple, "");
auto const& tupleComponents = tuple->components();
solAssert(symbComponents.size() == tupleComponents.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto tComponent = tupleComponents.at(i);
if (tComponent)
{
if (auto varDecl = identifierToVariable(*tComponent))
m_context.addAssertion(symbTuple->component(i) == currentValue(*varDecl));
else
{
if (!m_context.knownExpression(*tComponent))
createExpr(*tComponent);
m_context.addAssertion(symbTuple->component(i) == expr(*tComponent));
}
}
}
}
}
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()))
return bitwiseNotOperation(_op);
createExpr(_op);
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 computeNewValue = [&](auto currentValue) {
return arithmeticOperation(
_op.getOperator() == Token::Inc ? Token::Add : Token::Sub,
currentValue,
smtutil::Expression(size_t(1)),
_op.annotation().type,
_op
).first;
};
if (auto identifier = dynamic_cast<Identifier const*>(subExpr))
{
auto decl = identifierToVariable(*identifier);
solAssert(decl, "");
auto innerValue = currentValue(*decl);
auto newValue = computeNewValue(innerValue);
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
assignment(*decl, newValue);
}
else if (
dynamic_cast<IndexAccess const*>(subExpr) ||
dynamic_cast<MemberAccess const*>(subExpr)
)
{
auto innerValue = expr(*subExpr);
auto newValue = computeNewValue(innerValue);
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
indexOrMemberAssignment(*subExpr, newValue);
}
else if (isEmptyPush(*subExpr))
{
auto innerValue = expr(*subExpr);
auto newValue = computeNewValue(innerValue);
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
arrayPushPopAssign(*subExpr, newValue);
}
else
solAssert(false, "");
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()))
{
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()))
return;
createExpr(_op);
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 touchedVars = touchedVariables(_op.trueExpression());
auto indicesEndFalse = visitBranch(&_op.falseExpression(), !expr(_op.condition())).first;
touchedVars += touchedVariables(_op.falseExpression());
mergeVariables(touchedVars, 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;
}
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 (isPublicGetter(_funCall.expression()))
visitPublicGetter(_funCall);
break;
case FunctionType::Kind::ABIDecode:
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeWithSignature:
visitABIFunction(_funCall);
break;
case FunctionType::Kind::Internal:
case FunctionType::Kind::BareStaticCall:
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, m_context.state().blockhash(expr(*_funCall.arguments().at(0))));
break;
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
visitAddMulMod(_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 = m_context.state().balance();
setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context);
m_context.state().transfer(m_context.state().thisAddress(), expr(address), expr(*value));
break;
}
case FunctionType::Kind::ArrayPush:
case FunctionType::Kind::ByteArrayPush:
arrayPush(_funCall);
break;
case FunctionType::Kind::ArrayPop:
arrayPop(_funCall);
break;
case FunctionType::Kind::Event:
// This can be safely ignored.
break;
case FunctionType::Kind::ObjectCreation:
visitObjectCreation(_funCall);
return;
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::Creation:
default:
m_errorReporter.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);
}
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);
}
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 = m_context.state().abiFunction(&_funCall);
auto const& [name, inTypes, outTypes] = m_context.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();
vector<smtutil::Expression> symbArgs;
solAssert(inTypes.size() == argsActualLength, "");
for (unsigned i = 0; i < argsActualLength; ++i)
if (args.at(i))
symbArgs.emplace_back(expr(*args.at(i), inTypes.at(i)));
optional<smtutil::Expression> arg;
if (inTypes.size() == 1)
arg = expr(*args.at(0));
else
{
auto inputSort = dynamic_cast<smtutil::ArraySort&>(*symbFunction.sort).domain;
arg = smtutil::Expression::tuple_constructor(
smtutil::Expression(make_shared<smtutil::SortSort>(inputSort), ""),
symbArgs
);
}
auto out = smtutil::Expression::select(symbFunction, *arg);
if (outTypes.size() == 1)
defineExpr(_funCall, out);
else
{
auto symbTuple = 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));
optional<smtutil::Expression> result;
if (kind == FunctionType::Kind::KECCAK256)
result = smtutil::Expression::select(m_context.state().cryptoFunction("keccak256"), arg0);
else if (kind == FunctionType::Kind::SHA256)
result = smtutil::Expression::select(m_context.state().cryptoFunction("sha256"), arg0);
else if (kind == FunctionType::Kind::RIPEMD160)
result = smtutil::Expression::select(m_context.state().cryptoFunction("ripemd160"), arg0);
else if (kind == FunctionType::Kind::ECRecover)
{
auto e = m_context.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(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)
{
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::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 = 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())
{
if (RationalNumberType const* rationalType = isConstant(_identifier))
{
if (rationalType->isNegative())
defineExpr(_identifier, smtutil::Expression(u2s(rationalType->literalValue(nullptr))));
else
defineExpr(_identifier, smtutil::Expression(rationalType->literalValue(nullptr)));
}
else
{
solAssert(decl->value(), "");
decl->value()->accept(*this);
defineExpr(_identifier, expr(*decl->value()));
}
}
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, m_context.state().thisAddress());
m_uninterpretedTerms.insert(&_identifier);
}
// Ignore type identifiers
else if (dynamic_cast<TypeType 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(TypePointer _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).isByteArray();
// default
return true;
}
vector<string> structGetterReturnedMembers(StructType const& _structType)
{
vector<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)
{
MemberAccess const& access = dynamic_cast<MemberAccess const&>(_funCall.expression());
auto var = dynamic_cast<VariableDeclaration const*>(access.annotation().referencedDeclaration);
solAssert(var, "");
solAssert(m_context.knownExpression(_funCall), "");
auto paramExpectedTypes = FunctionType(*var).parameterTypes();
auto actualArguments = _funCall.arguments();
solAssert(actualArguments.size() == paramExpectedTypes.size(), "");
vector<smtutil::Expression> symbArguments;
for (unsigned i = 0; i < paramExpectedTypes.size(); ++i)
symbArguments.push_back(expr(*actualArguments[i], paramExpectedTypes[i]));
TypePointer type = var->type();
if (
type->isValueType() ||
(type->category() == Type::Category::Array && dynamic_cast<ArrayType const&>(*type).isByteArray())
)
{
solAssert(symbArguments.empty(), "");
defineExpr(_funCall, currentValue(*var));
return;
}
switch (type->category())
{
case Type::Category::Array:
case Type::Category::Mapping:
{
// 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.
smtutil::Expression exprVal = currentValue(*var);
for (auto const& arg: symbArguments)
{
exprVal = smtutil::Expression::select(
smtutil::Expression::tuple_get(exprVal, 0),
arg
);
}
defineExpr(_funCall, exprVal);
break;
}
case Type::Category::Struct:
{
auto returnedMembers = structGetterReturnedMembers(dynamic_cast<StructType const&>(*type));
solAssert(!returnedMembers.empty(), "");
auto structVar = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.variable(*var));
solAssert(structVar, "");
auto returnedValues = applyMap(returnedMembers, [&](string const& memberName) { return structVar->member(memberName); });
if (returnedValues.size() == 1)
defineExpr(_funCall, returnedValues.front());
else
{
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_funCall));
solAssert(symbTuple, "");
symbTuple->increaseIndex(); // Increasing the index explicitly since we cannot use defineExpr in this case.
auto const& symbComponents = symbTuple->components();
solAssert(symbComponents.size() == returnedValues.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
m_context.addAssertion(symbTuple->component(i) == returnedValues.at(i));
}
break;
}
default: {} // Unsupported cases, do nothing and the getter will be abstracted.
}
}
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;
}
// 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);
optional<smtutil::Expression> symbMin;
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), "");
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 (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));
structSymbolicVar.assignAllMembers(applyMap(_funCall.sortedArguments(), [this](auto const& arg) { return expr(*arg); }));
}
}
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 = 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,
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::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 = 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)
{
auto const& accessType = _memberAccess.annotation().type;
if (accessType->category() == Type::Category::Function)
return true;
createExpr(_memberAccess);
auto const& exprType = _memberAccess.expression().annotation().type;
solAssert(exprType, "");
if (exprType->category() == Type::Category::Magic)
{
if (auto const* identifier = dynamic_cast<Identifier const*>(&_memberAccess.expression()))
{
auto const& name = identifier->name();
solAssert(name == "block" || name == "msg" || name == "tx", "");
defineExpr(_memberAccess, m_context.state().txMember(name + "." + _memberAccess.memberName()));
}
else if (auto magicType = dynamic_cast<MagicType const*>(exprType); magicType->kind() == MagicType::Kind::MetaType)
{
auto const& memberName = _memberAccess.memberName();
if (memberName == "min" || memberName == "max")
{
IntegerType const& integerType = dynamic_cast<IntegerType const&>(*magicType->typeArgument());
defineExpr(_memberAccess, memberName == "min" ? integerType.minValue() : integerType.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_errorReporter.warning(
7507_error,
_memberAccess.location(),
"Assertion checker does not yet support this expression."
);
}
else
solUnimplementedAssert(false, "");
return false;
}
else if (smt::isNonRecursiveStruct(*exprType))
{
_memberAccess.expression().accept(*this);
auto const& symbStruct = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(_memberAccess.expression()));
defineExpr(_memberAccess, symbStruct->member(_memberAccess.memberName()));
return false;
}
else if (exprType->category() == Type::Category::TypeType)
{
auto const* decl = expressionToDeclaration(_memberAccess.expression());
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))
{
defineExpr(_memberAccess, currentValue(*var));
return false;
}
}
}
else if (exprType->category() == Type::Category::Address)
{
_memberAccess.expression().accept(*this);
if (_memberAccess.memberName() == "balance")
{
defineExpr(_memberAccess, m_context.state().balance(expr(_memberAccess.expression())));
setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context);
m_uninterpretedTerms.insert(&_memberAccess);
return false;
}
}
else if (exprType->category() == Type::Category::Array)
{
_memberAccess.expression().accept(*this);
if (_memberAccess.memberName() == "length")
{
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_memberAccess.expression()));
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
m_errorReporter.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;
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;
}
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);
}
else
{
solAssert(m_context.knownExpression(_indexAccess.baseExpression()), "");
array = m_context.expression(_indexAccess.baseExpression());
}
auto arrayVar = dynamic_pointer_cast<smt::SymbolicArrayVariable>(array);
solAssert(arrayVar, "");
TypePointer 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)
{
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_expr))
{
if (dynamic_cast<ContractDefinition const*>(expressionToDeclaration(memberAccess->expression())))
{
if (auto const* var = dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration))
{
if (var->hasReferenceOrMappingType())
resetReferences(*var);
assignment(*var, _rightHandSide);
defineExpr(_expr, currentValue(*var));
return;
}
}
}
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);
TypePointer baseType = base.annotation().type;
auto indexExpr = expr(*indexAccess->indexExpression(), keyType(baseType));
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(base));
solAssert(symbArray, "");
toStore = smtutil::Expression::tuple_constructor(
smtutil::Expression(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_errorReporter.warning(
4375_error,
memberAccess->location(),
"Assertion checker does not support recursive structs."
);
return;
}
auto symbStruct = 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 = 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();
smtutil::Expression element = arguments.empty() ?
smt::zeroValue(_funCall.annotation().type) :
expr(*arguments.front());
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, smtutil::Expression::select(symbArray->elements(), oldLength));
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
void SMTEncoder::arrayPop(FunctionCall const& _funCall)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
solAssert(memberAccess, "");
auto symbArray = 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);
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression const& _array)
{
Expression const* expr = innermostTuple(_expr);
if (auto const* id = dynamic_cast<Identifier const*>(expr))
{
auto varDecl = identifierToVariable(*id);
solAssert(varDecl, "");
if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl);
m_context.addAssertion(m_context.newValue(*varDecl) == _array);
m_context.expression(*id)->increaseIndex();
defineExpr(*id,currentValue(*varDecl));
}
else if (
dynamic_cast<IndexAccess const*>(expr) ||
dynamic_cast<MemberAccess const*>(expr)
)
indexOrMemberAssignment(_expr, _array);
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr))
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
// Push cannot occur on an expression that is itself a ByteArrayPush, i.e., bytes.push().push() is not possible.
solAssert(funType.kind() != FunctionType::Kind::ByteArrayPush, "");
if (funType.kind() == FunctionType::Kind::ArrayPush)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression());
solAssert(memberAccess, "");
auto symbArray = 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,
_array
);
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == store);
m_context.addAssertion(symbArray->length() == oldLength);
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
}
else
solAssert(false, "");
}
void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex)
{
if (!m_context.knownGlobalSymbol(_name))
{
bool abstract = m_context.createGlobalSymbol(_name, _expr);
if (abstract)
m_errorReporter.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 (smt::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_errorReporter.warning(
5188_error,
_op.location(),
"Assertion checker does not yet implement this operator."
);
}
}
pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
Token _op,
smtutil::Expression const& _left,
smtutil::Expression const& _right,
TypePointer const& _commonType,
Expression const& _operation
)
{
static 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 (_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 == 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;
string suffix = to_string(_operation.id()) + "_" + 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,
TypePointer const& _commonType
)
{
static 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);
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 const& commonType = _op.annotation().commonType;
solAssert(commonType, "");
if (smt::isSupportedType(*commonType))
{
smtutil::Expression left(expr(_op.leftExpression(), commonType));
smtutil::Expression right(expr(_op.rightExpression(), commonType));
Token op = _op.getOperator();
shared_ptr<smtutil::Expression> value;
if (smt::isNumber(*commonType))
{
value = 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 = 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_errorReporter.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(touchedVariables(_op.rightExpression()), !expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond);
defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
}
else
{
auto indicesAfterSecond = visitBranch(&_op.rightExpression(), !expr(_op.leftExpression())).first;
mergeVariables(touchedVariables(_op.rightExpression()), 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));
}
pair<smtutil::Expression, smtutil::Expression> SMTEncoder::divModWithSlacks(
smtutil::Expression _left,
smtutil::Expression _right,
IntegerType const& _type
)
{
IntegerType const* intType = &_type;
string suffix = "div_mod_" + 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,
TypePointer const& _type
)
{
solAssert(
_left.annotation().type->category() != Type::Category::Tuple,
"Tuple assignments should be handled by tupleAssignment."
);
Expression const* left = innermostTuple(_left);
if (!smt::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))
assignment(*varDecl, _right);
else if (
dynamic_cast<IndexAccess const*>(left) ||
dynamic_cast<MemberAccess const*>(left)
)
indexOrMemberAssignment(*left, _right);
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 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 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::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)
{
TypePointer 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);
}
pair<SMTEncoder::VariableIndices, smtutil::Expression> SMTEncoder::visitBranch(
ASTNode const* _statement,
smtutil::Expression _condition
)
{
return visitBranch(_statement, &_condition);
}
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, 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;
}
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::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(TypePointer _type)
{
m_context.resetVariables([&](VariableDeclaration const& _var) {
return sameTypeOrSubtype(_var.type(), _type);
});
}
bool SMTEncoder::sameTypeOrSubtype(TypePointer _a, TypePointer _b)
{
TypePointer prefix = _a;
while (
prefix->category() == Type::Category::Mapping ||
prefix->category() == Type::Category::Array
)
{
if (*typeWithoutPointer(_b) == *typeWithoutPointer(prefix))
return true;
if (prefix->category() == Type::Category::Mapping)
{
auto mapPrefix = dynamic_cast<MappingType const*>(prefix);
solAssert(mapPrefix, "");
prefix = mapPrefix->valueType();
}
else
{
auto arrayPrefix = dynamic_cast<ArrayType const*>(prefix);
solAssert(arrayPrefix, "");
prefix = arrayPrefix->baseType();
}
}
return false;
}
TypePointer SMTEncoder::typeWithoutPointer(TypePointer const& _type)
{
if (auto refType = dynamic_cast<ReferenceType const*>(_type))
return TypeProvider::withLocationIfReference(refType->location(), _type);
return _type;
}
void SMTEncoder::mergeVariables(set<VariableDeclaration const*> const& _variables, smtutil::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse)
{
auto cmp = [] (VariableDeclaration const* var1, VariableDeclaration const* var2) {
return var1->id() < var2->id();
};
set<VariableDeclaration const*, decltype(cmp)> sortedVars(begin(_variables), end(_variables), cmp);
/// Knowledge about references is erased if a reference is assigned,
/// so those also need their SSA's merged.
/// This does not cause scope harm since the symbolic variables
/// are kept alive.
for (auto const& var: _indicesEndTrue)
{
solAssert(_indicesEndFalse.count(var.first), "");
if (
_indicesEndFalse.at(var.first) != var.second &&
!sortedVars.count(var.first)
)
sortedVars.insert(var.first);
}
for (auto const* decl: sortedVars)
{
solAssert(_indicesEndTrue.count(decl) && _indicesEndFalse.count(decl), "");
auto trueIndex = static_cast<unsigned>(_indicesEndTrue.at(decl));
auto falseIndex = static_cast<unsigned>(_indicesEndFalse.at(decl));
solAssert(trueIndex != falseIndex, "");
m_context.addAssertion(m_context.newValue(*decl) == smtutil::Expression::ite(
_condition,
valueAtIndex(*decl, trueIndex),
valueAtIndex(*decl, falseIndex))
);
}
}
smtutil::Expression SMTEncoder::currentValue(VariableDeclaration const& _decl)
{
solAssert(m_context.knownVariable(_decl), "");
return m_context.variable(_decl)->currentValue();
}
smtutil::Expression SMTEncoder::valueAtIndex(VariableDeclaration const& _decl, unsigned _index)
{
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_errorReporter.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, TypePointer _targetType)
{
if (!m_context.knownExpression(_e))
{
m_errorReporter.warning(6031_error, _e.location(), "Internal error: Expression undefined for SMT solver." );
createExpr(_e);
}
return m_context.expression(_e)->currentValue(_targetType);
}
void SMTEncoder::createExpr(Expression const& _e)
{
bool abstract = m_context.createExpression(_e);
if (abstract)
m_errorReporter.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)
{
createExpr(_e);
solAssert(_value.sort->kind != smtutil::Kind::Function, "Equality operator applied to type that is not fully supported");
m_context.addAssertion(expr(_e) == _value);
}
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(vector<CallStackEntry> const& _callStack)
{
SecondarySourceLocation callStackLocation;
solAssert(!_callStack.empty(), "");
callStackLocation.append("Callstack:", SourceLocation());
for (auto const& call: _callStack | boost::adaptors::reversed)
if (call.second)
callStackLocation.append("", call.second->location());
return callStackLocation;
}
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;
}
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(static_cast<unsigned>(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();
}
m_context.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;
}
TypePointer SMTEncoder::keyType(TypePointer _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;
}
set<VariableDeclaration const*> SMTEncoder::touchedVariables(ASTNode const& _node)
{
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;
}
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 || funType.kind() == FunctionType::Kind::ByteArrayPush)
return &dynamic_cast<MemberAccess const&>(funCall->expression());
}
return nullptr;
}
bool SMTEncoder::isPublicGetter(Expression const& _expr) {
if (!isTrustedExternalCall(&_expr))
return false;
auto varDecl = dynamic_cast<VariableDeclaration const*>(
dynamic_cast<MemberAccess const&>(_expr).annotation().referencedDeclaration
);
return varDecl != nullptr;
}
bool SMTEncoder::isTrustedExternalCall(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)
;
}
string SMTEncoder::extraComment()
{
string extra;
if (m_arrayAssignmentHappened)
extra +=
"\nNote that array aliasing is not supported,"
" therefore all mapping information is erased after"
" a mapping local variable/parameter is assigned.\n"
"You can re-introduce information using require().";
return extra;
}
FunctionDefinition const* SMTEncoder::functionCallToDefinition(FunctionCall const& _funCall)
{
if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall)
return nullptr;
FunctionDefinition const* funDef = nullptr;
Expression const* calledExpr = &_funCall.expression();
if (TupleExpression const* fun = dynamic_cast<TupleExpression const*>(calledExpr))
{
solAssert(fun->components().size() == 1, "");
calledExpr = innermostTuple(*calledExpr);
}
if (Identifier const* fun = dynamic_cast<Identifier const*>(calledExpr))
funDef = dynamic_cast<FunctionDefinition const*>(fun->annotation().referencedDeclaration);
else if (MemberAccess const* fun = dynamic_cast<MemberAccess const*>(calledExpr))
funDef = dynamic_cast<FunctionDefinition const*>(fun->annotation().referencedDeclaration);
return funDef;
}
vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
return fold(
_contract.annotation().linearizedBaseContracts,
vector<VariableDeclaration const*>{},
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
);
}
vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
{
return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
}
vector<VariableDeclaration const*> SMTEncoder::localVariablesIncludingModifiers(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
return _function.localVariables() + modifiersVariables(_function, _contract);
}
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); }
vector<VariableDeclaration const*> vars;
};
vector<VariableDeclaration const*> vars;
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;
}
SourceUnit const* SMTEncoder::sourceUnitContaining(Scopable const& _scopable)
{
for (auto const* s = &_scopable; s; s = dynamic_cast<Scopable const*>(s->scope()))
if (auto const* source = dynamic_cast<SourceUnit const*>(s->scope()))
return source;
solAssert(false, "");
}
map<ContractDefinition const*, vector<ASTPointer<frontend::Expression>>> SMTEncoder::baseArguments(ContractDefinition const& _contract)
{
map<ContractDefinition const*, 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;
}
set<FunctionCall const*> 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::ABIEncodeWithSignature:
case FunctionType::Kind::ABIDecode:
abiCalls.insert(&_funCall);
break;
default: {}
}
}
set<FunctionCall const*> abiCalls;
};
return ABIFunctions(_node).abiCalls;
}
void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
{
FunctionDefinition const* funDef = functionCallToDefinition(_funCall);
if (!funDef)
return;
auto const& returnParams = funDef->returnParameters();
for (auto param: returnParams)
createVariable(*param);
if (returnParams.size() > 1)
{
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_funCall));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
solAssert(symbComponents.size() == returnParams.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto param = returnParams.at(i);
solAssert(param, "");
solAssert(m_context.knownVariable(*param), "");
m_context.addAssertion(symbTuple->component(i) == currentValue(*param));
}
}
else if (returnParams.size() == 1)
defineExpr(_funCall, currentValue(*returnParams.front()));
}
vector<smtutil::Expression> SMTEncoder::symbolicArguments(FunctionCall const& _funCall)
{
auto const* function = functionCallToDefinition(_funCall);
solAssert(function, "");
vector<smtutil::Expression> args;
Expression const* calledExpr = &_funCall.expression();
auto const& funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
solAssert(funType, "");
vector<ASTPointer<Expression const>> arguments = _funCall.sortedArguments();
auto const& functionParams = function->parameters();
unsigned firstParam = 0;
if (funType->bound())
{
calledExpr = innermostTuple(*calledExpr);
auto const& boundFunction = dynamic_cast<MemberAccess const*>(calledExpr);
solAssert(boundFunction, "");
args.push_back(expr(boundFunction->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;
}