mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #5617 from ethereum/controlFlowRework
Rework of ControlFlowGraph and analysis.
This commit is contained in:
		
						commit
						35d6db880a
					
				| @ -1,6 +1,7 @@ | |||||||
| ### 0.5.2 (unreleased) | ### 0.5.2 (unreleased) | ||||||
| 
 | 
 | ||||||
| Language Features: | Language Features: | ||||||
|  |  * Control Flow Graph: Detect every access to uninitialized storage pointers. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Compiler Features: | Compiler Features: | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <libsolidity/analysis/ControlFlowAnalyzer.h> | #include <libsolidity/analysis/ControlFlowAnalyzer.h> | ||||||
| #include <liblangutil/SourceLocation.h> | #include <liblangutil/SourceLocation.h> | ||||||
|  | #include <boost/range/algorithm/sort.hpp> | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
| using namespace langutil; | using namespace langutil; | ||||||
| @ -33,131 +34,112 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function) | |||||||
| 	if (_function.isImplemented()) | 	if (_function.isImplemented()) | ||||||
| 	{ | 	{ | ||||||
| 		auto const& functionFlow = m_cfg.functionFlow(_function); | 		auto const& functionFlow = m_cfg.functionFlow(_function); | ||||||
| 		checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit); | 		checkUninitializedAccess(functionFlow.entry, functionFlow.exit); | ||||||
| 	} | 	} | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| set<VariableDeclaration const*> ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node) | void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const | ||||||
| { | { | ||||||
| 	set<VariableDeclaration const*> result; | 	struct NodeInfo | ||||||
| 	for (auto expression: node->block.expressions) |  | ||||||
| 	{ | 	{ | ||||||
| 		if (auto const* assignment = dynamic_cast<Assignment const*>(expression)) | 		set<VariableDeclaration const*> unassignedVariablesAtEntry; | ||||||
|  | 		set<VariableDeclaration const*> unassignedVariablesAtExit; | ||||||
|  | 		set<VariableOccurrence const*> uninitializedVariableAccesses; | ||||||
|  | 		/// Propagate the information from another node to this node.
 | ||||||
|  | 		/// To be used to propagate information from a node to its exit nodes.
 | ||||||
|  | 		/// Returns true, if new variables were added and thus the current node has
 | ||||||
|  | 		/// to be traversed again.
 | ||||||
|  | 		bool propagateFrom(NodeInfo const& _entryNode) | ||||||
| 		{ | 		{ | ||||||
| 			stack<Expression const*> expressions; | 			size_t previousUnassignedVariablesAtEntry = unassignedVariablesAtEntry.size(); | ||||||
| 			expressions.push(&assignment->leftHandSide()); | 			size_t previousUninitializedVariableAccessess = uninitializedVariableAccesses.size(); | ||||||
| 			while (!expressions.empty()) | 			unassignedVariablesAtEntry += _entryNode.unassignedVariablesAtExit; | ||||||
| 			{ | 			uninitializedVariableAccesses += _entryNode.uninitializedVariableAccesses; | ||||||
| 				Expression const* expression = expressions.top(); | 			return | ||||||
| 				expressions.pop(); | 				unassignedVariablesAtEntry.size() > previousUnassignedVariablesAtEntry || | ||||||
| 
 | 				uninitializedVariableAccesses.size() > previousUninitializedVariableAccessess | ||||||
| 				if (auto const *tuple = dynamic_cast<TupleExpression const*>(expression)) | 			; | ||||||
| 					for (auto const& component: tuple->components()) |  | ||||||
| 						expressions.push(component.get()); |  | ||||||
| 				else if (auto const* identifier = dynamic_cast<Identifier const*>(expression)) |  | ||||||
| 					if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>( |  | ||||||
| 						identifier->annotation().referencedDeclaration |  | ||||||
| 					)) |  | ||||||
| 						result.insert(variableDeclaration); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	}; | ||||||
| 	return result; | 	map<CFGNode const*, NodeInfo> nodeInfos; | ||||||
| } | 	set<CFGNode const*> nodesToTraverse; | ||||||
|  | 	nodesToTraverse.insert(_entry); | ||||||
| 
 | 
 | ||||||
| void ControlFlowAnalyzer::checkUnassignedStorageReturnValues( | 	// Walk all paths starting from the nodes in ``nodesToTraverse`` until ``NodeInfo::propagateFrom``
 | ||||||
| 	FunctionDefinition const& _function, | 	// returns false for all exits, i.e. until all paths have been walked with maximal sets of unassigned
 | ||||||
| 	CFGNode const* _functionEntry, | 	// variables and accesses.
 | ||||||
| 	CFGNode const* _functionExit |  | ||||||
| ) const |  | ||||||
| { |  | ||||||
| 	if (_function.returnParameterList()->parameters().empty()) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	map<CFGNode const*, set<VariableDeclaration const*>> unassigned; |  | ||||||
| 
 |  | ||||||
| 	{ |  | ||||||
| 		auto& unassignedAtFunctionEntry = unassigned[_functionEntry]; |  | ||||||
| 		for (auto const& returnParameter: _function.returnParameterList()->parameters()) |  | ||||||
| 			if ( |  | ||||||
| 				returnParameter->type()->dataStoredIn(DataLocation::Storage) || |  | ||||||
| 				returnParameter->type()->category() == Type::Category::Mapping |  | ||||||
| 			) |  | ||||||
| 				unassignedAtFunctionEntry.insert(returnParameter.get()); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	stack<CFGNode const*> nodesToTraverse; |  | ||||||
| 	nodesToTraverse.push(_functionEntry); |  | ||||||
| 
 |  | ||||||
| 	// walk all paths from entry with maximal set of unassigned return values
 |  | ||||||
| 	while (!nodesToTraverse.empty()) | 	while (!nodesToTraverse.empty()) | ||||||
| 	{ | 	{ | ||||||
| 		auto node = nodesToTraverse.top(); | 		CFGNode const* currentNode = *nodesToTraverse.begin(); | ||||||
| 		nodesToTraverse.pop(); | 		nodesToTraverse.erase(nodesToTraverse.begin()); | ||||||
| 
 | 
 | ||||||
| 		auto& unassignedAtNode = unassigned[node]; | 		auto& nodeInfo = nodeInfos[currentNode]; | ||||||
| 
 | 		auto unassignedVariables = nodeInfo.unassignedVariablesAtEntry; | ||||||
| 		if (node->block.returnStatement != nullptr) | 		for (auto const& variableOccurrence: currentNode->variableOccurrences) | ||||||
| 			if (node->block.returnStatement->expression()) |  | ||||||
| 				unassignedAtNode.clear(); |  | ||||||
| 		if (!unassignedAtNode.empty()) |  | ||||||
| 		{ | 		{ | ||||||
| 			// kill all return values to which a value is assigned
 | 			switch (variableOccurrence.kind()) | ||||||
| 			for (auto const* variableDeclaration: variablesAssignedInNode(node)) | 			{ | ||||||
| 				unassignedAtNode.erase(variableDeclaration); | 				case VariableOccurrence::Kind::Assignment: | ||||||
| 
 | 					unassignedVariables.erase(&variableOccurrence.declaration()); | ||||||
| 			// kill all return values referenced in inline assembly
 | 					break; | ||||||
| 			// a reference is enough, checking whether there actually was an assignment might be overkill
 | 				case VariableOccurrence::Kind::InlineAssembly: | ||||||
| 			for (auto assembly: node->block.inlineAssemblyStatements) | 					// We consider all variables referenced in inline assembly as accessed.
 | ||||||
| 				for (auto const& ref: assembly->annotation().externalReferences) | 					// So far any reference is enough, but we might want to actually analyze
 | ||||||
| 					if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) | 					// the control flow in the assembly at some point.
 | ||||||
| 						unassignedAtNode.erase(variableDeclaration); | 				case VariableOccurrence::Kind::Access: | ||||||
|  | 				case VariableOccurrence::Kind::Return: | ||||||
|  | 					if (unassignedVariables.count(&variableOccurrence.declaration())) | ||||||
|  | 					{ | ||||||
|  | 						if (variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage)) | ||||||
|  | 							// Merely store the unassigned access. We do not generate an error right away, since this
 | ||||||
|  | 							// path might still always revert. It is only an error if this is propagated to the exit
 | ||||||
|  | 							// node of the function (i.e. there is a path with an uninitialized access).
 | ||||||
|  | 							nodeInfo.uninitializedVariableAccesses.insert(&variableOccurrence); | ||||||
|  | 					} | ||||||
|  | 					break; | ||||||
|  | 				case VariableOccurrence::Kind::Declaration: | ||||||
|  | 					unassignedVariables.insert(&variableOccurrence.declaration()); | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  | 		nodeInfo.unassignedVariablesAtExit = std::move(unassignedVariables); | ||||||
| 
 | 
 | ||||||
| 		for (auto const& exit: node->exits) | 		// Propagate changes to all exits and queue them for traversal, if needed.
 | ||||||
| 		{ | 		for (auto const& exit: currentNode->exits) | ||||||
| 			auto& unassignedAtExit = unassigned[exit]; | 			if (nodeInfos[exit].propagateFrom(nodeInfo)) | ||||||
| 			auto oldSize = unassignedAtExit.size(); | 				nodesToTraverse.insert(exit); | ||||||
| 			unassignedAtExit.insert(unassignedAtNode.begin(), unassignedAtNode.end()); |  | ||||||
| 			// (re)traverse an exit, if we are on a path with new unassigned return values to consider
 |  | ||||||
| 			// this will terminate, since there is only a finite number of unassigned return values
 |  | ||||||
| 			if (unassignedAtExit.size() > oldSize) |  | ||||||
| 				nodesToTraverse.push(exit); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (!unassigned[_functionExit].empty()) | 	auto const& exitInfo = nodeInfos[_exit]; | ||||||
|  | 	if (!exitInfo.uninitializedVariableAccesses.empty()) | ||||||
| 	{ | 	{ | ||||||
| 		vector<VariableDeclaration const*> unassignedOrdered( | 		vector<VariableOccurrence const*> uninitializedAccessesOrdered( | ||||||
| 			unassigned[_functionExit].begin(), | 			exitInfo.uninitializedVariableAccesses.begin(), | ||||||
| 			unassigned[_functionExit].end() | 			exitInfo.uninitializedVariableAccesses.end() | ||||||
| 		); | 		 ); | ||||||
| 		sort( | 		boost::range::sort( | ||||||
| 			unassignedOrdered.begin(), | 			uninitializedAccessesOrdered, | ||||||
| 			unassignedOrdered.end(), | 			[](VariableOccurrence const* lhs, VariableOccurrence const* rhs) -> bool | ||||||
| 			[](VariableDeclaration const* lhs, VariableDeclaration const* rhs) -> bool { | 			{ | ||||||
| 				return lhs->id() < rhs->id(); | 				return *lhs < *rhs; | ||||||
| 			} | 			} | ||||||
| 		); | 		); | ||||||
| 		for (auto const* returnVal: unassignedOrdered) | 
 | ||||||
|  | 		for (auto const* variableOccurrence: uninitializedAccessesOrdered) | ||||||
| 		{ | 		{ | ||||||
| 			SecondarySourceLocation ssl; | 			SecondarySourceLocation ssl; | ||||||
| 			for (CFGNode* lastNodeBeforeExit: _functionExit->entries) | 			if (variableOccurrence->occurrence()) | ||||||
| 				if (unassigned[lastNodeBeforeExit].count(returnVal)) | 				ssl.append("The variable was declared here.", variableOccurrence->declaration().location()); | ||||||
| 				{ |  | ||||||
| 					if (!!lastNodeBeforeExit->block.returnStatement) |  | ||||||
| 						ssl.append("Problematic return:", lastNodeBeforeExit->block.returnStatement->location()); |  | ||||||
| 					else |  | ||||||
| 						ssl.append("Problematic end of function:", _function.location()); |  | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 			m_errorReporter.typeError( | 			m_errorReporter.typeError( | ||||||
| 				returnVal->location(), | 				variableOccurrence->occurrence() ? | ||||||
|  | 					variableOccurrence->occurrence()->location() : | ||||||
|  | 					variableOccurrence->declaration().location(), | ||||||
| 				ssl, | 				ssl, | ||||||
| 				"This variable is of storage pointer type and might be returned without assignment and " | 				string("This variable is of storage pointer type and can be ") + | ||||||
| 				"could be used uninitialized. Assign the variable (potentially from itself) " | 				(variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") + | ||||||
| 				"to fix this error." | 				" without prior assignment." | ||||||
| 			); | 			); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -37,12 +37,8 @@ public: | |||||||
| 	bool visit(FunctionDefinition const& _function) override; | 	bool visit(FunctionDefinition const& _function) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	static std::set<VariableDeclaration const*> variablesAssignedInNode(CFGNode const *node); | 	/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
 | ||||||
| 	void checkUnassignedStorageReturnValues( | 	void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const; | ||||||
| 		FunctionDefinition const& _function, |  | ||||||
| 		CFGNode const* _functionEntry, |  | ||||||
| 		CFGNode const* _functionExit |  | ||||||
| 	) const; |  | ||||||
| 
 | 
 | ||||||
| 	CFG const& m_cfg; | 	CFG const& m_cfg; | ||||||
| 	langutil::ErrorReporter& m_errorReporter; | 	langutil::ErrorReporter& m_errorReporter; | ||||||
|  | |||||||
| @ -22,7 +22,10 @@ using namespace solidity; | |||||||
| using namespace std; | using namespace std; | ||||||
| 
 | 
 | ||||||
| ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): | ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): | ||||||
| 	m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry) | 	m_nodeContainer(_nodeContainer), | ||||||
|  | 	m_currentNode(_functionFlow.entry), | ||||||
|  | 	m_returnNode(_functionFlow.exit), | ||||||
|  | 	m_revertNode(_functionFlow.revert) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -37,28 +40,10 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow( | |||||||
| 	functionFlow->revert = _nodeContainer.newNode(); | 	functionFlow->revert = _nodeContainer.newNode(); | ||||||
| 	ControlFlowBuilder builder(_nodeContainer, *functionFlow); | 	ControlFlowBuilder builder(_nodeContainer, *functionFlow); | ||||||
| 	builder.appendControlFlow(_function); | 	builder.appendControlFlow(_function); | ||||||
| 	connect(builder.m_currentNode, functionFlow->exit); | 
 | ||||||
| 	return functionFlow; | 	return functionFlow; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| unique_ptr<ModifierFlow> ControlFlowBuilder::createModifierFlow( |  | ||||||
| 	CFG::NodeContainer& _nodeContainer, |  | ||||||
| 	ModifierDefinition const& _modifier |  | ||||||
| ) |  | ||||||
| { |  | ||||||
| 	auto modifierFlow = unique_ptr<ModifierFlow>(new ModifierFlow()); |  | ||||||
| 	modifierFlow->entry = _nodeContainer.newNode(); |  | ||||||
| 	modifierFlow->exit = _nodeContainer.newNode(); |  | ||||||
| 	modifierFlow->revert = _nodeContainer.newNode(); |  | ||||||
| 	modifierFlow->placeholderEntry = _nodeContainer.newNode(); |  | ||||||
| 	modifierFlow->placeholderExit = _nodeContainer.newNode(); |  | ||||||
| 	ControlFlowBuilder builder(_nodeContainer, *modifierFlow); |  | ||||||
| 	builder.appendControlFlow(_modifier); |  | ||||||
| 	connect(builder.m_currentNode, modifierFlow->exit); |  | ||||||
| 	return modifierFlow; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool ControlFlowBuilder::visit(BinaryOperation const& _operation) | bool ControlFlowBuilder::visit(BinaryOperation const& _operation) | ||||||
| { | { | ||||||
| 	solAssert(!!m_currentNode, ""); | 	solAssert(!!m_currentNode, ""); | ||||||
| @ -219,64 +204,24 @@ bool ControlFlowBuilder::visit(Continue const&) | |||||||
| bool ControlFlowBuilder::visit(Throw const&) | bool ControlFlowBuilder::visit(Throw const&) | ||||||
| { | { | ||||||
| 	solAssert(!!m_currentNode, ""); | 	solAssert(!!m_currentNode, ""); | ||||||
| 	solAssert(!!m_currentFunctionFlow.revert, ""); | 	solAssert(!!m_revertNode, ""); | ||||||
| 	connect(m_currentNode, m_currentFunctionFlow.revert); | 	connect(m_currentNode, m_revertNode); | ||||||
| 	m_currentNode = newLabel(); | 	m_currentNode = newLabel(); | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ControlFlowBuilder::visit(Block const&) |  | ||||||
| { |  | ||||||
| 	solAssert(!!m_currentNode, ""); |  | ||||||
| 	createLabelHere(); |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void ControlFlowBuilder::endVisit(Block const&) |  | ||||||
| { |  | ||||||
| 	solAssert(!!m_currentNode, ""); |  | ||||||
| 	createLabelHere(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool ControlFlowBuilder::visit(Return const& _return) |  | ||||||
| { |  | ||||||
| 	solAssert(!!m_currentNode, ""); |  | ||||||
| 	solAssert(!!m_currentFunctionFlow.exit, ""); |  | ||||||
| 	solAssert(!m_currentNode->block.returnStatement, ""); |  | ||||||
| 	m_currentNode->block.returnStatement = &_return; |  | ||||||
| 	connect(m_currentNode, m_currentFunctionFlow.exit); |  | ||||||
| 	m_currentNode = newLabel(); |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| bool ControlFlowBuilder::visit(PlaceholderStatement const&) | bool ControlFlowBuilder::visit(PlaceholderStatement const&) | ||||||
| { | { | ||||||
| 	solAssert(!!m_currentNode, ""); | 	solAssert(!!m_currentNode, ""); | ||||||
| 	auto modifierFlow = dynamic_cast<ModifierFlow const*>(&m_currentFunctionFlow); | 	solAssert(!!m_placeholderEntry, ""); | ||||||
| 	solAssert(!!modifierFlow, ""); | 	solAssert(!!m_placeholderExit, ""); | ||||||
| 
 |  | ||||||
| 	connect(m_currentNode, modifierFlow->placeholderEntry); |  | ||||||
| 
 | 
 | ||||||
|  | 	connect(m_currentNode, m_placeholderEntry); | ||||||
| 	m_currentNode = newLabel(); | 	m_currentNode = newLabel(); | ||||||
| 
 | 	connect(m_placeholderExit, m_currentNode); | ||||||
| 	connect(modifierFlow->placeholderExit, m_currentNode); |  | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ControlFlowBuilder::visitNode(ASTNode const& node) |  | ||||||
| { |  | ||||||
| 	solAssert(!!m_currentNode, ""); |  | ||||||
| 	if (auto const* expression = dynamic_cast<Expression const*>(&node)) |  | ||||||
| 		m_currentNode->block.expressions.emplace_back(expression); |  | ||||||
| 	else if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(&node)) |  | ||||||
| 		m_currentNode->block.variableDeclarations.emplace_back(variableDeclaration); |  | ||||||
| 	else if (auto const* assembly = dynamic_cast<InlineAssembly const*>(&node)) |  | ||||||
| 		m_currentNode->block.inlineAssemblyStatements.emplace_back(assembly); |  | ||||||
| 
 |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) | bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) | ||||||
| { | { | ||||||
| 	solAssert(!!m_currentNode, ""); | 	solAssert(!!m_currentNode, ""); | ||||||
| @ -286,19 +231,19 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) | |||||||
| 		switch (functionType->kind()) | 		switch (functionType->kind()) | ||||||
| 		{ | 		{ | ||||||
| 			case FunctionType::Kind::Revert: | 			case FunctionType::Kind::Revert: | ||||||
| 				solAssert(!!m_currentFunctionFlow.revert, ""); | 				solAssert(!!m_revertNode, ""); | ||||||
| 				_functionCall.expression().accept(*this); | 				_functionCall.expression().accept(*this); | ||||||
| 				ASTNode::listAccept(_functionCall.arguments(), *this); | 				ASTNode::listAccept(_functionCall.arguments(), *this); | ||||||
| 				connect(m_currentNode, m_currentFunctionFlow.revert); | 				connect(m_currentNode, m_revertNode); | ||||||
| 				m_currentNode = newLabel(); | 				m_currentNode = newLabel(); | ||||||
| 				return false; | 				return false; | ||||||
| 			case FunctionType::Kind::Require: | 			case FunctionType::Kind::Require: | ||||||
| 			case FunctionType::Kind::Assert: | 			case FunctionType::Kind::Assert: | ||||||
| 			{ | 			{ | ||||||
| 				solAssert(!!m_currentFunctionFlow.revert, ""); | 				solAssert(!!m_revertNode, ""); | ||||||
| 				_functionCall.expression().accept(*this); | 				_functionCall.expression().accept(*this); | ||||||
| 				ASTNode::listAccept(_functionCall.arguments(), *this); | 				ASTNode::listAccept(_functionCall.arguments(), *this); | ||||||
| 				connect(m_currentNode, m_currentFunctionFlow.revert); | 				connect(m_currentNode, m_revertNode); | ||||||
| 				auto nextNode = newLabel(); | 				auto nextNode = newLabel(); | ||||||
| 				connect(m_currentNode, nextNode); | 				connect(m_currentNode, nextNode); | ||||||
| 				m_currentNode = nextNode; | 				m_currentNode = nextNode; | ||||||
| @ -310,6 +255,183 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) | |||||||
| 	return ASTConstVisitor::visit(_functionCall); | 	return ASTConstVisitor::visit(_functionCall); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation) | ||||||
|  | { | ||||||
|  | 	if (auto arguments = _modifierInvocation.arguments()) | ||||||
|  | 		for (auto& argument: *arguments) | ||||||
|  | 			appendControlFlow(*argument); | ||||||
|  | 
 | ||||||
|  | 	auto modifierDefinition = dynamic_cast<ModifierDefinition const*>( | ||||||
|  | 		_modifierInvocation.name()->annotation().referencedDeclaration | ||||||
|  | 	); | ||||||
|  | 	if (!modifierDefinition) return false; | ||||||
|  | 	solAssert(!!modifierDefinition, ""); | ||||||
|  | 	solAssert(!!m_returnNode, ""); | ||||||
|  | 
 | ||||||
|  | 	m_placeholderEntry = newLabel(); | ||||||
|  | 	m_placeholderExit = newLabel(); | ||||||
|  | 
 | ||||||
|  | 	appendControlFlow(*modifierDefinition); | ||||||
|  | 	connect(m_currentNode, m_returnNode); | ||||||
|  | 
 | ||||||
|  | 	m_currentNode = m_placeholderEntry; | ||||||
|  | 	m_returnNode = m_placeholderExit; | ||||||
|  | 
 | ||||||
|  | 	m_placeholderEntry = nullptr; | ||||||
|  | 	m_placeholderExit = nullptr; | ||||||
|  | 
 | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition) | ||||||
|  | { | ||||||
|  | 	for (auto const& parameter: _functionDefinition.parameters()) | ||||||
|  | 		appendControlFlow(*parameter); | ||||||
|  | 
 | ||||||
|  | 	for (auto const& returnParameter: _functionDefinition.returnParameters()) | ||||||
|  | 	{ | ||||||
|  | 		appendControlFlow(*returnParameter); | ||||||
|  | 		m_returnNode->variableOccurrences.emplace_back( | ||||||
|  | 			*returnParameter, | ||||||
|  | 			VariableOccurrence::Kind::Return, | ||||||
|  | 			nullptr | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (auto const& modifier: _functionDefinition.modifiers()) | ||||||
|  | 		appendControlFlow(*modifier); | ||||||
|  | 
 | ||||||
|  | 	appendControlFlow(_functionDefinition.body()); | ||||||
|  | 
 | ||||||
|  | 	connect(m_currentNode, m_returnNode); | ||||||
|  | 	m_currentNode = nullptr; | ||||||
|  | 
 | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(Return const& _return) | ||||||
|  | { | ||||||
|  | 	solAssert(!!m_currentNode, ""); | ||||||
|  | 	solAssert(!!m_returnNode, ""); | ||||||
|  | 	if (_return.expression()) | ||||||
|  | 	{ | ||||||
|  | 		appendControlFlow(*_return.expression()); | ||||||
|  | 		// Returns with return expression are considered to be assignments to the return parameters.
 | ||||||
|  | 		for (auto returnParameter: _return.annotation().functionReturnParameters->parameters()) | ||||||
|  | 			m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 				*returnParameter, | ||||||
|  | 				VariableOccurrence::Kind::Assignment, | ||||||
|  | 				&_return | ||||||
|  | 			); | ||||||
|  | 	} | ||||||
|  | 	connect(m_currentNode, m_returnNode); | ||||||
|  | 	m_currentNode = newLabel(); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(FunctionTypeName const&) | ||||||
|  | { | ||||||
|  | 	// Do not visit the parameters and return values of a function type name.
 | ||||||
|  | 	// We do not want to consider them as variable declarations for the control flow graph.
 | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) | ||||||
|  | { | ||||||
|  | 	solAssert(!!m_currentNode, ""); | ||||||
|  | 	for (auto const& ref: _inlineAssembly.annotation().externalReferences) | ||||||
|  | 	{ | ||||||
|  | 		if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) | ||||||
|  | 			m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 				*variableDeclaration, | ||||||
|  | 				VariableOccurrence::Kind::InlineAssembly, | ||||||
|  | 				&_inlineAssembly | ||||||
|  | 			); | ||||||
|  | 	} | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) | ||||||
|  | { | ||||||
|  | 	solAssert(!!m_currentNode, ""); | ||||||
|  | 
 | ||||||
|  | 	m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 		_variableDeclaration, | ||||||
|  | 		VariableOccurrence::Kind::Declaration, | ||||||
|  | 		nullptr | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	// Handle declaration with immediate assignment.
 | ||||||
|  | 	if (_variableDeclaration.value()) | ||||||
|  | 		m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 			_variableDeclaration, | ||||||
|  | 			VariableOccurrence::Kind::Assignment, | ||||||
|  | 			_variableDeclaration.value().get() | ||||||
|  | 		); | ||||||
|  | 	// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
 | ||||||
|  | 	else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter()) | ||||||
|  | 		m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 			_variableDeclaration, | ||||||
|  | 			VariableOccurrence::Kind::Assignment, | ||||||
|  | 			nullptr | ||||||
|  | 		); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement) | ||||||
|  | { | ||||||
|  | 	solAssert(!!m_currentNode, ""); | ||||||
|  | 
 | ||||||
|  | 	for (auto const& var: _variableDeclarationStatement.declarations()) | ||||||
|  | 		if (var) | ||||||
|  | 			var->accept(*this); | ||||||
|  | 	if (_variableDeclarationStatement.initialValue()) | ||||||
|  | 	{ | ||||||
|  | 		_variableDeclarationStatement.initialValue()->accept(*this); | ||||||
|  | 		for (size_t i = 0; i < _variableDeclarationStatement.declarations().size(); i++) | ||||||
|  | 			if (auto const& var = _variableDeclarationStatement.declarations()[i]) | ||||||
|  | 			{ | ||||||
|  | 				auto expression = _variableDeclarationStatement.initialValue(); | ||||||
|  | 				if (auto tupleExpression = dynamic_cast<TupleExpression const*>(expression)) | ||||||
|  | 					if (tupleExpression->components().size() > 1) | ||||||
|  | 					{ | ||||||
|  | 						solAssert(tupleExpression->components().size() > i, ""); | ||||||
|  | 						expression = tupleExpression->components()[i].get(); | ||||||
|  | 					} | ||||||
|  | 				while (auto tupleExpression = dynamic_cast<TupleExpression const*>(expression)) | ||||||
|  | 					if (tupleExpression->components().size() == 1) | ||||||
|  | 						expression = tupleExpression->components().front().get(); | ||||||
|  | 					else | ||||||
|  | 						break; | ||||||
|  | 				m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 					*var, | ||||||
|  | 					VariableOccurrence::Kind::Assignment, | ||||||
|  | 					expression | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ControlFlowBuilder::visit(Identifier const& _identifier) | ||||||
|  | { | ||||||
|  | 	solAssert(!!m_currentNode, ""); | ||||||
|  | 
 | ||||||
|  | 	if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) | ||||||
|  | 		m_currentNode->variableOccurrences.emplace_back( | ||||||
|  | 			*variableDeclaration, | ||||||
|  | 			static_cast<Expression const&>(_identifier).annotation().lValueRequested ? | ||||||
|  | 			VariableOccurrence::Kind::Assignment : | ||||||
|  | 			VariableOccurrence::Kind::Access, | ||||||
|  | 			&_identifier | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) | void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) | ||||||
| { | { | ||||||
| 	_node.accept(*this); | 	_node.accept(*this); | ||||||
|  | |||||||
| @ -38,14 +38,11 @@ public: | |||||||
| 		CFG::NodeContainer& _nodeContainer, | 		CFG::NodeContainer& _nodeContainer, | ||||||
| 		FunctionDefinition const& _function | 		FunctionDefinition const& _function | ||||||
| 	); | 	); | ||||||
| 	static std::unique_ptr<ModifierFlow> createModifierFlow( |  | ||||||
| 		CFG::NodeContainer& _nodeContainer, |  | ||||||
| 		ModifierDefinition const& _modifier |  | ||||||
| 	); |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow); | 	explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow); | ||||||
| 
 | 
 | ||||||
|  | 	// Visits for constructing the control flow.
 | ||||||
| 	bool visit(BinaryOperation const& _operation) override; | 	bool visit(BinaryOperation const& _operation) override; | ||||||
| 	bool visit(Conditional const& _conditional) override; | 	bool visit(Conditional const& _conditional) override; | ||||||
| 	bool visit(IfStatement const& _ifStatement) override; | 	bool visit(IfStatement const& _ifStatement) override; | ||||||
| @ -54,12 +51,20 @@ private: | |||||||
| 	bool visit(Break const&) override; | 	bool visit(Break const&) override; | ||||||
| 	bool visit(Continue const&) override; | 	bool visit(Continue const&) override; | ||||||
| 	bool visit(Throw const&) override; | 	bool visit(Throw const&) override; | ||||||
| 	bool visit(Block const&) override; |  | ||||||
| 	void endVisit(Block const&) override; |  | ||||||
| 	bool visit(Return const& _return) override; |  | ||||||
| 	bool visit(PlaceholderStatement const&) override; | 	bool visit(PlaceholderStatement const&) override; | ||||||
| 	bool visit(FunctionCall const& _functionCall) override; | 	bool visit(FunctionCall const& _functionCall) override; | ||||||
|  | 	bool visit(ModifierInvocation const& _modifierInvocation) override; | ||||||
| 
 | 
 | ||||||
|  | 	// Visits for constructing the control flow as well as filling variable occurrences.
 | ||||||
|  | 	bool visit(FunctionDefinition const& _functionDefinition) override; | ||||||
|  | 	bool visit(Return const& _return) override; | ||||||
|  | 
 | ||||||
|  | 	// Visits for filling variable occurrences.
 | ||||||
|  | 	bool visit(FunctionTypeName const& _functionTypeName) override; | ||||||
|  | 	bool visit(InlineAssembly const& _inlineAssembly) override; | ||||||
|  | 	bool visit(VariableDeclaration const& _variableDeclaration) override; | ||||||
|  | 	bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; | ||||||
|  | 	bool visit(Identifier const& _identifier) override; | ||||||
| 
 | 
 | ||||||
| 	/// Appends the control flow of @a _node to the current control flow.
 | 	/// Appends the control flow of @a _node to the current control flow.
 | ||||||
| 	void appendControlFlow(ASTNode const& _node); | 	void appendControlFlow(ASTNode const& _node); | ||||||
| @ -73,9 +78,6 @@ private: | |||||||
| 	static void connect(CFGNode* _from, CFGNode* _to); | 	static void connect(CFGNode* _from, CFGNode* _to); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| protected: |  | ||||||
| 	bool visitNode(ASTNode const& node) override; |  | ||||||
| 
 |  | ||||||
| private: | private: | ||||||
| 
 | 
 | ||||||
| 	/// Splits the control flow starting at the current node into n paths.
 | 	/// Splits the control flow starting at the current node into n paths.
 | ||||||
| @ -114,17 +116,18 @@ private: | |||||||
| 
 | 
 | ||||||
| 	CFG::NodeContainer& m_nodeContainer; | 	CFG::NodeContainer& m_nodeContainer; | ||||||
| 
 | 
 | ||||||
| 	/// The control flow of the function that is currently parsed.
 |  | ||||||
| 	/// Note: this can also be a ModifierFlow
 |  | ||||||
| 	FunctionFlow const& m_currentFunctionFlow; |  | ||||||
| 
 |  | ||||||
| 	CFGNode* m_currentNode = nullptr; | 	CFGNode* m_currentNode = nullptr; | ||||||
|  | 	CFGNode* m_returnNode = nullptr; | ||||||
|  | 	CFGNode* m_revertNode = nullptr; | ||||||
| 
 | 
 | ||||||
| 	/// The current jump destination of break Statements.
 | 	/// The current jump destination of break Statements.
 | ||||||
| 	CFGNode* m_breakJump = nullptr; | 	CFGNode* m_breakJump = nullptr; | ||||||
| 	/// The current jump destination of continue Statements.
 | 	/// The current jump destination of continue Statements.
 | ||||||
| 	CFGNode* m_continueJump = nullptr; | 	CFGNode* m_continueJump = nullptr; | ||||||
| 
 | 
 | ||||||
|  | 	CFGNode* m_placeholderEntry = nullptr; | ||||||
|  | 	CFGNode* m_placeholderExit = nullptr; | ||||||
|  | 
 | ||||||
| 	/// Helper class that replaces the break and continue jump destinations for the
 | 	/// Helper class that replaces the break and continue jump destinations for the
 | ||||||
| 	/// current scope and restores the originals at the end of the scope.
 | 	/// current scope and restores the originals at the end of the scope.
 | ||||||
| 	class BreakContinueScope | 	class BreakContinueScope | ||||||
|  | |||||||
| @ -29,20 +29,14 @@ using namespace dev::solidity; | |||||||
| bool CFG::constructFlow(ASTNode const& _astRoot) | bool CFG::constructFlow(ASTNode const& _astRoot) | ||||||
| { | { | ||||||
| 	_astRoot.accept(*this); | 	_astRoot.accept(*this); | ||||||
| 	applyModifiers(); |  | ||||||
| 	return Error::containsOnlyWarnings(m_errorReporter.errors()); | 	return Error::containsOnlyWarnings(m_errorReporter.errors()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| bool CFG::visit(ModifierDefinition const& _modifier) |  | ||||||
| { |  | ||||||
| 	m_modifierControlFlow[&_modifier] = ControlFlowBuilder::createModifierFlow(m_nodeContainer, _modifier); |  | ||||||
| 	return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool CFG::visit(FunctionDefinition const& _function) | bool CFG::visit(FunctionDefinition const& _function) | ||||||
| { | { | ||||||
| 	m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); | 	if (_function.isImplemented()) | ||||||
|  | 		m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -57,81 +51,3 @@ CFGNode* CFG::NodeContainer::newNode() | |||||||
| 	m_nodes.emplace_back(new CFGNode()); | 	m_nodes.emplace_back(new CFGNode()); | ||||||
| 	return m_nodes.back().get(); | 	return m_nodes.back().get(); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| void CFG::applyModifiers() |  | ||||||
| { |  | ||||||
| 	for (auto const& function: m_functionControlFlow) |  | ||||||
| 	{ |  | ||||||
| 		for (auto const& modifierInvocation: boost::adaptors::reverse(function.first->modifiers())) |  | ||||||
| 		{ |  | ||||||
| 			if (auto modifierDefinition = dynamic_cast<ModifierDefinition const*>( |  | ||||||
| 				modifierInvocation->name()->annotation().referencedDeclaration |  | ||||||
| 			)) |  | ||||||
| 			{ |  | ||||||
| 				solAssert(m_modifierControlFlow.count(modifierDefinition), ""); |  | ||||||
| 				applyModifierFlowToFunctionFlow(*m_modifierControlFlow[modifierDefinition], function.second.get()); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CFG::applyModifierFlowToFunctionFlow( |  | ||||||
| 	ModifierFlow const& _modifierFlow, |  | ||||||
| 	FunctionFlow* _functionFlow |  | ||||||
| ) |  | ||||||
| { |  | ||||||
| 	solAssert(!!_functionFlow, ""); |  | ||||||
| 
 |  | ||||||
| 	map<CFGNode*, CFGNode*> copySrcToCopyDst; |  | ||||||
| 
 |  | ||||||
| 	// inherit the revert node of the function
 |  | ||||||
| 	copySrcToCopyDst[_modifierFlow.revert] = _functionFlow->revert; |  | ||||||
| 
 |  | ||||||
| 	// replace the placeholder nodes by the function entry and exit
 |  | ||||||
| 	copySrcToCopyDst[_modifierFlow.placeholderEntry] = _functionFlow->entry; |  | ||||||
| 	copySrcToCopyDst[_modifierFlow.placeholderExit] = _functionFlow->exit; |  | ||||||
| 
 |  | ||||||
| 	stack<CFGNode*> nodesToCopy; |  | ||||||
| 	nodesToCopy.push(_modifierFlow.entry); |  | ||||||
| 
 |  | ||||||
| 	// map the modifier entry to a new node that will become the new function entry
 |  | ||||||
| 	copySrcToCopyDst[_modifierFlow.entry] = m_nodeContainer.newNode(); |  | ||||||
| 
 |  | ||||||
| 	while (!nodesToCopy.empty()) |  | ||||||
| 	{ |  | ||||||
| 		CFGNode* copySrcNode = nodesToCopy.top(); |  | ||||||
| 		nodesToCopy.pop(); |  | ||||||
| 
 |  | ||||||
| 		solAssert(copySrcToCopyDst.count(copySrcNode), ""); |  | ||||||
| 
 |  | ||||||
| 		CFGNode* copyDstNode = copySrcToCopyDst[copySrcNode]; |  | ||||||
| 
 |  | ||||||
| 		copyDstNode->block = copySrcNode->block; |  | ||||||
| 		for (auto const& entry: copySrcNode->entries) |  | ||||||
| 		{ |  | ||||||
| 			if (!copySrcToCopyDst.count(entry)) |  | ||||||
| 			{ |  | ||||||
| 				copySrcToCopyDst[entry] = m_nodeContainer.newNode(); |  | ||||||
| 				nodesToCopy.push(entry); |  | ||||||
| 			} |  | ||||||
| 			copyDstNode->entries.emplace_back(copySrcToCopyDst[entry]); |  | ||||||
| 		} |  | ||||||
| 		for (auto const& exit: copySrcNode->exits) |  | ||||||
| 		{ |  | ||||||
| 			if (!copySrcToCopyDst.count(exit)) |  | ||||||
| 			{ |  | ||||||
| 				copySrcToCopyDst[exit] = m_nodeContainer.newNode(); |  | ||||||
| 				nodesToCopy.push(exit); |  | ||||||
| 			} |  | ||||||
| 			copyDstNode->exits.emplace_back(copySrcToCopyDst[exit]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// if the modifier control flow never reached its exit node,
 |  | ||||||
| 	// we need to create a new (disconnected) exit node now
 |  | ||||||
| 	if (!copySrcToCopyDst.count(_modifierFlow.exit)) |  | ||||||
| 		copySrcToCopyDst[_modifierFlow.exit] = m_nodeContainer.newNode(); |  | ||||||
| 
 |  | ||||||
| 	_functionFlow->entry = copySrcToCopyDst[_modifierFlow.entry]; |  | ||||||
| 	_functionFlow->exit = copySrcToCopyDst[_modifierFlow.exit]; |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -31,25 +31,57 @@ namespace dev | |||||||
| namespace solidity | namespace solidity | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| /** Basic Control Flow Block.
 | /** Occurrence of a variable in a block of control flow.
 | ||||||
|  * Basic block of control flow. Consists of a set of (unordered) AST nodes |   * Stores the declaration of the referenced variable, the | ||||||
|  * for which control flow is always linear. A basic control flow block |   * kind of the occurrence and possibly the node at which | ||||||
|  * encompasses at most one scope. Reverts are considered to break the control |   * it occurred. | ||||||
|  * flow. |   */ | ||||||
|  * @todo Handle function calls correctly. So far function calls are not considered | class VariableOccurrence | ||||||
|  * to change the control flow. |  | ||||||
|  */ |  | ||||||
| struct ControlFlowBlock |  | ||||||
| { | { | ||||||
| 	/// All variable declarations inside this control flow block.
 | public: | ||||||
| 	std::vector<VariableDeclaration const*> variableDeclarations; | 	enum class Kind | ||||||
| 	/// All expressions inside this control flow block (this includes all subexpressions!).
 | 	{ | ||||||
| 	std::vector<Expression const*> expressions; | 		Declaration, | ||||||
| 	/// All inline assembly statements inside in this control flow block.
 | 		Access, | ||||||
| 	std::vector<InlineAssembly const*> inlineAssemblyStatements; | 		Return, | ||||||
| 	/// If control flow returns in this node, the return statement is stored in returnStatement,
 | 		Assignment, | ||||||
| 	/// otherwise returnStatement is nullptr.
 | 		InlineAssembly | ||||||
| 	Return const* returnStatement = nullptr; | 	}; | ||||||
|  | 	VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence): | ||||||
|  | 		m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence) | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/// Defines a deterministic order on variable occurrences.
 | ||||||
|  | 	bool operator<(VariableOccurrence const& _rhs) const | ||||||
|  | 	{ | ||||||
|  | 		if (m_occurrence && _rhs.m_occurrence) | ||||||
|  | 		{ | ||||||
|  | 			if (m_occurrence->id() < _rhs.m_occurrence->id()) return true; | ||||||
|  | 			if (_rhs.m_occurrence->id() < m_occurrence->id()) return false; | ||||||
|  | 		} | ||||||
|  | 		else if (_rhs.m_occurrence) | ||||||
|  | 			return true; | ||||||
|  | 		else if (m_occurrence) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 		using KindCompareType = std::underlying_type<VariableOccurrence::Kind>::type; | ||||||
|  | 		return | ||||||
|  | 			std::make_pair(m_declaration.id(), static_cast<KindCompareType>(m_occurrenceKind)) < | ||||||
|  | 			std::make_pair(_rhs.m_declaration.id(), static_cast<KindCompareType>(_rhs.m_occurrenceKind)) | ||||||
|  | 		; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	VariableDeclaration const& declaration() const { return m_declaration; } | ||||||
|  | 	Kind kind() const { return m_occurrenceKind; }; | ||||||
|  | 	ASTNode const* occurrence() const { return m_occurrence; } | ||||||
|  | private: | ||||||
|  | 	/// Declaration of the occurring variable.
 | ||||||
|  | 	VariableDeclaration const& m_declaration; | ||||||
|  | 	/// Kind of occurrence.
 | ||||||
|  | 	Kind m_occurrenceKind = Kind::Access; | ||||||
|  | 	/// AST node at which the variable occurred, if available (may be nullptr).
 | ||||||
|  | 	ASTNode const* m_occurrence = nullptr; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** Node of the Control Flow Graph.
 | /** Node of the Control Flow Graph.
 | ||||||
| @ -64,8 +96,8 @@ struct CFGNode | |||||||
| 	/// Exit nodes. All CFG nodes to which control flow may continue after this node.
 | 	/// Exit nodes. All CFG nodes to which control flow may continue after this node.
 | ||||||
| 	std::vector<CFGNode*> exits; | 	std::vector<CFGNode*> exits; | ||||||
| 
 | 
 | ||||||
| 	/// Control flow in the node.
 | 	/// Variable occurrences in the node.
 | ||||||
| 	ControlFlowBlock block; | 	std::vector<VariableOccurrence> variableOccurrences; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** Describes the control flow of a function. */ | /** Describes the control flow of a function. */ | ||||||
| @ -85,19 +117,6 @@ struct FunctionFlow | |||||||
| 	CFGNode* revert = nullptr; | 	CFGNode* revert = nullptr; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** Describes the control flow of a modifier.
 |  | ||||||
|  * Every placeholder breaks the control flow. The node preceding the |  | ||||||
|  * placeholder is assigned placeholderEntry as exit and the node |  | ||||||
|  * following the placeholder is assigned placeholderExit as entry. |  | ||||||
|  */ |  | ||||||
| struct ModifierFlow: FunctionFlow |  | ||||||
| { |  | ||||||
| 	/// Control flow leading towards a placeholder exit in placeholderEntry.
 |  | ||||||
| 	CFGNode* placeholderEntry = nullptr; |  | ||||||
| 	/// Control flow coming from a placeholder enter from placeholderExit.
 |  | ||||||
| 	CFGNode* placeholderExit = nullptr; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class CFG: private ASTConstVisitor | class CFG: private ASTConstVisitor | ||||||
| { | { | ||||||
| public: | public: | ||||||
| @ -105,7 +124,6 @@ public: | |||||||
| 
 | 
 | ||||||
| 	bool constructFlow(ASTNode const& _astRoot); | 	bool constructFlow(ASTNode const& _astRoot); | ||||||
| 
 | 
 | ||||||
| 	bool visit(ModifierDefinition const& _modifier) override; |  | ||||||
| 	bool visit(FunctionDefinition const& _function) override; | 	bool visit(FunctionDefinition const& _function) override; | ||||||
| 
 | 
 | ||||||
| 	FunctionFlow const& functionFlow(FunctionDefinition const& _function) const; | 	FunctionFlow const& functionFlow(FunctionDefinition const& _function) const; | ||||||
| @ -118,20 +136,6 @@ public: | |||||||
| 		std::vector<std::unique_ptr<CFGNode>> m_nodes; | 		std::vector<std::unique_ptr<CFGNode>> m_nodes; | ||||||
| 	}; | 	}; | ||||||
| private: | private: | ||||||
| 	/// Initially the control flow for all functions *ignoring* modifiers and for
 |  | ||||||
| 	/// all modifiers is constructed. Afterwards the control flow of functions
 |  | ||||||
| 	/// is adjusted by applying all modifiers.
 |  | ||||||
| 	void applyModifiers(); |  | ||||||
| 
 |  | ||||||
| 	/// Creates a copy of the modifier flow @a _modifierFlow, while replacing the
 |  | ||||||
| 	/// placeholder entry and exit with the function entry and exit, as well as
 |  | ||||||
| 	/// replacing the modifier revert node with the function's revert node.
 |  | ||||||
| 	/// The resulting control flow is the new function flow with the modifier applied.
 |  | ||||||
| 	/// @a _functionFlow is updated in-place.
 |  | ||||||
| 	void applyModifierFlowToFunctionFlow( |  | ||||||
| 		ModifierFlow const& _modifierFlow, |  | ||||||
| 		FunctionFlow* _functionFlow |  | ||||||
| 	); |  | ||||||
| 
 | 
 | ||||||
| 	langutil::ErrorReporter& m_errorReporter; | 	langutil::ErrorReporter& m_errorReporter; | ||||||
| 
 | 
 | ||||||
| @ -141,7 +145,6 @@ private: | |||||||
| 	NodeContainer m_nodeContainer; | 	NodeContainer m_nodeContainer; | ||||||
| 
 | 
 | ||||||
| 	std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow; | 	std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow; | ||||||
| 	std::map<ModifierDefinition const*, std::unique_ptr<ModifierFlow>> m_modifierControlFlow; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,4 +2,4 @@ contract C { | |||||||
|     function f() internal pure returns (mapping(uint=>uint) storage r) { } |     function f() internal pure returns (mapping(uint=>uint) storage r) { } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (53-82): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (53-82): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -2,4 +2,4 @@ contract C { | |||||||
|     function f() internal pure returns (mapping(uint=>uint) storage) {} |     function f() internal pure returns (mapping(uint=>uint) storage) {} | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (53-80): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (53-80): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -7,4 +7,4 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (87-96): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (87-96): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -1,26 +0,0 @@ | |||||||
| contract C { |  | ||||||
|     struct S { bool f; } |  | ||||||
|     S s; |  | ||||||
|     function f() internal returns (S storage c) { |  | ||||||
|         assembly { |  | ||||||
|             sstore(c_slot, sload(s_slot)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     function g(bool flag) internal returns (S storage c) { |  | ||||||
|         // control flow in assembly will not be analyzed for now, |  | ||||||
|         // so this will not issue an error |  | ||||||
|         assembly { |  | ||||||
|             if flag { |  | ||||||
|                 sstore(c_slot, sload(s_slot)) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     function h() internal returns (S storage c) { |  | ||||||
|         // any reference from assembly will be sufficient for now, |  | ||||||
|         // so this will not issue an error |  | ||||||
|         assembly { |  | ||||||
|             sstore(s_slot, sload(c_slot)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| // ---- |  | ||||||
| @ -45,8 +45,8 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (87-98): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (223-234): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (440-451): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (654-665): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (871-882): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -12,5 +12,5 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (87-98): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (182-193): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (182-193): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -14,5 +14,5 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (96-107): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (186-197): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (186-197): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -18,5 +18,5 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (249-258): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (249-258): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (367-376): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (367-376): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -13,6 +13,6 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (87-98): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (176-187): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (176-187): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (264-275): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (264-275): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -9,5 +9,5 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (96-107): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
| // TypeError: (200-211): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (200-211): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -8,4 +8,4 @@ contract C { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // TypeError: (87-98): This variable is of storage pointer type and might be returned without assignment and could be used uninitialized. Assign the variable (potentially from itself) to fix this error. | // TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. | ||||||
|  | |||||||
| @ -0,0 +1,8 @@ | |||||||
|  | contract C { | ||||||
|  |     function f() internal view returns(uint[] storage a) | ||||||
|  |     { | ||||||
|  |         uint b = a[0]; | ||||||
|  |         revert(); | ||||||
|  |         b; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | contract C { | ||||||
|  | 	uint[] r; | ||||||
|  |     function f() internal view returns (uint[] storage s) { | ||||||
|  |         assembly { pop(s_slot) } | ||||||
|  |         s = r; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (92-126): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | contract C { | ||||||
|  |     // Make sure function parameters and return values are not considered | ||||||
|  |     // for uninitialized return detection in the control flow analysis. | ||||||
|  |     function f(function(uint[] storage) internal returns (uint[] storage)) internal pure | ||||||
|  |     returns (function(uint[] storage) internal returns (uint[] storage)) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | contract C { | ||||||
|  |     modifier m1(uint[] storage a) { _; } | ||||||
|  |     modifier m2(uint[] storage a) { _; } | ||||||
|  |     uint[] s; | ||||||
|  |     function f() m1(b) m2(b = s) internal view returns (uint[] storage b) {} | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (129-130): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | contract C { | ||||||
|  |     modifier m1(uint[] storage a) { _; } | ||||||
|  |     modifier m2(uint[] storage a) { _; } | ||||||
|  |     uint[] s; | ||||||
|  |     function f() m1(b = s) m2(b) internal view returns (uint[] storage b) {} | ||||||
|  | } | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | contract C { | ||||||
|  |     uint[] s; | ||||||
|  |     modifier mod(uint[] storage b) { | ||||||
|  |         _; | ||||||
|  |         b[0] = 0; | ||||||
|  |     } | ||||||
|  |     function f() mod(a) internal returns (uint[] storage a) | ||||||
|  |     { | ||||||
|  | 		a = s; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | contract C { | ||||||
|  |     uint[] s; | ||||||
|  |     modifier mod(uint[] storage b) { | ||||||
|  |         b[0] = 0; | ||||||
|  |         _; | ||||||
|  |     } | ||||||
|  |     function f() mod(a) internal returns (uint[] storage a) | ||||||
|  |     { | ||||||
|  | 		a = s; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | contract C { | ||||||
|  |     uint[] s; | ||||||
|  |     function f() internal returns (uint[] storage a) | ||||||
|  |     { | ||||||
|  |         a[0] = 0; | ||||||
|  |         a = s; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (94-95): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | contract C { | ||||||
|  |     struct S { uint a; } | ||||||
|  |     S s; | ||||||
|  |     function f() internal returns (S storage r) | ||||||
|  |     { | ||||||
|  |         r.a = 0; | ||||||
|  |         r = s; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // TypeError: (109-110): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | contract C { | ||||||
|  |     uint[] s; | ||||||
|  |     function f() internal returns (uint[] storage a) | ||||||
|  |     { | ||||||
|  |         revert(); | ||||||
|  |         a[0] = 0; | ||||||
|  |         a = s; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
| @ -8,3 +8,9 @@ library L { | |||||||
|     function i(uint[] calldata, uint[] storage) external pure returns (S storage x) {return x; } |     function i(uint[] calldata, uint[] storage) external pure returns (S storage x) {return x; } | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
|  | // TypeError: (197-198): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | // TypeError: (203-204): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | // TypeError: (359-360): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | // TypeError: (365-366): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | // TypeError: (460-461): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | // TypeError: (557-558): This variable is of storage pointer type and can be accessed without prior assignment. | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user