From f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 19:12:40 +0100 Subject: Type checking for pure expressions. --- libsolidity/analysis/TypeChecker.cpp | 68 ++++++++++++++++------ libsolidity/ast/ASTAnnotations.h | 2 + libsolidity/ast/Types.cpp | 12 ++++ libsolidity/ast/Types.h | 4 ++ test/libsolidity/SolidityNameAndTypeResolution.cpp | 30 ++++++---- 5 files changed, 88 insertions(+), 28 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index fbff6865..38da9b14 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -467,27 +467,20 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) // TypeChecker at the VariableDeclarationStatement level. TypePointer varType = _variable.annotation().type; solAssert(!!varType, "Failed to infer variable type."); + if (_variable.value()) + expectType(*_variable.value(), *varType); if (_variable.isConstant()) { - if (!dynamic_cast(_variable.scope())) + if (!_variable.isStateVariable()) typeError(_variable.location(), "Illegal use of \"constant\" specifier."); if (!_variable.value()) typeError(_variable.location(), "Uninitialized \"constant\" variable."); - if (!varType->isValueType()) - { - bool constImplemented = false; - if (auto arrayType = dynamic_cast(varType.get())) - constImplemented = arrayType->isByteArray(); - if (!constImplemented) - typeError( - _variable.location(), - "Illegal use of \"constant\" specifier. \"constant\" " - "is not yet implemented for this type." - ); - } + else if (!_variable.value()->annotation().isPure) + typeError( + _variable.value()->location(), + "Initial value for constant variable has to be compile-time constant." + ); } - if (_variable.value()) - expectType(*_variable.value(), *varType); if (!_variable.isStateVariable()) { 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().isPure = + _conditional.condition().annotation().isPure && + _conditional.trueExpression().annotation().isPure && + _conditional.falseExpression().annotation().isPure; if (_conditional.annotation().lValueRequested) typeError( @@ -1009,6 +1006,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) } else { + bool isPure = true; TypePointer inlineArrayType; for (size_t i = 0; i < components.size(); ++i) { @@ -1031,10 +1029,13 @@ bool TypeChecker::visit(TupleExpression const& _tuple) else if (inlineArrayType) inlineArrayType = Type::commonType(inlineArrayType, types[i]); } + if (!components[i]->annotation().isPure) + isPure = false; } else types.push_back(TypePointer()); } + _tuple.annotation().isPure = isPure; if (_tuple.isInlineArray()) { if (!inlineArrayType) @@ -1061,7 +1062,8 @@ bool TypeChecker::visit(UnaryOperation const& _operation) { // Inc, Dec, Add, Sub, Not, BitNot, Delete 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()); else _operation.subExpression().accept(*this); @@ -1079,6 +1081,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation) t = subExprType; } _operation.annotation().type = t; + _operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure; return false; } @@ -1105,6 +1108,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) Token::isCompareOp(_operation.getOperator()) ? make_shared() : commonType; + _operation.annotation().isPure = + _operation.leftExpression().annotation().isPure && + _operation.rightExpression().annotation().isPure; + if (_operation.getOperator() == Token::Exp) { if ( @@ -1133,6 +1140,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) vector> arguments = _functionCall.arguments(); vector> const& argumentNames = _functionCall.names(); + bool isPure = true; + // We need to check arguments' type first as they will be needed for overload resolution. shared_ptr argumentTypes; if (isPositionalCall) @@ -1140,6 +1149,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) for (ASTPointer const& argument: arguments) { argument->accept(*this); + if (!argument->annotation().isPure) + isPure = false; // only store them for positional calls if (isPositionalCall) argumentTypes->push_back(type(*argument)); @@ -1177,6 +1188,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) typeError(_functionCall.location(), "Explicit type conversion not allowed."); } _functionCall.annotation().type = resultType; + _functionCall.annotation().isPure = isPure; return false; } @@ -1193,9 +1205,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) auto const& structType = dynamic_cast(*t.actualType()); functionType = structType.constructorType(); membersRemovedForStructConstructor = structType.membersMissingInMemory(); + _functionCall.annotation().isPure = isPure; } else + { functionType = dynamic_pointer_cast(expressionType); + _functionCall.annotation().isPure = + isPure && + _functionCall.expression().annotation().isPure && + functionType->isPure(); + } if (!functionType) { @@ -1360,6 +1379,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) strings(), FunctionType::Location::ObjectCreation ); + _newExpression.annotation().isPure = true; } else fatalTypeError(_newExpression.location(), "Contract or array type expected."); @@ -1445,6 +1465,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) 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; } @@ -1454,6 +1477,7 @@ bool TypeChecker::visit(IndexAccess const& _access) TypePointer baseType = type(_access.baseExpression()); TypePointer resultType; bool isLValue = false; + bool isPure = _access.baseExpression().annotation().isPure; Expression const* index = _access.indexExpression(); switch (baseType->category()) { @@ -1535,6 +1559,9 @@ bool TypeChecker::visit(IndexAccess const& _access) } _access.annotation().type = move(resultType); _access.annotation().isLValue = isLValue; + if (index && !index->annotation().isPure) + isPure = false; + _access.annotation().isPure = isPure; return false; } @@ -1589,18 +1616,22 @@ bool TypeChecker::visit(Identifier const& _identifier) !!annotation.referencedDeclaration, "Referenced declaration is null after overload resolution." ); - auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration); - annotation.isConstant = variableDeclaration != nullptr && variableDeclaration->isConstant(); annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.type = annotation.referencedDeclaration->type(); if (!annotation.type) fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); + if (auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration)) + annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); + else if (dynamic_cast(annotation.referencedDeclaration)) + if (auto functionType = dynamic_cast(annotation.type.get())) + annotation.isPure = functionType->isPure(); return false; } void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) { _expr.annotation().type = make_shared(Type::fromElementaryTypeName(_expr.typeName())); + _expr.annotation().isPure = true; } void TypeChecker::endVisit(Literal const& _literal) @@ -1620,6 +1651,7 @@ void TypeChecker::endVisit(Literal const& _literal) ); } _literal.annotation().type = Type::forLiteral(_literal); + _literal.annotation().isPure = true; if (!_literal.annotation().type) fatalTypeError(_literal.location(), "Invalid literal value."); } diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 9c4c3ae8..bd297f9e 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -156,6 +156,8 @@ struct ExpressionAnnotation: ASTAnnotation TypePointer type; /// Whether the expression is a constant variable 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). bool isLValue = false; /// Whether the expression is used in a context where the LValue is actually required. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d2793b6d..0e11c3ec 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2456,6 +2456,18 @@ u256 FunctionType::externalIdentifier() const 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 pointers; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 022b67c4..7c8fd429 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -972,6 +972,10 @@ public: } bool hasDeclaration() const { return !!m_declaration; } 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; } /// @return A shared pointer of an ASTString. /// Can contain a nullptr in which case indicates absence of documentation diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index aef93e92..90831ccd 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2180,7 +2180,18 @@ BOOST_AUTO_TEST_CASE(assigning_state_to_const_variable) 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) @@ -2197,7 +2208,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression) { char const* text = R"( contract C { - uint constant x = 0x123 + 9x456; + uint constant x = 0x123 + 0x456; } )"; CHECK_SUCCESS(text); @@ -2207,7 +2218,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_keccak) { char const* text = R"( contract C { - bytes32 constant x = keccak("abc"); + bytes32 constant x = keccak256("abc"); } )"; CHECK_SUCCESS(text); @@ -2217,22 +2228,21 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_array_vars) { char const* text = R"( contract C { - uint[3] memory constant x = [1, 2, 3]; + uint[3] constant x = [uint(1), 2, 3]; } )"; 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"( - contract Foo { - mapping(uint => bool) x; - mapping(uint => bool) constant mapVar = x; + contract C { + struct S { uint x; uint[] y; } + S constant x = S(5, new uint[](4)); } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(uninitialized_const_variable) -- cgit