aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchriseth <c@ethdev.com>2017-03-02 02:12:40 +0800
committerchriseth <c@ethdev.com>2017-03-13 20:30:21 +0800
commitf39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa (patch)
tree0dc1b367e2f5bc285457dc4358b2377ad4d5d1f0
parentbde913f088c873cba75db30b2d463f50f9dfe862 (diff)
downloaddexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar.gz
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar.bz2
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar.lz
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar.xz
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.tar.zst
dexon-solidity-f39763e91c97b29bfe6d2c79cb1c1ebf80b2b9aa.zip
Type checking for pure expressions.
-rw-r--r--libsolidity/analysis/TypeChecker.cpp68
-rw-r--r--libsolidity/ast/ASTAnnotations.h2
-rw-r--r--libsolidity/ast/Types.cpp12
-rw-r--r--libsolidity/ast/Types.h4
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp30
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<ContractDefinition const*>(_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<ArrayType const*>(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<BoolType>() :
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<ASTPointer<Expression const>> arguments = _functionCall.arguments();
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.
shared_ptr<TypePointers> argumentTypes;
if (isPositionalCall)
@@ -1140,6 +1149,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (ASTPointer<Expression const> 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<StructType const&>(*t.actualType());
functionType = structType.constructorType();
membersRemovedForStructConstructor = structType.membersMissingInMemory();
+ _functionCall.annotation().isPure = isPure;
}
else
+ {
functionType = dynamic_pointer_cast<FunctionType const>(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<VariableDeclaration const*>(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<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;
}
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = make_shared<TypeType>(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)