From 5470da4d9adc8ef07aa1c2a758b7062be843cca4 Mon Sep 17 00:00:00 2001
From: chriseth <chris@ethereum.org>
Date: Mon, 28 Aug 2017 19:48:34 +0200
Subject: View-pure checker.

---
 libsolidity/analysis/StaticAnalyzer.h    |   4 +-
 libsolidity/analysis/ViewPureChecker.cpp | 227 +++++++++++++++++++++++++++++++
 libsolidity/analysis/ViewPureChecker.h   |  79 +++++++++++
 libsolidity/ast/Types.h                  |   1 +
 libsolidity/interface/CompilerStack.cpp  |  11 ++
 5 files changed, 320 insertions(+), 2 deletions(-)
 create mode 100644 libsolidity/analysis/ViewPureChecker.cpp
 create mode 100644 libsolidity/analysis/ViewPureChecker.h

diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index a3080b42..24ed119f 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/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
new file mode 100644
index 00000000..0e2cfacf
--- /dev/null
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -0,0 +1,227 @@
+/*
+	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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <libsolidity/analysis/ViewPureChecker.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+bool ViewPureChecker::check()
+{
+	vector<ContractDefinition const*> contracts;
+
+	for (auto const& node: m_ast)
+	{
+		SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get());
+		solAssert(source, "");
+		for (auto const& topLevelNode: source->nodes())
+		{
+			ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(topLevelNode.get());
+			if (contract)
+				contracts.push_back(contract);
+		}
+	}
+
+	// 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.isConstructor() &&
+		!_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<VariableDeclaration const*>(declaration))
+	{
+		if (varDecl->isStateVariable())
+			mutability = writes ? StateMutability::NonPayable : StateMutability::View;
+	}
+	else if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
+	{
+		switch (magicVar->type()->category())
+		{
+		case Type::Category::Contract:
+			solAssert(_identifier.name() == "this" || _identifier.name() == "super", "");
+			if (!dynamic_cast<ContractType const&>(*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);
+}
+
+void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
+{
+	// @TOOD we can and should analyze it further.
+	reportMutability(StateMutability::NonPayable, _inlineAssembly);
+}
+
+void ViewPureChecker::reportMutability(StateMutability _mutability, const ASTNode& _node)
+{
+	if (m_currentFunction && m_currentFunction->stateMutability() < _mutability)
+	{
+		m_errors = true;
+		if (_mutability == StateMutability::View)
+			m_errorReporter.typeError(
+				_node.location(),
+				"Function declared as pure, but this expression reads from the environment or state and thus "
+				"requires \"view\"."
+			);
+		else if (_mutability == StateMutability::NonPayable)
+			m_errorReporter.typeError(
+				_node.location(),
+				"Function declared as " +
+				stateMutabilityToString(m_currentFunction->stateMutability()) +
+				", but this expression modifies the state and thus "
+				"requires non-payable (the default) or payable."
+			);
+		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<FunctionType const&>(*_functionCall.expression().annotation().type).stateMutability();
+	// We only require "nonpayable" to call a payble function.
+	if (mut == StateMutability::Payable)
+		mut = StateMutability::NonPayable;
+	reportMutability(mut, _functionCall);
+}
+
+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<ArrayType const&>(*_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);
+}
+
+void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
+{
+	solAssert(_indexAccess.indexExpression(), "");
+
+	bool writes = _indexAccess.annotation().lValueRequested;
+	if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
+		reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess);
+}
+
+void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
+{
+	solAssert(_modifier.name(), "");
+	ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration);
+	solAssert(mod, "");
+	solAssert(m_inferredMutability.count(mod), "");
+
+	reportMutability(m_inferredMutability.at(mod), _modifier);
+}
+
diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h
new file mode 100644
index 00000000..6aedfa36
--- /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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <libsolidity/ast/ASTEnums.h>
+#include <libsolidity/ast/ASTForward.h>
+#include <libsolidity/ast/ASTVisitor.h>
+
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <map>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ASTNode;
+class FunctionDefinition;
+class ModifierDefinition;
+class Identifier;
+class MemberAccess;
+class IndexAccess;
+class ModifierInvocation;
+class FunctionCall;
+class InlineAssembly;
+
+class ViewPureChecker: private ASTConstVisitor
+{
+public:
+	ViewPureChecker(std::vector<std::shared_ptr<ASTNode>> const& _ast, ErrorReporter& _errorReporter):
+		m_ast(_ast), m_errorReporter(_errorReporter) {}
+
+	bool check();
+
+private:
+
+	virtual bool visit(FunctionDefinition const& _funDef) override;
+	virtual void endVisit(FunctionDefinition const& _funDef) override;
+	virtual bool visit(ModifierDefinition const& _modifierDef) override;
+	virtual void endVisit(ModifierDefinition const& _modifierDef) override;
+	virtual void endVisit(Identifier const& _identifier) override;
+	virtual void endVisit(MemberAccess const& _memberAccess) override;
+	virtual void endVisit(IndexAccess const& _indexAccess) override;
+	virtual void endVisit(ModifierInvocation const& _modifier) override;
+	virtual void endVisit(FunctionCall const& _functionCall) override;
+	virtual void endVisit(InlineAssembly const& _inlineAssembly) override;
+
+	/// Called when an element of mutability @a _mutability is encountered.
+	/// Creates appropriate warnings and errors and sets @a m_currentBestMutability.
+	void reportMutability(StateMutability _mutability, ASTNode const& _node);
+
+	std::vector<std::shared_ptr<ASTNode>> const& m_ast;
+	ErrorReporter& m_errorReporter;
+
+	bool m_errors = false;
+	StateMutability m_currentBestMutability = StateMutability::Payable;
+	FunctionDefinition const* m_currentFunction = nullptr;
+	std::map<ModifierDefinition const*, StateMutability> m_inferredMutability;
+};
+
+}
+}
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index de6dcee9..d4d6da69 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -1064,6 +1064,7 @@ public:
 	{
 		return _inLibrary ? shared_from_this() : TypePointer();
 	}
+	virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
 
 	TypePointer const& keyType() const { return m_keyType; }
 	TypePointer const& valueType() const { return m_valueType; }
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 259694da..99ad061c 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -36,6 +36,7 @@
 #include <libsolidity/analysis/StaticAnalyzer.h>
 #include <libsolidity/analysis/PostTypeChecker.h>
 #include <libsolidity/analysis/SyntaxChecker.h>
+#include <libsolidity/analysis/ViewPureChecker.h>
 #include <libsolidity/codegen/Compiler.h>
 #include <libsolidity/formal/SMTChecker.h>
 #include <libsolidity/interface/ABI.h>
@@ -220,6 +221,16 @@ bool CompilerStack::analyze()
 				noErrors = false;
 	}
 
+	if (noErrors)
+	{
+		vector<ASTPointer<ASTNode>> ast;
+		for (Source const* source: m_sourceOrder)
+			ast.push_back(source->ast);
+
+		if (!ViewPureChecker(ast, m_errorReporter).check())
+			noErrors = false;
+	}
+
 	if (noErrors)
 	{
 		SMTChecker smtChecker(m_errorReporter, m_smtQuery);
-- 
cgit v1.2.3