mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #12082 from ethereum/controlFlowSideEffectsUserDefined
Control flow side effects of user defined functions
This commit is contained in:
		
						commit
						1e630fc584
					
				| @ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall) | ||||
| 	yul::ASTWalker::operator()(_functionCall); | ||||
| 
 | ||||
| 	if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name)) | ||||
| 		if (builtinFunction->controlFlowSideEffects.terminates) | ||||
| 		{ | ||||
| 			if (builtinFunction->controlFlowSideEffects.reverts) | ||||
| 				connect(m_currentNode, m_revertNode); | ||||
| 			else | ||||
| 				connect(m_currentNode, m_transactionReturnNode); | ||||
| 	{ | ||||
| 		if (builtinFunction->controlFlowSideEffects.canTerminate) | ||||
| 			connect(m_currentNode, m_transactionReturnNode); | ||||
| 		if (builtinFunction->controlFlowSideEffects.canRevert) | ||||
| 			connect(m_currentNode, m_revertNode); | ||||
| 		if (!builtinFunction->controlFlowSideEffects.canContinue) | ||||
| 			m_currentNode = newLabel(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(yul::FunctionDefinition const&) | ||||
|  | ||||
| @ -34,6 +34,9 @@ add_library(yul | ||||
| 	AssemblyStack.cpp | ||||
| 	CompilabilityChecker.cpp | ||||
| 	CompilabilityChecker.h | ||||
| 	ControlFlowSideEffects.h | ||||
| 	ControlFlowSideEffectsCollector.cpp | ||||
| 	ControlFlowSideEffectsCollector.h | ||||
| 	Dialect.cpp | ||||
| 	Dialect.h | ||||
| 	Exceptions.h | ||||
|  | ||||
| @ -18,22 +18,32 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <set> | ||||
| 
 | ||||
| namespace solidity::yul | ||||
| { | ||||
| 
 | ||||
| /**
 | ||||
|  * Side effects of code related to control flow. | ||||
|  * Side effects of a user-defined or builtin function. | ||||
|  * | ||||
|  * Each of the three booleans represents a reachability condition. There is an implied | ||||
|  * fourth alternative, which is going out of gas while executing the function. Since | ||||
|  * this can always happen and depends on the supply of gas, it is not considered. | ||||
|  * | ||||
|  * If all three booleans are false, it means that the function always leads to infinite | ||||
|  * recursion. | ||||
|  */ | ||||
| struct ControlFlowSideEffects | ||||
| { | ||||
| 	/// If true, this code terminates the control flow.
 | ||||
| 	/// State may or may not be reverted as indicated by the ``reverts`` flag.
 | ||||
| 	bool terminates = false; | ||||
| 	/// If true, this code reverts all state changes in the transaction.
 | ||||
| 	/// Whenever this is true, ``terminates`` has to be true as well.
 | ||||
| 	bool reverts = false; | ||||
| 	/// If true, the function contains at least one reachable branch that terminates successfully.
 | ||||
| 	bool canTerminate = false; | ||||
| 	/// If true, the function contains at least one reachable branch that reverts.
 | ||||
| 	bool canRevert = false; | ||||
| 	/// If true, the function has a regular outgoing control-flow.
 | ||||
| 	bool canContinue = true; | ||||
| 
 | ||||
| 	bool terminatesOrReverts() const | ||||
| 	{ | ||||
| 		return (canTerminate || canRevert) && !canContinue; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										274
									
								
								libyul/ControlFlowSideEffectsCollector.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								libyul/ControlFlowSideEffectsCollector.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,274 @@ | ||||
| /*
 | ||||
| 	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/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #include <libyul/ControlFlowSideEffectsCollector.h> | ||||
| 
 | ||||
| #include <libyul/optimiser/FunctionDefinitionCollector.h> | ||||
| 
 | ||||
| #include <libyul/AST.h> | ||||
| #include <libyul/Dialect.h> | ||||
| 
 | ||||
| #include <libsolutil/Common.h> | ||||
| #include <libsolutil/CommonData.h> | ||||
| #include <libsolutil/Algorithms.h> | ||||
| 
 | ||||
| #include <range/v3/view/map.hpp> | ||||
| #include <range/v3/view/reverse.hpp> | ||||
| #include <range/v3/algorithm/find_if.hpp> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace solidity::yul; | ||||
| 
 | ||||
| 
 | ||||
| ControlFlowBuilder::ControlFlowBuilder(Block const& _ast) | ||||
| { | ||||
| 	for (auto const& statement: _ast.statements) | ||||
| 		if (auto const* function = get_if<FunctionDefinition>(&statement)) | ||||
| 			(*this)(*function); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(FunctionCall const& _functionCall) | ||||
| { | ||||
| 	walkVector(_functionCall.arguments | ranges::views::reverse); | ||||
| 	newConnectedNode(); | ||||
| 	m_currentNode->functionCall = _functionCall.functionName.name; | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(If const& _if) | ||||
| { | ||||
| 	visit(*_if.condition); | ||||
| 	ControlFlowNode* node = m_currentNode; | ||||
| 	(*this)(_if.body); | ||||
| 	newConnectedNode(); | ||||
| 	node->successors.emplace_back(m_currentNode); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(Switch const& _switch) | ||||
| { | ||||
| 	visit(*_switch.expression); | ||||
| 	ControlFlowNode* initialNode = m_currentNode; | ||||
| 	ControlFlowNode* finalNode = newNode(); | ||||
| 
 | ||||
| 	if (_switch.cases.back().value) | ||||
| 		initialNode->successors.emplace_back(finalNode); | ||||
| 
 | ||||
| 	for (Case const& case_: _switch.cases) | ||||
| 	{ | ||||
| 		m_currentNode = initialNode; | ||||
| 		(*this)(case_.body); | ||||
| 		newConnectedNode(); | ||||
| 		m_currentNode->successors.emplace_back(finalNode); | ||||
| 	} | ||||
| 	m_currentNode = finalNode; | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(FunctionDefinition const& _function) | ||||
| { | ||||
| 	ScopedSaveAndRestore currentNode(m_currentNode, nullptr); | ||||
| 	yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used."); | ||||
| 
 | ||||
| 	FunctionFlow flow; | ||||
| 	flow.exit = newNode(); | ||||
| 	m_currentNode = newNode(); | ||||
| 	flow.entry = m_currentNode; | ||||
| 	m_leave = flow.exit; | ||||
| 
 | ||||
| 	(*this)(_function.body); | ||||
| 
 | ||||
| 	m_currentNode->successors.emplace_back(flow.exit); | ||||
| 
 | ||||
| 	m_functionFlows[_function.name] = move(flow); | ||||
| 
 | ||||
| 	m_leave = nullptr; | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(ForLoop const& _for) | ||||
| { | ||||
| 	ScopedSaveAndRestore scopedBreakNode(m_break, nullptr); | ||||
| 	ScopedSaveAndRestore scopedContinueNode(m_continue, nullptr); | ||||
| 
 | ||||
| 	(*this)(_for.pre); | ||||
| 
 | ||||
| 	ControlFlowNode* breakNode = newNode(); | ||||
| 	m_break = breakNode; | ||||
| 	ControlFlowNode* continueNode = newNode(); | ||||
| 	m_continue = continueNode; | ||||
| 
 | ||||
| 	newConnectedNode(); | ||||
| 	ControlFlowNode* loopNode = m_currentNode; | ||||
| 	visit(*_for.condition); | ||||
| 	m_currentNode->successors.emplace_back(m_break); | ||||
| 	newConnectedNode(); | ||||
| 
 | ||||
| 	(*this)(_for.body); | ||||
| 
 | ||||
| 	m_currentNode->successors.emplace_back(m_continue); | ||||
| 	m_currentNode = continueNode; | ||||
| 
 | ||||
| 	(*this)(_for.post); | ||||
| 	m_currentNode->successors.emplace_back(loopNode); | ||||
| 
 | ||||
| 	m_currentNode = breakNode; | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(Break const&) | ||||
| { | ||||
| 	yulAssert(m_break); | ||||
| 	m_currentNode->successors.emplace_back(m_break); | ||||
| 	m_currentNode = newNode(); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(Continue const&) | ||||
| { | ||||
| 	yulAssert(m_continue); | ||||
| 	m_currentNode->successors.emplace_back(m_continue); | ||||
| 	m_currentNode = newNode(); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::operator()(Leave const&) | ||||
| { | ||||
| 	yulAssert(m_leave); | ||||
| 	m_currentNode->successors.emplace_back(m_leave); | ||||
| 	m_currentNode = newNode(); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowBuilder::newConnectedNode() | ||||
| { | ||||
| 	ControlFlowNode* node = newNode(); | ||||
| 	m_currentNode->successors.emplace_back(node); | ||||
| 	m_currentNode = node; | ||||
| } | ||||
| 
 | ||||
| ControlFlowNode* ControlFlowBuilder::newNode() | ||||
| { | ||||
| 	m_nodes.emplace_back(make_shared<ControlFlowNode>()); | ||||
| 	return m_nodes.back().get(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector( | ||||
| 	Dialect const& _dialect, | ||||
| 	Block const& _ast | ||||
| ): | ||||
| 	m_dialect(_dialect), | ||||
| 	m_cfgBuilder(_ast) | ||||
| { | ||||
| 	for (auto&& [name, flow]: m_cfgBuilder.functionFlows()) | ||||
| 	{ | ||||
| 		yulAssert(!flow.entry->functionCall); | ||||
| 		m_processedNodes[name] = {}; | ||||
| 		m_pendingNodes[name].push_front(flow.entry); | ||||
| 		m_functionSideEffects[name] = {false, false, false}; | ||||
| 	} | ||||
| 
 | ||||
| 	// Process functions while we have progress. For now, we are only interested
 | ||||
| 	// in `canContinue`.
 | ||||
| 	bool progress = true; | ||||
| 	while (progress) | ||||
| 	{ | ||||
| 		progress = false; | ||||
| 		for (auto const& functionName: m_pendingNodes | ranges::views::keys) | ||||
| 			if (processFunction(functionName)) | ||||
| 				progress = true; | ||||
| 	} | ||||
| 
 | ||||
| 	// No progress anymore: All remaining nodes are calls
 | ||||
| 	// to functions that always recurse.
 | ||||
| 	// If we have not set `canContinue` by now, the function's exit
 | ||||
| 	// is not reachable.
 | ||||
| 
 | ||||
| 	for (auto&& [functionName, calls]: m_functionCalls) | ||||
| 	{ | ||||
| 		ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName]; | ||||
| 		auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable { | ||||
| 			if (sideEffects.canTerminate && sideEffects.canRevert) | ||||
| 				return; | ||||
| 			if (!visited.insert(_function).second) | ||||
| 				return; | ||||
| 
 | ||||
| 			ControlFlowSideEffects const* calledSideEffects = nullptr; | ||||
| 			if (BuiltinFunction const* f = _dialect.builtin(_function)) | ||||
| 				calledSideEffects = &f->controlFlowSideEffects; | ||||
| 			else | ||||
| 				calledSideEffects = &m_functionSideEffects.at(_function); | ||||
| 
 | ||||
| 			if (calledSideEffects->canTerminate) | ||||
| 				sideEffects.canTerminate = true; | ||||
| 			if (calledSideEffects->canRevert) | ||||
| 				sideEffects.canRevert = true; | ||||
| 
 | ||||
| 			for (YulString callee: util::valueOrDefault(m_functionCalls, _function)) | ||||
| 				_recurse(callee, _recurse); | ||||
| 		}; | ||||
| 		for (auto const& call: calls) | ||||
| 			_visit(call, _visit); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| bool ControlFlowSideEffectsCollector::processFunction(YulString _name) | ||||
| { | ||||
| 	bool progress = false; | ||||
| 	while (ControlFlowNode const* node = nextProcessableNode(_name)) | ||||
| 	{ | ||||
| 		if (node == m_cfgBuilder.functionFlows().at(_name).exit) | ||||
| 		{ | ||||
| 			m_functionSideEffects[_name].canContinue = true; | ||||
| 			return true; | ||||
| 		} | ||||
| 		for (ControlFlowNode const* s: node->successors) | ||||
| 			recordReachabilityAndQueue(_name, s); | ||||
| 
 | ||||
| 		progress = true; | ||||
| 	} | ||||
| 	return progress; | ||||
| } | ||||
| 
 | ||||
| ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName) | ||||
| { | ||||
| 	std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName]; | ||||
| 	auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) { | ||||
| 		return !_node->functionCall || sideEffects(*_node->functionCall).canContinue; | ||||
| 	}); | ||||
| 	if (it == nodes.end()) | ||||
| 		return nullptr; | ||||
| 
 | ||||
| 	ControlFlowNode const* node = *it; | ||||
| 	nodes.erase(it); | ||||
| 	return node; | ||||
| } | ||||
| 
 | ||||
| ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const | ||||
| { | ||||
| 	if (auto const* builtin = m_dialect.builtin(_functionName)) | ||||
| 		return builtin->controlFlowSideEffects; | ||||
| 	else | ||||
| 		return m_functionSideEffects.at(_functionName); | ||||
| } | ||||
| 
 | ||||
| void ControlFlowSideEffectsCollector::recordReachabilityAndQueue( | ||||
| 	YulString _functionName, | ||||
| 	ControlFlowNode const* _node | ||||
| ) | ||||
| { | ||||
| 	if (_node->functionCall) | ||||
| 		m_functionCalls[_functionName].insert(*_node->functionCall); | ||||
| 	if (m_processedNodes[_functionName].insert(_node).second) | ||||
| 		m_pendingNodes.at(_functionName).push_front(_node); | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										130
									
								
								libyul/ControlFlowSideEffectsCollector.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								libyul/ControlFlowSideEffectsCollector.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| /*
 | ||||
| 	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/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libyul/optimiser/ASTWalker.h> | ||||
| #include <libyul/ControlFlowSideEffects.h> | ||||
| 
 | ||||
| #include <set> | ||||
| #include <stack> | ||||
| #include <optional> | ||||
| #include <list> | ||||
| 
 | ||||
| namespace solidity::yul | ||||
| { | ||||
| 
 | ||||
| struct Dialect; | ||||
| 
 | ||||
| struct ControlFlowNode | ||||
| { | ||||
| 	std::vector<ControlFlowNode const*> successors; | ||||
| 	/// Name of the called function if the node calls a function.
 | ||||
| 	std::optional<YulString> functionCall; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * The control flow of a function with entry and exit nodes. | ||||
|  */ | ||||
| struct FunctionFlow | ||||
| { | ||||
| 	ControlFlowNode const* entry; | ||||
| 	ControlFlowNode const* exit; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Requires: Disambiguator, Function Hoister. | ||||
|  */ | ||||
| class ControlFlowBuilder: private ASTWalker | ||||
| { | ||||
| public: | ||||
| 	/// Computes the control-flows of all function defined in the block.
 | ||||
| 	/// Assumes the functions are hoisted to the topmost block.
 | ||||
| 	explicit ControlFlowBuilder(Block const& _ast); | ||||
| 	std::map<YulString, FunctionFlow> const& functionFlows() const { return m_functionFlows; } | ||||
| 
 | ||||
| private: | ||||
| 	using ASTWalker::operator(); | ||||
| 	void operator()(FunctionCall const& _functionCall) override; | ||||
| 	void operator()(If const& _if) override; | ||||
| 	void operator()(Switch const& _switch) override; | ||||
| 	void operator()(FunctionDefinition const& _functionDefinition) override; | ||||
| 	void operator()(ForLoop const& _forLoop) override; | ||||
| 	void operator()(Break const& _break) override; | ||||
| 	void operator()(Continue const& _continue) override; | ||||
| 	void operator()(Leave const& _leaveStatement) override; | ||||
| 
 | ||||
| 	void newConnectedNode(); | ||||
| 	ControlFlowNode* newNode(); | ||||
| 
 | ||||
| 	std::vector<std::shared_ptr<ControlFlowNode>> m_nodes; | ||||
| 
 | ||||
| 	ControlFlowNode* m_currentNode = nullptr; | ||||
| 	ControlFlowNode const* m_leave = nullptr; | ||||
| 	ControlFlowNode const* m_break = nullptr; | ||||
| 	ControlFlowNode const* m_continue = nullptr; | ||||
| 
 | ||||
| 	std::map<YulString, FunctionFlow> m_functionFlows; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Requires: Disambiguator, Function Hoister. | ||||
|  */ | ||||
| class ControlFlowSideEffectsCollector | ||||
| { | ||||
| public: | ||||
| 	explicit ControlFlowSideEffectsCollector( | ||||
| 		Dialect const& _dialect, | ||||
| 		Block const& _ast | ||||
| 	); | ||||
| 
 | ||||
| 	std::map<YulString, ControlFlowSideEffects> const& functionSideEffects() const | ||||
| 	{ | ||||
| 		return m_functionSideEffects; | ||||
| 	} | ||||
| private: | ||||
| 
 | ||||
| 	/// @returns false if nothing could be processed.
 | ||||
| 	bool processFunction(YulString _name); | ||||
| 
 | ||||
| 	/// @returns the next pending node of the function that is not
 | ||||
| 	/// a function call to a function that might not continue.
 | ||||
| 	/// De-queues the node or returns nullptr if no such node is found.
 | ||||
| 	ControlFlowNode const* nextProcessableNode(YulString _functionName); | ||||
| 
 | ||||
| 	/// @returns the side-effects of either a builtin call or a user defined function
 | ||||
| 	/// call (as far as already computed).
 | ||||
| 	ControlFlowSideEffects const& sideEffects(YulString _functionName) const; | ||||
| 
 | ||||
| 	/// Queues the given node to be processed (if not already visited)
 | ||||
| 	/// and if it is a function call, records that `_functionName` calls
 | ||||
| 	/// `*_node->functionCall`.
 | ||||
| 	void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node); | ||||
| 
 | ||||
| 	Dialect const& m_dialect; | ||||
| 	ControlFlowBuilder m_cfgBuilder; | ||||
| 	std::map<YulString, ControlFlowSideEffects> m_functionSideEffects; | ||||
| 	std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes; | ||||
| 	std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes; | ||||
| 	/// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y`
 | ||||
| 	std::map<YulString, std::set<YulString>> m_functionCalls; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -22,8 +22,8 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libyul/YulString.h> | ||||
| #include <libyul/SideEffects.h> | ||||
| #include <libyul/ControlFlowSideEffects.h> | ||||
| #include <libyul/SideEffects.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <set> | ||||
|  | ||||
| @ -94,7 +94,7 @@ struct SideEffects | ||||
| 			cannotLoop && _other.cannotLoop, | ||||
| 			otherState + _other.otherState, | ||||
| 			storage + _other.storage, | ||||
| 			memory +  _other.memory | ||||
| 			memory + _other.memory | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -229,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) | ||||
| 	//       not only for builtins.
 | ||||
| 	if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression)) | ||||
| 		if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name)) | ||||
| 			if (builtin->controlFlowSideEffects.terminates) | ||||
| 			if (builtin->controlFlowSideEffects.terminatesOrReverts()) | ||||
| 			{ | ||||
| 				m_currentBlock->exit = CFG::BasicBlock::Terminated{}; | ||||
| 				m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); | ||||
|  | ||||
| @ -57,8 +57,20 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction( | ||||
| 	f.parameters.resize(static_cast<size_t>(info.args)); | ||||
| 	f.returns.resize(static_cast<size_t>(info.ret)); | ||||
| 	f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction); | ||||
| 	f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction); | ||||
| 	f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction); | ||||
| 	if (evmasm::SemanticInformation::terminatesControlFlow(_instruction)) | ||||
| 	{ | ||||
| 		f.controlFlowSideEffects.canContinue = false; | ||||
| 		if (evmasm::SemanticInformation::reverts(_instruction)) | ||||
| 		{ | ||||
| 			f.controlFlowSideEffects.canTerminate = false; | ||||
| 			f.controlFlowSideEffects.canRevert = true; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			f.controlFlowSideEffects.canTerminate = true; | ||||
| 			f.controlFlowSideEffects.canRevert = false; | ||||
| 		} | ||||
| 	} | ||||
| 	f.isMSize = _instruction == evmasm::Instruction::MSIZE; | ||||
| 	f.literalArguments.clear(); | ||||
| 	f.instruction = _instruction; | ||||
|  | ||||
| @ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) | ||||
| 			yulAssert(!_block.operations.empty(), ""); | ||||
| 			CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation); | ||||
| 			yulAssert(builtinCall, ""); | ||||
| 			yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminates, ""); | ||||
| 			yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), ""); | ||||
| 		} | ||||
| 	}, _block.exit); | ||||
| 	// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.
 | ||||
