Type checking for pure expressions.

This commit is contained in:
chriseth 2017-03-01 19:12:40 +01:00
parent bde913f088
commit f39763e91c
5 changed files with 88 additions and 28 deletions

View File

@ -467,27 +467,20 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// TypeChecker at the VariableDeclarationStatement level. // TypeChecker at the VariableDeclarationStatement level.
TypePointer varType = _variable.annotation().type; TypePointer varType = _variable.annotation().type;
solAssert(!!varType, "Failed to infer variable type."); solAssert(!!varType, "Failed to infer variable type.");
if (_variable.value())
expectType(*_variable.value(), *varType);
if (_variable.isConstant()) if (_variable.isConstant())
{ {
if (!dynamic_cast<ContractDefinition const*>(_variable.scope())) if (!_variable.isStateVariable())
typeError(_variable.location(), "Illegal use of \"constant\" specifier."); typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
if (!_variable.value()) if (!_variable.value())
typeError(_variable.location(), "Uninitialized \"constant\" variable."); typeError(_variable.location(), "Uninitialized \"constant\" variable.");
if (!varType->isValueType()) else if (!_variable.value()->annotation().isPure)
{ typeError(
bool constImplemented = false; _variable.value()->location(),
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get())) "Initial value for constant variable has to be compile-time constant."
constImplemented = arrayType->isByteArray(); );
if (!constImplemented)
typeError(
_variable.location(),
"Illegal use of \"constant\" specifier. \"constant\" "
"is not yet implemented for this type."
);
}
} }
if (_variable.value())
expectType(*_variable.value(), *varType);
if (!_variable.isStateVariable()) if (!_variable.isStateVariable())
{ {
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData)) if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
@ -928,6 +921,10 @@ bool TypeChecker::visit(Conditional const& _conditional)
} }
_conditional.annotation().type = commonType; _conditional.annotation().type = commonType;
_conditional.annotation().isPure =
_conditional.condition().annotation().isPure &&
_conditional.trueExpression().annotation().isPure &&
_conditional.falseExpression().annotation().isPure;
if (_conditional.annotation().lValueRequested) if (_conditional.annotation().lValueRequested)
typeError( typeError(
@ -1009,6 +1006,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
} }
else else
{ {
bool isPure = true;
TypePointer inlineArrayType; TypePointer inlineArrayType;
for (size_t i = 0; i < components.size(); ++i) for (size_t i = 0; i < components.size(); ++i)
{ {
@ -1031,10 +1029,13 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
else if (inlineArrayType) else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]); inlineArrayType = Type::commonType(inlineArrayType, types[i]);
} }
if (!components[i]->annotation().isPure)
isPure = false;
} }
else else
types.push_back(TypePointer()); types.push_back(TypePointer());
} }
_tuple.annotation().isPure = isPure;
if (_tuple.isInlineArray()) if (_tuple.isInlineArray())
{ {
if (!inlineArrayType) if (!inlineArrayType)
@ -1061,7 +1062,8 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
{ {
// Inc, Dec, Add, Sub, Not, BitNot, Delete // Inc, Dec, Add, Sub, Not, BitNot, Delete
Token::Value op = _operation.getOperator(); Token::Value op = _operation.getOperator();
if (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete) bool const modifying = (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete);
if (modifying)
requireLValue(_operation.subExpression()); requireLValue(_operation.subExpression());
else else
_operation.subExpression().accept(*this); _operation.subExpression().accept(*this);
@ -1079,6 +1081,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
t = subExprType; t = subExprType;
} }
_operation.annotation().type = t; _operation.annotation().type = t;
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
return false; return false;
} }
@ -1105,6 +1108,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
Token::isCompareOp(_operation.getOperator()) ? Token::isCompareOp(_operation.getOperator()) ?
make_shared<BoolType>() : make_shared<BoolType>() :
commonType; commonType;
_operation.annotation().isPure =
_operation.leftExpression().annotation().isPure &&
_operation.rightExpression().annotation().isPure;
if (_operation.getOperator() == Token::Exp) if (_operation.getOperator() == Token::Exp)
{ {
if ( if (
@ -1133,6 +1140,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names(); vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names();
bool isPure = true;
// We need to check arguments' type first as they will be needed for overload resolution. // We need to check arguments' type first as they will be needed for overload resolution.
shared_ptr<TypePointers> argumentTypes; shared_ptr<TypePointers> argumentTypes;
if (isPositionalCall) if (isPositionalCall)
@ -1140,6 +1149,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (ASTPointer<Expression const> const& argument: arguments) for (ASTPointer<Expression const> const& argument: arguments)
{ {
argument->accept(*this); argument->accept(*this);
if (!argument->annotation().isPure)
isPure = false;
// only store them for positional calls // only store them for positional calls
if (isPositionalCall) if (isPositionalCall)
argumentTypes->push_back(type(*argument)); argumentTypes->push_back(type(*argument));
@ -1177,6 +1188,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
typeError(_functionCall.location(), "Explicit type conversion not allowed."); typeError(_functionCall.location(), "Explicit type conversion not allowed.");
} }
_functionCall.annotation().type = resultType; _functionCall.annotation().type = resultType;
_functionCall.annotation().isPure = isPure;
return false; return false;
} }
@ -1193,9 +1205,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
auto const& structType = dynamic_cast<StructType const&>(*t.actualType()); auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
functionType = structType.constructorType(); functionType = structType.constructorType();
membersRemovedForStructConstructor = structType.membersMissingInMemory(); membersRemovedForStructConstructor = structType.membersMissingInMemory();
_functionCall.annotation().isPure = isPure;
} }
else else
{
functionType = dynamic_pointer_cast<FunctionType const>(expressionType); functionType = dynamic_pointer_cast<FunctionType const>(expressionType);
_functionCall.annotation().isPure =
isPure &&
_functionCall.expression().annotation().isPure &&
functionType->isPure();
}
if (!functionType) if (!functionType)
{ {
@ -1360,6 +1379,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
strings(), strings(),
FunctionType::Location::ObjectCreation FunctionType::Location::ObjectCreation
); );
_newExpression.annotation().isPure = true;
} }
else else
fatalTypeError(_newExpression.location(), "Contract or array type expected."); fatalTypeError(_newExpression.location(), "Contract or array type expected.");
@ -1445,6 +1465,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.isLValue = annotation.referencedDeclaration->isLValue();
} }
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this to false for now.
return false; return false;
} }
@ -1454,6 +1477,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
TypePointer baseType = type(_access.baseExpression()); TypePointer baseType = type(_access.baseExpression());
TypePointer resultType; TypePointer resultType;
bool isLValue = false; bool isLValue = false;
bool isPure = _access.baseExpression().annotation().isPure;
Expression const* index = _access.indexExpression(); Expression const* index = _access.indexExpression();
switch (baseType->category()) switch (baseType->category())
{ {
@ -1535,6 +1559,9 @@ bool TypeChecker::visit(IndexAccess const& _access)
} }
_access.annotation().type = move(resultType); _access.annotation().type = move(resultType);
_access.annotation().isLValue = isLValue; _access.annotation().isLValue = isLValue;
if (index && !index->annotation().isPure)
isPure = false;
_access.annotation().isPure = isPure;
return false; return false;
} }
@ -1589,18 +1616,22 @@ bool TypeChecker::visit(Identifier const& _identifier)
!!annotation.referencedDeclaration, !!annotation.referencedDeclaration,
"Referenced declaration is null after overload resolution." "Referenced declaration is null after overload resolution."
); );
auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration);
annotation.isConstant = variableDeclaration != nullptr && variableDeclaration->isConstant();
annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.isLValue = annotation.referencedDeclaration->isLValue();
annotation.type = annotation.referencedDeclaration->type(); annotation.type = annotation.referencedDeclaration->type();
if (!annotation.type) if (!annotation.type)
fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
if (auto functionType = dynamic_cast<FunctionType const*>(annotation.type.get()))
annotation.isPure = functionType->isPure();
return false; return false;
} }
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{ {
_expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeName())); _expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeName()));
_expr.annotation().isPure = true;
} }
void TypeChecker::endVisit(Literal const& _literal) void TypeChecker::endVisit(Literal const& _literal)
@ -1620,6 +1651,7 @@ void TypeChecker::endVisit(Literal const& _literal)
); );
} }
_literal.annotation().type = Type::forLiteral(_literal); _literal.annotation().type = Type::forLiteral(_literal);
_literal.annotation().isPure = true;
if (!_literal.annotation().type) if (!_literal.annotation().type)
fatalTypeError(_literal.location(), "Invalid literal value."); fatalTypeError(_literal.location(), "Invalid literal value.");
} }

