[SMTChecker] Support structs

This commit is contained in:
Leonardo Alt 2020-08-25 16:58:09 +02:00
parent ff6415aa9e
commit e61b731647
6 changed files with 190 additions and 66 deletions

View File

@ -492,7 +492,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
auto innerValue = expr(*subExpr); auto innerValue = expr(*subExpr);
auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
arrayIndexAssignment(*subExpr, newValue); indexOrMemberAssignment(_op.subExpression(), newValue);
} }
else else
m_errorReporter.warning( m_errorReporter.warning(
@ -521,8 +521,11 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
auto const& symbVar = m_context.expression(*subExpr); auto const& symbVar = m_context.expression(*subExpr);
symbVar->increaseIndex(); symbVar->increaseIndex();
m_context.setZeroValue(*symbVar); m_context.setZeroValue(*symbVar);
if (dynamic_cast<IndexAccess const*>(subExpr)) if (
arrayIndexAssignment(*subExpr, symbVar->currentValue()); dynamic_cast<IndexAccess const*>(&_op.subExpression()) ||
dynamic_cast<MemberAccess const*>(&_op.subExpression())
)
indexOrMemberAssignment(_op.subExpression(), symbVar->currentValue());
else else
m_errorReporter.warning( m_errorReporter.warning(
2683_error, 2683_error,
@ -893,6 +896,13 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess);
return false; return false;
} }
else if (exprType->category() == Type::Category::Struct)
{
_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) else if (exprType->category() == Type::Category::TypeType)
{ {
if (identifier && dynamic_cast<EnumDefinition const*>(identifier->annotation().referencedDeclaration)) if (identifier && dynamic_cast<EnumDefinition const*>(identifier->annotation().referencedDeclaration))
@ -964,19 +974,10 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
solAssert(varDecl, ""); solAssert(varDecl, "");
array = m_context.variable(*varDecl); array = m_context.variable(*varDecl);
} }
else if (auto const* innerAccess = dynamic_cast<IndexAccess const*>(&_indexAccess.baseExpression()))
{
solAssert(m_context.knownExpression(*innerAccess), "");
array = m_context.expression(*innerAccess);
}
else else
{ {
m_errorReporter.warning( solAssert(m_context.knownExpression(_indexAccess.baseExpression()), "");
9118_error, array = m_context.expression(_indexAccess.baseExpression());
_indexAccess.location(),
"Assertion checker does not yet implement this expression."
);
return;
} }
auto arrayVar = dynamic_pointer_cast<smt::SymbolicArrayVariable>(array); auto arrayVar = dynamic_pointer_cast<smt::SymbolicArrayVariable>(array);
@ -1008,14 +1009,44 @@ void SMTEncoder::arrayAssignment()
m_arrayAssignmentHappened = true; m_arrayAssignmentHappened = true;
} }
void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide) void SMTEncoder::indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide)
{ {
auto toStore = _rightHandSide; auto toStore = _rightHandSide;
auto indexAccess = dynamic_cast<IndexAccess const*>(&_expr); auto const* lastExpr = &_expr;
solAssert(indexAccess, "");
while (true) while (true)
{ {
if (auto const& id = dynamic_cast<Identifier const*>(&indexAccess->baseExpression())) if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(lastExpr))
{
auto const& base = indexAccess->baseExpression();
if (dynamic_cast<Identifier const*>(&base))
base.accept(*this);
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(base));
solAssert(symbArray, "");
auto baseType = symbArray->type();
toStore = smtutil::Expression::tuple_constructor(
smtutil::Expression(make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
{smtutil::Expression::store(symbArray->elements(), expr(*indexAccess->indexExpression()), toStore), symbArray->length()}
);
m_context.expression(*indexAccess)->increaseIndex();
defineExpr(*indexAccess, smtutil::Expression::select(
symbArray->elements(),
expr(*indexAccess->indexExpression())
));
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);
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); auto varDecl = identifierToVariable(*id);
solAssert(varDecl, ""); solAssert(varDecl, "");
@ -1023,36 +1054,11 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smtutil::Expressi
if (varDecl->hasReferenceOrMappingType()) if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl); resetReferences(*varDecl);
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.variable(*varDecl)); m_context.addAssertion(m_context.newValue(*varDecl) == toStore);
smtutil::Expression store = smtutil::Expression::store( m_context.expression(*id)->increaseIndex();
symbArray->elements(), defineExpr(*id,currentValue(*varDecl));
expr(*indexAccess->indexExpression()),
toStore
);
auto oldLength = symbArray->length();
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == store);
m_context.addAssertion(symbArray->length() == oldLength);
// Update the SMT select value after the assignment,
// necessary for sound models.
defineExpr(*indexAccess, smtutil::Expression::select(
symbArray->elements(),
expr(*indexAccess->indexExpression())
));
break; break;
} }
else if (auto base = dynamic_cast<IndexAccess const*>(&indexAccess->baseExpression()))
{
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*base));
solAssert(symbArray, "");
auto baseType = base->annotation().type;
toStore = smtutil::Expression::tuple_constructor(
smtutil::Expression(make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
{smtutil::Expression::store(symbArray->elements(), expr(*indexAccess->indexExpression()), toStore), symbArray->length()}
);
indexAccess = base;
}
else else
{ {
m_errorReporter.warning( m_errorReporter.warning(
@ -1131,9 +1137,14 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
if (varDecl->hasReferenceOrMappingType()) if (varDecl->hasReferenceOrMappingType())
resetReferences(*varDecl); resetReferences(*varDecl);
m_context.addAssertion(m_context.newValue(*varDecl) == _array); m_context.addAssertion(m_context.newValue(*varDecl) == _array);
m_context.expression(*id)->increaseIndex();
defineExpr(*id,currentValue(*varDecl));
} }
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(expr)) else if (
arrayIndexAssignment(*indexAccess, _array); dynamic_cast<IndexAccess const*>(expr) ||
dynamic_cast<MemberAccess const*>(expr)
)
indexOrMemberAssignment(_expr, _array);
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr)) else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr))
{ {
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type); FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
@ -1475,8 +1486,11 @@ void SMTEncoder::assignment(
} }
else if (auto varDecl = identifierToVariable(*left)) else if (auto varDecl = identifierToVariable(*left))
assignment(*varDecl, _right); assignment(*varDecl, _right);
else if (dynamic_cast<IndexAccess const*>(left)) else if (
arrayIndexAssignment(*left, _right); dynamic_cast<IndexAccess const*>(left) ||
dynamic_cast<MemberAccess const*>(left)
)
indexOrMemberAssignment(*left, _right);
else else
m_errorReporter.warning( m_errorReporter.warning(
8182_error, 8182_error,

View File

@ -146,8 +146,8 @@ protected:
/// to variable of some SMT array type /// to variable of some SMT array type
/// while aliasing is not supported. /// while aliasing is not supported.
void arrayAssignment(); void arrayAssignment();
/// Handles assignment to SMT array index. /// Handles assignments to index or member access.
void arrayIndexAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide); void indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide);
void arrayPush(FunctionCall const& _funCall); void arrayPush(FunctionCall const& _funCall);
void arrayPop(FunctionCall const& _funCall); void arrayPop(FunctionCall const& _funCall);