|  | ||||
| @ -129,8 +129,9 @@ WasmDialect::WasmDialect() | ||||
| 	m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None; | ||||
| 	m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None; | ||||
| 	m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None; | ||||
| 	m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true; | ||||
| 	m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true; | ||||
| 	m_functions["unreachable"_yulstring].controlFlowSideEffects.canTerminate = false; | ||||
| 	m_functions["unreachable"_yulstring].controlFlowSideEffects.canRevert = true; | ||||
| 	m_functions["unreachable"_yulstring].controlFlowSideEffects.canContinue = false; | ||||
| 
 | ||||
| 	addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String}); | ||||
| 	addFunction("dataoffset", {i64}, {i64}, true, {LiteralKind::String}); | ||||
| @ -215,11 +216,11 @@ void WasmDialect::addExternals() | ||||
| 		{"eth", "log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}}, | ||||
| 		{"eth", "getBlockNumber", {}, {i64}}, | ||||
| 		{"eth", "getTxOrigin", {i32ptr}, {}}, | ||||
| 		{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}}, | ||||
| 		{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}}, | ||||
| 		{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false, false}}, | ||||
| 		{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{false, true, false}}, | ||||
| 		{"eth", "getReturnDataSize", {}, {i32}}, | ||||
| 		{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}}, | ||||
| 		{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}}, | ||||
| 		{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{false, true, false}}, | ||||
| 		{"eth", "getBlockTimestamp", {}, {i64}}, | ||||
| 		{"debug", "print32", {i32}, {}}, | ||||
| 		{"debug", "print64", {i64}, {}}, | ||||
| @ -240,7 +241,7 @@ void WasmDialect::addExternals() | ||||
| 		// TODO some of them are side effect free.
 | ||||
