diff --git a/Changelog.md b/Changelog.md
index 71f873a9c..62e4eb093 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -6,6 +6,7 @@ Features:
* Type Checker: Do not show the same error multiple times for events.
* Type Checker: Greatly reduce the number of duplicate errors shown for duplicate constructors and functions.
* Type Checker: Warn on using literals as tight packing parameters in ``keccak256``, ``sha3``, ``sha256`` and ``ripemd160``.
+ * Type Checker: Enforce ``view`` and ``pure``.
Bugfixes:
* ABI JSON: Include all overloaded events.
diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp
index f63f0c616..ceb3fbdd0 100644
--- a/libevmasm/SemanticInformation.cpp
+++ b/libevmasm/SemanticInformation.cpp
@@ -188,3 +188,56 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction)
return false;
}
}
+
+bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
+{
+ switch (_instruction)
+ {
+ case Instruction::ADDRESS:
+ case Instruction::BALANCE:
+ case Instruction::ORIGIN:
+ case Instruction::CALLER:
+ case Instruction::CALLVALUE:
+ case Instruction::GASPRICE:
+ case Instruction::EXTCODESIZE:
+ case Instruction::EXTCODECOPY:
+ case Instruction::BLOCKHASH:
+ case Instruction::COINBASE:
+ case Instruction::TIMESTAMP:
+ case Instruction::NUMBER:
+ case Instruction::DIFFICULTY:
+ case Instruction::GASLIMIT:
+ case Instruction::STATICCALL:
+ case Instruction::SLOAD:
+ return true;
+ default:
+ break;
+ }
+ return invalidInViewFunctions(_instruction);
+}
+
+bool SemanticInformation::invalidInViewFunctions(Instruction _instruction)
+{
+ switch (_instruction)
+ {
+ case Instruction::SSTORE:
+ case Instruction::JUMP:
+ case Instruction::JUMPI:
+ case Instruction::GAS:
+ case Instruction::LOG0:
+ case Instruction::LOG1:
+ case Instruction::LOG2:
+ case Instruction::LOG3:
+ case Instruction::LOG4:
+ case Instruction::CREATE:
+ case Instruction::CALL:
+ case Instruction::CALLCODE:
+ case Instruction::DELEGATECALL:
+ case Instruction::CREATE2:
+ case Instruction::SELFDESTRUCT:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h
index 5b02061fc..e5ea7c180 100644
--- a/libevmasm/SemanticInformation.h
+++ b/libevmasm/SemanticInformation.h
@@ -53,6 +53,8 @@ struct SemanticInformation
static bool invalidatesMemory(solidity::Instruction _instruction);
/// @returns true if the given instruction modifies storage (even indirectly).
static bool invalidatesStorage(solidity::Instruction _instruction);
+ static bool invalidInPureFunctions(solidity::Instruction _instruction);
+ static bool invalidInViewFunctions(solidity::Instruction _instruction);
};
}
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
index d012c25d3..ffa538b69 100644
--- a/libsolidity/analysis/StaticAnalyzer.cpp
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -57,8 +57,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function)
solAssert(m_localVarUseCount.empty(), "");
m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
m_constructor = _function.isConstructor();
- if (_function.stateMutability() == StateMutability::Pure)
- m_errorReporter.warning(_function.location(), "Function is marked pure. Be careful, pureness is not enforced yet.");
return true;
}
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index a3080b428..24ed119fa 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -37,8 +37,8 @@ namespace solidity
/**
* The module that performs static analysis on the AST.
* In this context, static analysis is anything that can produce warnings which can help
- * programmers write cleaner code. For every warning generated eher, it has to be possible to write
- * equivalent code that does generate the warning.
+ * programmers write cleaner code. For every warning generated here, it has to be possible to write
+ * equivalent code that does not generate the warning.
*/
class StaticAnalyzer: private ASTConstVisitor
{
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index c54f4c871..26529c229 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -339,6 +339,9 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
if (!functionType.hasEqualArgumentTypes(superType))
return;
+ if (!function.annotation().superFunction)
+ function.annotation().superFunction = &super;
+
if (function.visibility() != super.visibility())
overrideError(function, super, "Overriding function visibility differs.");
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index f2e13765d..0c6f54d3f 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -63,6 +63,7 @@ private:
void checkContractDuplicateFunctions(ContractDefinition const& _contract);
void checkContractIllegalOverrides(ContractDefinition const& _contract);
/// Reports a type error with an appropiate message if overriden function signature differs.
+ /// Also stores the direct super function in the AST annotations.
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
void checkContractAbstractFunctions(ContractDefinition const& _contract);
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
new file mode 100644
index 000000000..8f9d41c92
--- /dev/null
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -0,0 +1,313 @@
+/*
+ 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 .
+*/
+
+#include
+
+#include
+
+#include
+
+#include
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+namespace
+{
+
+class AssemblyViewPureChecker: public boost::static_visitor
+{
+public:
+ explicit AssemblyViewPureChecker(std::function _reportMutability):
+ m_reportMutability(_reportMutability) {}
+
+ void operator()(assembly::Label const&) { }
+ void operator()(assembly::Instruction const& _instruction)
+ {
+ if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction))
+ m_reportMutability(StateMutability::NonPayable, _instruction.location);
+ else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction))
+ m_reportMutability(StateMutability::View, _instruction.location);
+ }
+ void operator()(assembly::Literal const&) {}
+ void operator()(assembly::Identifier const&) {}
+ void operator()(assembly::FunctionalInstruction const& _instr)
+ {
+ (*this)(_instr.instruction);
+ for (auto const& arg: _instr.arguments)
+ boost::apply_visitor(*this, arg);
+ }
+ void operator()(assembly::StackAssignment const&) {}
+ void operator()(assembly::Assignment const& _assignment)
+ {
+ boost::apply_visitor(*this, *_assignment.value);
+ }
+ void operator()(assembly::VariableDeclaration const& _varDecl)
+ {
+ if (_varDecl.value)
+ boost::apply_visitor(*this, *_varDecl.value);
+ }
+ void operator()(assembly::FunctionDefinition const& _funDef)
+ {
+ (*this)(_funDef.body);
+ }
+ void operator()(assembly::FunctionCall const& _funCall)
+ {
+ for (auto const& arg: _funCall.arguments)
+ boost::apply_visitor(*this, arg);
+ }
+ void operator()(assembly::Switch const& _switch)
+ {
+ boost::apply_visitor(*this, *_switch.expression);
+ for (auto const& _case: _switch.cases)
+ {
+ if (_case.value)
+ (*this)(*_case.value);
+ (*this)(_case.body);
+ }
+ }
+ void operator()(assembly::ForLoop const& _for)
+ {
+ (*this)(_for.pre);
+ boost::apply_visitor(*this, *_for.condition);
+ (*this)(_for.body);
+ (*this)(_for.post);
+ }
+ void operator()(assembly::Block const& _block)
+ {
+ for (auto const& s: _block.statements)
+ boost::apply_visitor(*this, s);
+ }
+
+private:
+ std::function m_reportMutability;
+};
+
+}
+
+bool ViewPureChecker::check()
+{
+ vector contracts;
+
+ for (auto const& node: m_ast)
+ {
+ SourceUnit const* source = dynamic_cast(node.get());
+ solAssert(source, "");
+ contracts += source->filteredNodes(source->nodes());
+ }
+
+ // Check modifiers first to infer their state mutability.
+ for (auto const* contract: contracts)
+ for (ModifierDefinition const* mod: contract->functionModifiers())
+ mod->accept(*this);
+
+ for (auto const* contract: contracts)
+ contract->accept(*this);
+
+ return !m_errors;
+}
+
+
+
+bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
+{
+ solAssert(!m_currentFunction, "");
+ m_currentFunction = &_funDef;
+ m_currentBestMutability = StateMutability::Pure;
+ return true;
+}
+
+void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
+{
+ solAssert(m_currentFunction == &_funDef, "");
+ if (
+ m_currentBestMutability < _funDef.stateMutability() &&
+ _funDef.stateMutability() != StateMutability::Payable &&
+ _funDef.isImplemented() &&
+ !_funDef.isConstructor() &&
+ !_funDef.isFallback() &&
+ !_funDef.annotation().superFunction
+ )
+ m_errorReporter.warning(
+ _funDef.location(),
+ "Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability)
+ );
+ m_currentFunction = nullptr;
+}
+
+bool ViewPureChecker::visit(ModifierDefinition const&)
+{
+ solAssert(m_currentFunction == nullptr, "");
+ m_currentBestMutability = StateMutability::Pure;
+ return true;
+}
+
+void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef)
+{
+ solAssert(m_currentFunction == nullptr, "");
+ m_inferredMutability[&_modifierDef] = m_currentBestMutability;
+}
+
+void ViewPureChecker::endVisit(Identifier const& _identifier)
+{
+ Declaration const* declaration = _identifier.annotation().referencedDeclaration;
+ solAssert(declaration, "");
+
+ StateMutability mutability = StateMutability::Pure;
+
+ bool writes = _identifier.annotation().lValueRequested;
+ if (VariableDeclaration const* varDecl = dynamic_cast(declaration))
+ {
+ if (varDecl->isStateVariable() && !varDecl->isConstant())
+ mutability = writes ? StateMutability::NonPayable : StateMutability::View;
+ }
+ else if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration))
+ {
+ switch (magicVar->type()->category())
+ {
+ case Type::Category::Contract:
+ solAssert(_identifier.name() == "this" || _identifier.name() == "super", "");
+ if (!dynamic_cast(*magicVar->type()).isSuper())
+ // reads the address
+ mutability = StateMutability::View;
+ break;
+ case Type::Category::Integer:
+ solAssert(_identifier.name() == "now", "");
+ mutability = StateMutability::View;
+ break;
+ default:
+ break;
+ }
+ }
+
+ reportMutability(mutability, _identifier.location());
+}
+
+void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
+{
+ AssemblyViewPureChecker{
+ [=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
+ }(_inlineAssembly.operations());
+}
+
+void ViewPureChecker::reportMutability(StateMutability _mutability, SourceLocation const& _location)
+{
+ if (m_currentFunction && m_currentFunction->stateMutability() < _mutability)
+ {
+ string text;
+ if (_mutability == StateMutability::View)
+ text =
+ "Function declared as pure, but this expression (potentially) reads from the "
+ "environment or state and thus requires \"view\".";
+ else if (_mutability == StateMutability::NonPayable)
+ text =
+ "Function declared as " +
+ stateMutabilityToString(m_currentFunction->stateMutability()) +
+ ", but this expression (potentially) modifies the state and thus "
+ "requires non-payable (the default) or payable.";
+ else
+ solAssert(false, "");
+
+ if (m_currentFunction->stateMutability() == StateMutability::View)
+ // TODO Change this to error with 0.5.0
+ m_errorReporter.warning(_location, text);
+ else if (m_currentFunction->stateMutability() == StateMutability::Pure)
+ {
+ m_errors = true;
+ m_errorReporter.typeError(_location, text);
+ }
+ else
+ solAssert(false, "");
+ }
+ if (_mutability > m_currentBestMutability)
+ m_currentBestMutability = _mutability;
+}
+
+void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
+{
+ if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
+ return;
+
+ StateMutability mut = dynamic_cast(*_functionCall.expression().annotation().type).stateMutability();
+ // We only require "nonpayable" to call a payble function.
+ if (mut == StateMutability::Payable)
+ mut = StateMutability::NonPayable;
+ reportMutability(mut, _functionCall.location());
+}
+
+void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
+{
+ StateMutability mutability = StateMutability::Pure;
+ bool writes = _memberAccess.annotation().lValueRequested;
+
+ ASTString const& member = _memberAccess.memberName();
+ switch (_memberAccess.expression().annotation().type->category())
+ {
+ case Type::Category::Contract:
+ case Type::Category::Integer:
+ if (member == "balance" && !_memberAccess.annotation().referencedDeclaration)
+ mutability = StateMutability::View;
+ break;
+ case Type::Category::Magic:
+ // we can ignore the kind of magic and only look at the name of the member
+ if (member != "data" && member != "sig" && member != "blockhash")
+ mutability = StateMutability::View;
+ break;
+ case Type::Category::Struct:
+ {
+ if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))
+ mutability = writes ? StateMutability::NonPayable : StateMutability::View;
+ break;
+ }
+ case Type::Category::Array:
+ {
+ auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type);
+ if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
+ mutability = writes ? StateMutability::NonPayable : StateMutability::View;
+ break;
+ }
+ default:
+ break;
+ }
+ reportMutability(mutability, _memberAccess.location());
+}
+
+void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
+{
+ if (!_indexAccess.indexExpression())
+ solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
+ else
+ {
+ bool writes = _indexAccess.annotation().lValueRequested;
+ if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
+ reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
+ }
+}
+
+void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
+{
+ solAssert(_modifier.name(), "");
+ if (ModifierDefinition const* mod = dynamic_cast(_modifier.name()->annotation().referencedDeclaration))
+ {
+ solAssert(m_inferredMutability.count(mod), "");
+ reportMutability(m_inferredMutability.at(mod), _modifier.location());
+ }
+ else
+ solAssert(dynamic_cast(_modifier.name()->annotation().referencedDeclaration), "");
+}
+
diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h
new file mode 100644
index 000000000..ae3035336
--- /dev/null
+++ b/libsolidity/analysis/ViewPureChecker.h
@@ -0,0 +1,79 @@
+/*
+ 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 .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+#include