View File

@ -25,6 +25,7 @@
#include <vector> #include <vector>
using namespace std; using namespace std;
using namespace solidity::util;
using namespace solidity::smtutil; using namespace solidity::smtutil;
namespace solidity::frontend::smt namespace solidity::frontend::smt
@ -130,18 +131,31 @@ SortPointer smtSort(frontend::Type const& _type)
} }
case Kind::Tuple: case Kind::Tuple:
{ {
auto tupleType = dynamic_cast<frontend::TupleType const*>(&_type);
solAssert(tupleType, "");
vector<string> members; vector<string> members;
auto const& tupleName = _type.identifier(); auto const& tupleName = _type.toString(true);
vector<SortPointer> sorts;
if (auto const* tupleType = dynamic_cast<frontend::TupleType const*>(&_type))
{
auto const& components = tupleType->components(); auto const& components = tupleType->components();
for (unsigned i = 0; i < components.size(); ++i) for (unsigned i = 0; i < components.size(); ++i)
members.emplace_back(tupleName + "_accessor_" + to_string(i)); members.emplace_back(tupleName + "_accessor_" + to_string(i));
return make_shared<TupleSort>( sorts = smtSortAbstractFunction(tupleType->components());
tupleName, }
members, else if (auto const* structType = dynamic_cast<frontend::StructType const*>(&_type))
smtSortAbstractFunction(tupleType->components()) {
); auto const& structMembers = structType->structDefinition().members();
for (auto member: structMembers)
members.emplace_back(tupleName + "_accessor_" + member->name());
sorts = smtSortAbstractFunction(applyMap(
structMembers,
[](auto var) { return var->type(); }
));
}
else
solAssert(false, "");
return make_shared<TupleSort>(tupleName, members, sorts);
} }
default: default:
// Abstract case. // Abstract case.
@ -185,7 +199,7 @@ Kind smtKind(frontend::Type::Category _category)
return Kind::Function; return Kind::Function;
else if (isMapping(_category) || isArray(_category)) else if (isMapping(_category) || isArray(_category))
return Kind::Array; return Kind::Array;
else if (isTuple(_category)) else if (isTuple(_category) || isStruct(_category))
return Kind::Tuple; return Kind::Tuple;
// Abstract case. // Abstract case.
return Kind::Int; return Kind::Int;
@ -197,7 +211,8 @@ bool isSupportedType(frontend::Type::Category _category)
isBool(_category) || isBool(_category) ||
isMapping(_category) || isMapping(_category) ||
isArray(_category) || isArray(_category) ||
isTuple(_category); isTuple(_category) ||
isStruct(_category);
} }
bool isSupportedTypeDeclaration(frontend::Type::Category _category) bool isSupportedTypeDeclaration(frontend::Type::Category _category)
@ -277,6 +292,8 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
auto stringType = TypeProvider::stringMemory(); auto stringType = TypeProvider::stringMemory();
var = make_shared<SymbolicArrayVariable>(stringType, type, _uniqueName, _context); var = make_shared<SymbolicArrayVariable>(stringType, type, _uniqueName, _context);
} }
else if (isStruct(_type.category()))
var = make_shared<SymbolicStructVariable>(type, _uniqueName, _context);
else else
solAssert(false, ""); solAssert(false, "");
return make_pair(abstract, var); return make_pair(abstract, var);
@ -370,6 +387,11 @@ bool isStringLiteral(frontend::Type::Category _category)
return _category == frontend::Type::Category::StringLiteral; return _category == frontend::Type::Category::StringLiteral;
} }
bool isStruct(frontend::Type::Category _category)
{
return _category == frontend::Type::Category::Struct;
}
smtutil::Expression minValue(frontend::IntegerType const& _type) smtutil::Expression minValue(frontend::IntegerType const& _type)
{ {
return smtutil::Expression(_type.minValue()); return smtutil::Expression(_type.minValue());
@ -421,11 +443,24 @@ smtutil::Expression zeroValue(frontend::TypePointer const& _type)
solAssert(zeroArray, ""); solAssert(zeroArray, "");
return smtutil::Expression::tuple_constructor( return smtutil::Expression::tuple_constructor(
smtutil::Expression(std::make_shared<SortSort>(smtSort(*_type)), _type->toString(true)), smtutil::Expression(std::make_shared<SortSort>(tupleSort), tupleSort->name),
vector<smtutil::Expression>{*zeroArray, length} vector<smtutil::Expression>{*zeroArray, length}
); );
} }
if (isStruct(_type->category()))
{
auto const* structType = dynamic_cast<StructType const*>(_type);
solAssert(structType, "");
auto structSort = dynamic_pointer_cast<TupleSort>(smtSort(*_type));
return smtutil::Expression::tuple_constructor(
smtutil::Expression(make_shared<SortSort>(structSort), structSort->name),
applyMap(
structType->structDefinition().members(),
[](auto var) { return zeroValue(var->type()); }
)
);
}
solAssert(false, ""); solAssert(false, "");
} }
// Unsupported types are abstracted as Int. // Unsupported types are abstracted as Int.