View File

@ -156,6 +156,8 @@ struct ExpressionAnnotation: ASTAnnotation
TypePointer type; TypePointer type;
/// Whether the expression is a constant variable /// Whether the expression is a constant variable
bool isConstant = false; bool isConstant = false;
/// Whether the expression is pure, i.e. compile-time constant.
bool isPure = false;
/// Whether it is an LValue (i.e. something that can be assigned to). /// Whether it is an LValue (i.e. something that can be assigned to).
bool isLValue = false; bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required. /// Whether the expression is used in a context where the LValue is actually required.

View File

@ -2456,6 +2456,18 @@ u256 FunctionType::externalIdentifier() const
return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature()))); return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature())));
} }
bool FunctionType::isPure() const
{
return
m_location == Location::SHA3 ||
m_location == Location::ECRecover ||
m_location == Location::SHA256 ||
m_location == Location::RIPEMD160 ||
m_location == Location::AddMod ||
m_location == Location::MulMod ||
m_location == Location::ObjectCreation;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
{ {
TypePointers pointers; TypePointers pointers;

View File

@ -972,6 +972,10 @@ public:
} }
bool hasDeclaration() const { return !!m_declaration; } bool hasDeclaration() const { return !!m_declaration; }
bool isConstant() const { return m_isConstant; } bool isConstant() const { return m_isConstant; }
/// @returns true if the the result of this function only depends on its arguments
/// and it does not modify the state.
/// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const;
bool isPayable() const { return m_isPayable; } bool isPayable() const { return m_isPayable; }
/// @return A shared pointer of an ASTString. /// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation /// Can contain a nullptr in which case indicates absence of documentation

View File

@ -2180,7 +2180,18 @@ BOOST_AUTO_TEST_CASE(assigning_state_to_const_variable)
address constant x = msg.sender; address constant x = msg.sender;
} }
)"; )";
CHECK_ERROR(text, TypeError, "Expression is not compile-time constant."); CHECK_ERROR(text, TypeError, "Initial value for constant variable has to be compile-time constant.");
}
BOOST_AUTO_TEST_CASE(assign_constant_function_value_to_constant)
{
char const* text = R"(
contract C {
function () constant returns (uint) x;
uint constant y = x();
}
)";
CHECK_ERROR(text, TypeError, "Initial value for constant variable has to be compile-time constant.");
} }
BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_conversion) BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_conversion)
@ -2197,7 +2208,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression)
{ {
char const* text = R"( char const* text = R"(
contract C { contract C {
uint constant x = 0x123 + 9x456; uint constant x = 0x123 + 0x456;
} }
)"; )";
CHECK_SUCCESS(text); CHECK_SUCCESS(text);
@ -2207,7 +2218,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_keccak)
{ {
char const* text = R"( char const* text = R"(
contract C { contract C {
bytes32 constant x = keccak("abc"); bytes32 constant x = keccak256("abc");
} }
)"; )";
CHECK_SUCCESS(text); CHECK_SUCCESS(text);
@ -2217,22 +2228,21 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_array_vars)
{ {
char const* text = R"( char const* text = R"(
contract C { contract C {
uint[3] memory constant x = [1, 2, 3]; uint[3] constant x = [uint(1), 2, 3];
} }
)"; )";
CHECK_SUCCESS(text); CHECK_SUCCESS(text);
} }
BOOST_AUTO_TEST_CASE(complex_const_variable) BOOST_AUTO_TEST_CASE(constant_struct)
{ {
//for now constant specifier is valid only for uint bytesXX and enums
char const* text = R"( char const* text = R"(
contract Foo { contract C {
mapping(uint => bool) x; struct S { uint x; uint[] y; }
mapping(uint => bool) constant mapVar = x; S constant x = S(5, new uint[](4));
} }
)"; )";
CHECK_ERROR(text, TypeError, ""); CHECK_SUCCESS(text);
} }
BOOST_AUTO_TEST_CASE(uninitialized_const_variable) BOOST_AUTO_TEST_CASE(uninitialized_const_variable)