| 		f.sideEffects = SideEffects::worst(); | ||||
| 		f.sideEffects.cannotLoop = true; | ||||
| 		f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates; | ||||
| 		f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminatesOrReverts(); | ||||
| 		f.controlFlowSideEffects = ext.controlFlowSideEffects; | ||||
| 		f.isMSize = false; | ||||
| 		f.literalArguments.clear(); | ||||
|  | ||||
| @ -22,7 +22,7 @@ using namespace std; | ||||
| using namespace solidity; | ||||
| using namespace solidity::yul; | ||||
| 
 | ||||
| map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block) | ||||
| map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block) | ||||
| { | ||||
| 	FunctionDefinitionCollector functionDefinitionCollector; | ||||
| 	functionDefinitionCollector(_block); | ||||
|  | ||||
| @ -34,7 +34,7 @@ namespace solidity::yul | ||||
| class FunctionDefinitionCollector: ASTWalker | ||||
| { | ||||
| public: | ||||
| 	static std::map<YulString, FunctionDefinition const*> run(Block& _block); | ||||
| 	static std::map<YulString, FunctionDefinition const*> run(Block const& _block); | ||||
| private: | ||||
| 	using ASTWalker::operator(); | ||||
| 	void operator()(FunctionDefinition const& _functionDefinition) override; | ||||
|  | ||||
| @ -130,6 +130,8 @@ set(libyul_sources | ||||
|     libyul/CompilabilityChecker.cpp | ||||
|     libyul/ControlFlowGraphTest.cpp | ||||
|     libyul/ControlFlowGraphTest.h | ||||
|     libyul/ControlFlowSideEffectsTest.cpp | ||||
|     libyul/ControlFlowSideEffectsTest.h | ||||
|     libyul/EVMCodeTransformTest.cpp | ||||
|     libyul/EVMCodeTransformTest.h | ||||
|     libyul/EwasmTranslationTest.cpp | ||||
|  | ||||
| @ -31,6 +31,7 @@ | ||||
| #include <test/libyul/YulOptimizerTest.h> | ||||
| #include <test/libyul/YulInterpreterTest.h> | ||||
| #include <test/libyul/ObjectCompilerTest.h> | ||||
| #include <test/libyul/ControlFlowSideEffectsTest.h> | ||||
| #include <test/libyul/FunctionSideEffects.h> | ||||
| #include <test/libyul/StackLayoutGeneratorTest.h> | ||||
| #include <test/libyul/SyntaxTest.h> | ||||
| @ -57,12 +58,12 @@ struct Testsuite | ||||
| Testsuite const g_interactiveTestsuites[] = { | ||||
| /*
 | ||||
| 	Title                   Path           Subpath                SMT   NeedsVM Creator function */ | ||||
| 	{"Ewasm Translation",      "libyul",      "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create}, | ||||
| 	{"Yul Optimizer",          "libyul",      "yulOptimizerTests",     false, false, &yul::test::YulOptimizerTest::create}, | ||||
| 	{"Yul Interpreter",        "libyul",      "yulInterpreterTests",   false, false, &yul::test::YulInterpreterTest::create}, | ||||
| 	{"Yul Object Compiler",    "libyul",      "objectCompiler",        false, false, &yul::test::ObjectCompilerTest::create}, | ||||
| 	{"Yul Control Flow Graph", "libyul",      "yulControlFlowGraph",   false, false, &yul::test::ControlFlowGraphTest::create}, | ||||
| 	{"Yul Stack Layout",       "libyul",      "yulStackLayout",        false, false, &yul::test::StackLayoutGeneratorTest::create}, | ||||
| 	{"Control Flow Side Effects","libyul",    "controlFlowSideEffects",false, false, &yul::test::ControlFlowSideEffectsTest::create}, | ||||
| 	{"Function Side Effects",  "libyul",      "functionSideEffects",   false, false, &yul::test::FunctionSideEffects::create}, | ||||
| 	{"Yul Syntax",             "libyul",      "yulSyntaxTests",        false, false, &yul::test::SyntaxTest::create}, | ||||
| 	{"EVM Code Transform",     "libyul",      "evmCodeTransform",      false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}}, | ||||
| @ -72,7 +73,8 @@ Testsuite const g_interactiveTestsuites[] = { | ||||
| 	{"JSON AST",               "libsolidity", "ASTJSON",               false, false, &ASTJSONTest::create}, | ||||
| 	{"JSON ABI",               "libsolidity", "ABIJson",               false, false, &ABIJsonTest::create}, | ||||
| 	{"SMT Checker",            "libsolidity", "smtCheckerTests",       true,  false, &SMTCheckerTest::create}, | ||||
| 	{"Gas Estimates",          "libsolidity", "gasTests",              false, false, &GasTest::create} | ||||
| 	{"Gas Estimates",          "libsolidity", "gasTests",              false, false, &GasTest::create}, | ||||
| 	{"Ewasm Translation",      "libyul",      "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create} | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										79
									
								
								test/libyul/ControlFlowSideEffectsTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								test/libyul/ControlFlowSideEffectsTest.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #include <test/libyul/ControlFlowSideEffectsTest.h> | ||||
| 
 | ||||
| #include <test/Common.h> | ||||
| #include <test/libyul/Common.h> | ||||
| 
 | ||||
| #include <libyul/Object.h> | ||||
| #include <libyul/ControlFlowSideEffects.h> | ||||
| #include <libyul/ControlFlowSideEffectsCollector.h> | ||||
| #include <libyul/backends/evm/EVMDialect.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace solidity; | ||||
| using namespace solidity::yul; | ||||
| using namespace solidity::yul::test; | ||||
| using namespace solidity::frontend::test; | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| string toString(ControlFlowSideEffects const& _sideEffects) | ||||
| { | ||||
| 	vector<string> r; | ||||
| 	if (_sideEffects.canTerminate) | ||||
| 		r.emplace_back("can terminate"); | ||||
| 	if (_sideEffects.canRevert) | ||||
| 		r.emplace_back("can revert"); | ||||
| 	if (_sideEffects.canContinue) | ||||
| 		r.emplace_back("can continue"); | ||||
| 	return util::joinHumanReadable(r); | ||||
| } | ||||
| } | ||||
| 
 | ||||
| ControlFlowSideEffectsTest::ControlFlowSideEffectsTest(string const& _filename): | ||||
| 	TestCase(_filename) | ||||
| { | ||||
| 	m_source = m_reader.source(); | ||||
| 	m_expectation = m_reader.simpleExpectations(); | ||||
| } | ||||
| 
 | ||||
| TestCase::TestResult ControlFlowSideEffectsTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) | ||||
| { | ||||
| 	Object obj; | ||||
| 	std::tie(obj.code, obj.analysisInfo) = yul::test::parse(m_source, false); | ||||
| 	if (!obj.code) | ||||
| 		BOOST_THROW_EXCEPTION(runtime_error("Parsing input failed.")); | ||||
| 
 | ||||
| 	std::map<YulString, ControlFlowSideEffects> sideEffects = | ||||
| 		ControlFlowSideEffectsCollector( | ||||
| 			EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), | ||||
| 			*obj.code | ||||
| 		).functionSideEffects(); | ||||
| 
 | ||||
| 	std::map<std::string, std::string> controlFlowSideEffectsStr; | ||||
| 	for (auto&& [fun, effects]: sideEffects) | ||||
| 		controlFlowSideEffectsStr[fun.str()] = toString(effects); | ||||
| 
 | ||||
| 	m_obtainedResult.clear(); | ||||
| 	for (auto&& [functionName, effect]: controlFlowSideEffectsStr) | ||||
| 		m_obtainedResult += functionName + (effect.empty() ? ":" : ": " + effect) + "\n"; | ||||
| 
 | ||||
| 	return checkResult(_stream, _linePrefix, _formatted); | ||||
| } | ||||
							
								
								
									
										40
									
								
								test/libyul/ControlFlowSideEffectsTest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								test/libyul/ControlFlowSideEffectsTest.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| /*
 | ||||
| 	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/>.
 | ||||
| */ | ||||
| // SPDX-License-Identifier: GPL-3.0
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <test/TestCase.h> | ||||
| 
 | ||||
| #include <iosfwd> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace solidity::yul::test | ||||
| { | ||||
| 
 | ||||
| class ControlFlowSideEffectsTest: public solidity::frontend::test::TestCase | ||||
| { | ||||
| public: | ||||
| 	static std::unique_ptr<TestCase> create(Config const& _config) | ||||
| 	{ return std::make_unique<ControlFlowSideEffectsTest>(_config.filename); } | ||||
| 	explicit ControlFlowSideEffectsTest(std::string const& _filename); | ||||
| 	TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										19
									
								
								test/libyul/controlFlowSideEffects/eval_order.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								test/libyul/controlFlowSideEffects/eval_order.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| { | ||||
|     function a() -> x { | ||||
|         revert(0, 0) | ||||
|     } | ||||
|     function b() -> x { | ||||
|         return(0, 0) | ||||
|     } | ||||
|     function c() { | ||||
|         sstore(a(), b()) | ||||
|     } | ||||
|     function d() { | ||||
|         sstore(b(), a()) | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // a: can revert | ||||
| // b: can terminate | ||||
| // c: can terminate | ||||
| // d: can revert | ||||
							
								
								
									
										55
									
								
								test/libyul/controlFlowSideEffects/for_loop.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								test/libyul/controlFlowSideEffects/for_loop.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| { | ||||
|     function a() { | ||||
|         for { leave } calldataload(0) { } { | ||||
|             break | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|     function b() { | ||||
|         for { } calldataload(0) { leave } { | ||||
|             break | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|     function b2() { | ||||
|         for { } calldataload(0) { leave } { | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|     function c() { | ||||
|         for { } calldataload(0) { revert(0, 0) } { | ||||
|             break | ||||
|         } | ||||
|     } | ||||
|     function c2() { | ||||
|         for { } calldataload(0) { revert(0, 0) } { | ||||
|             break | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
|     function d() { | ||||
|         for { } calldataload(0) { revert(0, 0) } { | ||||
|             continue | ||||
|         } | ||||
|     } | ||||
|     function e() { | ||||
|         for { } calldataload(0) { revert(0, 0) } { | ||||
|             if calldataload(1) { break } | ||||
|         } | ||||
|     } | ||||
|     function f() { | ||||
|         for { } calldataload(0) {  } { | ||||
|             if calldataload(1) { continue } | ||||
|             revert(0, 0) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // a: can continue | ||||
| // b: can continue | ||||
| // b2: can revert, can continue | ||||
| // c: can continue | ||||
| // c2: can continue | ||||
| // d: can revert, can continue | ||||
| // e: can revert, can continue | ||||
| // f: can revert, can continue | ||||
							
								
								
									
										13
									
								
								test/libyul/controlFlowSideEffects/leave.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								test/libyul/controlFlowSideEffects/leave.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| { | ||||
|     function a() { | ||||
|         revert(0, 0) | ||||
|         leave | ||||
|     } | ||||
|     function b() { | ||||
|         leave | ||||
|         revert(0, 0) | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // a: can revert | ||||
| // b: can continue | ||||
							
								
								
									
										37
									
								
								test/libyul/controlFlowSideEffects/recursion.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								test/libyul/controlFlowSideEffects/recursion.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| { | ||||
|     function a() { | ||||
|         if calldataload(0) { | ||||
|             revert(0, 0) | ||||
|         } | ||||
|         reg() | ||||
|         b() | ||||
|     } | ||||
|     function b() { | ||||
|         a() | ||||
|         return(0, 0) | ||||
|     } | ||||
|     function c() { | ||||
|         c() | ||||
|         revert(0, 0) | ||||
|     } | ||||
|     function d() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { x() } | ||||
|         case 1 { y() reg() revert(0, 0) } | ||||
|         default { z() } | ||||
|     } | ||||
|     function x() { d() revert(0, 0) } | ||||
|     function y() { reg() x() } | ||||
|     function z() { y() } | ||||
| 
 | ||||
|     function reg() {} | ||||
| } | ||||
| // ---- | ||||
| // a: can revert | ||||
| // b: can revert | ||||
| // c: | ||||
| // d: | ||||
| // reg: can continue | ||||
| // x: | ||||
| // y: | ||||
| // z: | ||||
							
								
								
									
										34
									
								
								test/libyul/controlFlowSideEffects/simple_conditionals.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								test/libyul/controlFlowSideEffects/simple_conditionals.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| { | ||||
|     function a() { | ||||
|         if calldataload(0) { g() } | ||||
|     } | ||||
|     function b() { | ||||
|         g() | ||||
|         if calldataload(0) { } | ||||
|     } | ||||
|     function c() { | ||||
|         if calldataload(0) { } | ||||
|         g() | ||||
|     } | ||||
|     function d() { | ||||
|         stop() | ||||
|         if calldataload(0) { g() } | ||||
|     } | ||||
|     function e() { | ||||
|         if calldataload(0) { g() } | ||||
|         stop() | ||||
|     } | ||||
|     function f() { | ||||
|         g() | ||||
|         if calldataload(0) { g() } | ||||
|     } | ||||
|     function g() { revert(0, 0) } | ||||
| } | ||||
| // ---- | ||||
| // a: can revert, can continue | ||||
| // b: can revert | ||||
| // c: can revert | ||||
| // d: can terminate | ||||
| // e: can terminate, can revert | ||||
| // f: can revert | ||||
| // g: can revert | ||||
							
								
								
									
										17
									
								
								test/libyul/controlFlowSideEffects/simple_functions.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/libyul/controlFlowSideEffects/simple_functions.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| { | ||||
|     function a() {} | ||||
|     function f() { g() } | ||||
|     function g() { revert(0, 0) } | ||||
|     function h() { stop() } | ||||
|     function i() { h() } | ||||
|     function j() { h() g() } | ||||
|     function k() { g() h() } | ||||
| } | ||||
| // ---- | ||||
| // a: can continue | ||||
| // f: can revert | ||||
| // g: can revert | ||||
| // h: can terminate | ||||
| // i: can terminate | ||||
| // j: can terminate | ||||
| // k: can revert | ||||
							
								
								
									
										41
									
								
								test/libyul/controlFlowSideEffects/switch.yul
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								test/libyul/controlFlowSideEffects/switch.yul
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| { | ||||
|     function a() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { revert(0, 0) } | ||||
|     } | ||||
|     function b() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { revert(0, 0) } | ||||
|         default { revert(0, 0) } | ||||
|     } | ||||
|     function c() { | ||||
|         return(0, 0) | ||||
|         switch calldataload(0) | ||||
|         case 0 { revert(0, 0) } | ||||
|         default { } | ||||
|     } | ||||
|     function d() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { return(0, 0) } | ||||
|         default { return(0, 0) } | ||||
|         revert(0, 0) | ||||
|     } | ||||
|     function e() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { return(0, 0) } | ||||
|         revert(0, 0) | ||||
|     } | ||||
|     function f() { | ||||
|         switch calldataload(0) | ||||
|         case 0 { leave } | ||||
|         default { leave } | ||||
|         revert(0, 0) | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // a: can revert, can continue | ||||
| // b: can revert | ||||
| // c: can terminate | ||||
| // d: can terminate | ||||
| // e: can terminate, can revert | ||||
| // f: can continue | ||||
| @ -33,6 +33,7 @@ add_executable(isoltest | ||||
| 	../libsolidity/SMTCheckerTest.cpp | ||||
| 	../libyul/Common.cpp | ||||
| 	../libyul/ControlFlowGraphTest.cpp | ||||
| 	../libyul/ControlFlowSideEffectsTest.cpp | ||||
| 	../libyul/EVMCodeTransformTest.cpp | ||||
| 	../libyul/EwasmTranslationTest.cpp | ||||
| 	../libyul/FunctionSideEffects.cpp | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user