View File

@ -57,6 +57,7 @@ bool isMapping(frontend::Type::Category _category);
bool isArray(frontend::Type::Category _category); bool isArray(frontend::Type::Category _category);
bool isTuple(frontend::Type::Category _category); bool isTuple(frontend::Type::Category _category);
bool isStringLiteral(frontend::Type::Category _category); bool isStringLiteral(frontend::Type::Category _category);
bool isStruct(frontend::Type::Category _category);
/// Returns a new symbolic variable, according to _type. /// Returns a new symbolic variable, according to _type.
/// Also returns whether the type is abstract or not, /// Also returns whether the type is abstract or not,

View File

@ -21,8 +21,11 @@
#include <libsolidity/formal/SymbolicTypes.h> #include <libsolidity/formal/SymbolicTypes.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolutil/Algorithms.h>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util;
using namespace solidity::smtutil; using namespace solidity::smtutil;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace solidity::frontend::smt; using namespace solidity::frontend::smt;
@ -319,3 +322,47 @@ smtutil::Expression SymbolicArrayVariable::length()
{ {
return m_pair.component(1); return m_pair.component(1);
} }
SymbolicStructVariable::SymbolicStructVariable(
frontend::TypePointer _type,
string _uniqueName,
EncodingContext& _context
):
SymbolicVariable(_type, _type, move(_uniqueName), _context)
{
solAssert(isStruct(m_type->category()), "");
auto const* structType = dynamic_cast<StructType const*>(_type);
solAssert(structType, "");
auto const& members = structType->structDefinition().members();
for (unsigned i = 0; i < members.size(); ++i)
{
solAssert(members.at(i), "");
m_memberIndices.emplace(members.at(i)->name(), i);
}
}
smtutil::Expression SymbolicStructVariable::member(string const& _member)
{
return smtutil::Expression::tuple_get(currentValue(), m_memberIndices.at(_member));
}
smtutil::Expression SymbolicStructVariable::assignMember(string const& _member, smtutil::Expression const& _memberValue)
{
auto const* structType = dynamic_cast<StructType const*>(m_type);
solAssert(structType, "");
auto const& structDef = structType->structDefinition();
auto const& structMembers = structDef.members();
auto oldMembers = applyMap(
structMembers,
[&](auto _member) { return member(_member->name()); }
);
increaseIndex();
for (unsigned i = 0; i < structMembers.size(); ++i)
{
auto const& memberName = structMembers.at(i)->name();
auto newMember = memberName == _member ? _memberValue : oldMembers.at(i);
m_context.addAssertion(member(memberName) == newMember);
}
return currentValue();
}

View File

@ -23,6 +23,8 @@
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
#include <libsmtutil/SolverInterface.h> #include <libsmtutil/SolverInterface.h>
#include <map>
#include <memory> #include <memory>
namespace solidity::frontend::smt namespace solidity::frontend::smt
@ -265,4 +267,29 @@ private:
SymbolicTupleVariable m_pair; SymbolicTupleVariable m_pair;
}; };
/**
* Specialization of SymbolicVariable for Struct.
*/
class SymbolicStructVariable: public SymbolicVariable
{
public:
SymbolicStructVariable(
frontend::TypePointer _type,
std::string _uniqueName,
EncodingContext& _context
);
/// @returns the symbolic expression representing _member.
smtutil::Expression member(std::string const& _member);
/// @returns the symbolic expression representing this struct
/// with field _member updated.
smtutil::Expression assignMember(std::string const& _member, smtutil::Expression const& _memberValue);
private:
std::map<std::string, unsigned> m_memberIndices;
};
} }