mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #3220 from ethereum/IuliaIf
If statement for Iulia / Inline Assembly
This commit is contained in:
		
						commit
						a1f59cbb17
					
				| @ -5,6 +5,7 @@ Features: | ||||
|  * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. | ||||
|  * Type Checker: Improve address checksum warning. | ||||
|  * Type Checker: More detailed errors for invalid array lengths (such as division by zero). | ||||
|  * Inline Assembly: ``if`` statement. | ||||
| 
 | ||||
| Bugfixes: | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ arising when writing manual assembly by the following features: | ||||
| * access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }`` | ||||
| * labels: ``let x := 10  repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` | ||||
| * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` | ||||
| * if statements: ``if slt(x, 0) { x := sub(0, x) }`` | ||||
| * switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }`` | ||||
| * function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) }   }`` | ||||
| 
 | ||||
| @ -400,7 +401,7 @@ Labels | ||||
| Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses | ||||
| which can change easily. Solidity inline assembly provides labels to make the use of | ||||
| jumps easier. Note that labels are a low-level feature and it is possible to write | ||||
| efficient assembly without labels, just using assembly functions, loops and switch instructions | ||||
| efficient assembly without labels, just using assembly functions, loops, if and switch instructions | ||||
| (see below). The following code computes an element in the Fibonacci series. | ||||
| 
 | ||||
| .. code:: | ||||
| @ -523,6 +524,21 @@ is performed by replacing the variable's value on the stack by the new value. | ||||
|         =: v // instruction style assignment, puts the result of sload(10) into v | ||||
|     } | ||||
| 
 | ||||
| If | ||||
| -- | ||||
| 
 | ||||
| The if statement can be used for conditionally executing code. | ||||
| There is no "else" part, consider using "switch" (see below) if | ||||
| you need multiple alternatives. | ||||
| 
 | ||||
| .. code:: | ||||
| 
 | ||||
|     { | ||||
|         if eq(value, 0) { revert(0, 0) } | ||||
|     } | ||||
| 
 | ||||
| The curly braces for the body are required. | ||||
| 
 | ||||
| Switch | ||||
| ------ | ||||
| 
 | ||||
| @ -622,7 +638,7 @@ Things to Avoid | ||||
| --------------- | ||||
| 
 | ||||
| Inline assembly might have a quite high-level look, but it actually is extremely | ||||
| low-level. Function calls, loops and switches are converted by simple | ||||
| low-level. Function calls, loops, ifs and switches are converted by simple | ||||
| rewriting rules and after that, the only thing the assembler does for you is re-arranging | ||||
| functional-style opcodes, managing jump labels, counting stack height for | ||||
| variable access and removing stack slots for assembly-local variables when the end | ||||
| @ -669,7 +685,7 @@ for the Solidity compiler. In this form, it tries to achieve several goals: | ||||
| 3. Control flow should be easy to detect to help in formal verification and optimization. | ||||
| 
 | ||||
| In order to achieve the first and last goal, assembly provides high-level constructs | ||||
| like ``for`` loops, ``switch`` statements and function calls. It should be possible | ||||
| like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible | ||||
| to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, | ||||
| ``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow | ||||
| and the last two obfuscate control flow. Furthermore, functional statements of | ||||
| @ -875,6 +891,7 @@ Grammar:: | ||||
|         FunctionalAssemblyAssignment | | ||||
|         AssemblyAssignment | | ||||
|         LabelDefinition | | ||||
|         AssemblyIf | | ||||
|         AssemblySwitch | | ||||
|         AssemblyFunctionDefinition | | ||||
|         AssemblyFor | | ||||
| @ -891,6 +908,7 @@ Grammar:: | ||||
|     IdentifierList = Identifier ( ',' Identifier)* | ||||
|     AssemblyAssignment = '=:' Identifier | ||||
|     LabelDefinition = Identifier ':' | ||||
|     AssemblyIf = 'if' FunctionalAssemblyExpression AssemblyBlock | ||||
|     AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* | ||||
|         ( 'default' AssemblyBlock )? | ||||
|     AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock | ||||
|  | ||||
| @ -15,7 +15,7 @@ future versions of the Solidity compiler will even use JULIA as intermediate | ||||
| language. It should also be easy to build high-level optimizer stages for JULIA. | ||||
| 
 | ||||
| The core components of JULIA are functions, blocks, variables, literals, | ||||
| for-loops, switch-statements, expressions and assignments to variables. | ||||
| for-loops, if-statements, switch-statements, expressions and assignments to variables. | ||||
| 
 | ||||
| JULIA is typed, both variables and literals must specify the type with postfix | ||||
| notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``, | ||||
| @ -88,6 +88,8 @@ Grammar:: | ||||
|         IdentifierList ':=' Expression | ||||
|     Expression = | ||||
|         FunctionCall | Identifier | Literal | ||||
|     If = | ||||
|         'if' Expression Block | ||||
|     Switch = | ||||
|         'switch' Expression Case* ( 'default' Block )? | ||||
|     Case = | ||||
| @ -248,8 +250,14 @@ We will use a destructuring notation for the AST nodes. | ||||
|         G, L, break | ||||
|     E(G, L, continue: BreakContinue) = | ||||
|         G, L, continue | ||||
|     E(G, L, <if condition body>: If) = | ||||
|         let G0, L0, v = E(G, L, condition) | ||||
|         if v is true: | ||||
|             E(G0, L0, body) | ||||
|         else: | ||||
|             G0, L0, regular | ||||
|     E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn>: Switch) = | ||||
|         E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) = | ||||
|         E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) | ||||
|     E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn default st'>: Switch) = | ||||
|         let G0, L0, v = E(G, L, condition) | ||||
|         // i = 1 .. n | ||||
|  | ||||
| @ -217,6 +217,19 @@ void CodeTransform::operator()(assembly::Instruction const& _instruction) | ||||
| 	checkStackHeight(&_instruction); | ||||
| } | ||||
| 
 | ||||
| void CodeTransform::operator()(If const& _if) | ||||
| { | ||||
| 	visitExpression(*_if.condition); | ||||
| 	m_assembly.setSourceLocation(_if.location); | ||||
| 	m_assembly.appendInstruction(solidity::Instruction::ISZERO); | ||||
| 	AbstractAssembly::LabelID end = m_assembly.newLabelId(); | ||||
| 	m_assembly.appendJumpToIf(end); | ||||
| 	(*this)(_if.body); | ||||
| 	m_assembly.setSourceLocation(_if.location); | ||||
| 	m_assembly.appendLabel(end); | ||||
| 	checkStackHeight(&_if); | ||||
| } | ||||
| 
 | ||||
| void CodeTransform::operator()(Switch const& _switch) | ||||
| { | ||||
| 	//@TODO use JUMPV in EVM1.5?
 | ||||
|  | ||||
| @ -104,6 +104,7 @@ public: | ||||
| 	void operator()(solidity::assembly::StackAssignment const& _assignment); | ||||
| 	void operator()(solidity::assembly::Assignment const& _assignment); | ||||
| 	void operator()(solidity::assembly::VariableDeclaration const& _varDecl); | ||||
| 	void operator()(solidity::assembly::If const& _if); | ||||
| 	void operator()(solidity::assembly::Switch const& _switch); | ||||
| 	void operator()(solidity::assembly::FunctionDefinition const&); | ||||
| 	void operator()(solidity::assembly::ForLoop const&); | ||||
|  | ||||
| @ -72,6 +72,11 @@ public: | ||||
| 		for (auto const& arg: _funCall.arguments) | ||||
| 			boost::apply_visitor(*this, arg); | ||||
| 	} | ||||
| 	void operator()(assembly::If const& _if) | ||||
| 	{ | ||||
| 		boost::apply_visitor(*this, *_if.condition); | ||||
| 		(*this)(_if.body); | ||||
| 	} | ||||
| 	void operator()(assembly::Switch const& _switch) | ||||
| 	{ | ||||
| 		boost::apply_visitor(*this, *_switch.expression); | ||||
|  | ||||
| @ -168,7 +168,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) | ||||
| 		{ | ||||
| 			size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); | ||||
| 			solAssert(members > 0, "empty enum should have caused a parser error."); | ||||
| 			Whiskers w("switch lt(value, <members>) case 0 { <failure> } cleaned := value"); | ||||
| 			Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value"); | ||||
| 			w("members", to_string(members)); | ||||
| 			if (_revertOnFailure) | ||||
| 				w("failure", "revert(0, 0)"); | ||||
| @ -988,8 +988,8 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) | ||||
| 					{ | ||||
| 						mstore(add(dst, i), mload(add(src, i))) | ||||
| 					} | ||||
| 					switch eq(i, length) | ||||
| 					case 0 { | ||||
| 					if gt(i, length) | ||||
| 					{ | ||||
| 						// clear end
 | ||||
| 						mstore(add(dst, length), 0) | ||||
| 					} | ||||
|  | ||||
| @ -286,6 +286,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(If const& _if) | ||||
| { | ||||
| 	bool success = true; | ||||
| 
 | ||||
| 	if (!expectExpression(*_if.condition)) | ||||
| 		success = false; | ||||
| 	m_stackHeight--; | ||||
| 
 | ||||
| 	if (!(*this)(_if.body)) | ||||
| 		success = false; | ||||
| 
 | ||||
| 	m_info.stackHeightInfo[&_if] = m_stackHeight; | ||||
| 
 | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(Switch const& _switch) | ||||
| { | ||||
| 	bool success = true; | ||||
|  | ||||
| @ -70,6 +70,7 @@ public: | ||||
| 	bool operator()(assembly::VariableDeclaration const& _variableDeclaration); | ||||
| 	bool operator()(assembly::FunctionDefinition const& _functionDefinition); | ||||
| 	bool operator()(assembly::FunctionCall const& _functionCall); | ||||
| 	bool operator()(assembly::If const& _if); | ||||
| 	bool operator()(assembly::Switch const& _switch); | ||||
| 	bool operator()(assembly::ForLoop const& _forLoop); | ||||
| 	bool operator()(assembly::Block const& _block); | ||||
|  | ||||
| @ -68,6 +68,8 @@ struct VariableDeclaration { SourceLocation location; TypedNameList variables; s | ||||
| struct Block { SourceLocation location; std::vector<Statement> statements; }; | ||||
| /// Function definition ("function f(a, b) -> (d, e) { ... }")
 | ||||
| struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; }; | ||||
| /// Conditional execution without "else" part.
 | ||||
| struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; }; | ||||
| /// Switch case or default case
 | ||||
| struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; }; | ||||
| /// Switch statement
 | ||||
|  | ||||
| @ -41,11 +41,12 @@ struct VariableDeclaration; | ||||
| struct FunctionalInstruction; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| struct If; | ||||
| struct Switch; | ||||
| struct ForLoop; | ||||
| struct Block; | ||||
| 
 | ||||
| using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>; | ||||
| using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -73,13 +73,23 @@ assembly::Statement Parser::parseStatement() | ||||
| 		return parseFunctionDefinition(); | ||||
| 	case Token::LBrace: | ||||
| 		return parseBlock(); | ||||
| 	case Token::If: | ||||
| 	{ | ||||
| 		assembly::If _if = createWithLocation<assembly::If>(); | ||||
| 		m_scanner->next(); | ||||
| 		_if.condition = make_shared<Statement>(parseExpression()); | ||||
| 		if (_if.condition->type() == typeid(assembly::Instruction)) | ||||
| 			fatalParserError("Instructions are not supported as conditions for if - try to append \"()\"."); | ||||
| 		_if.body = parseBlock(); | ||||
| 		return _if; | ||||
| 	} | ||||
| 	case Token::Switch: | ||||
| 	{ | ||||
| 		assembly::Switch _switch = createWithLocation<assembly::Switch>(); | ||||
| 		m_scanner->next(); | ||||
| 		_switch.expression = make_shared<Statement>(parseExpression()); | ||||
| 		if (_switch.expression->type() == typeid(assembly::Instruction)) | ||||
| 			fatalParserError("Instructions are not supported as expressions for switch."); | ||||
| 			fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\"."); | ||||
| 		while (m_scanner->currentToken() == Token::Case) | ||||
| 			_switch.cases.emplace_back(parseCase()); | ||||
| 		if (m_scanner->currentToken() == Token::Default) | ||||
|  | ||||
| @ -174,6 +174,11 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall) | ||||
| 		")"; | ||||
| } | ||||
| 
 | ||||
| string AsmPrinter::operator()(If const& _if) | ||||
| { | ||||
| 	return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body); | ||||
| } | ||||
| 
 | ||||
| string AsmPrinter::operator()(Switch const& _switch) | ||||
| { | ||||
| 	string out = "switch " + boost::apply_visitor(*this, *_switch.expression); | ||||
|  | ||||
| @ -48,6 +48,7 @@ public: | ||||
| 	std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); | ||||
| 	std::string operator()(assembly::FunctionDefinition const& _functionDefinition); | ||||
| 	std::string operator()(assembly::FunctionCall const& _functionCall); | ||||
| 	std::string operator()(assembly::If const& _if); | ||||
| 	std::string operator()(assembly::Switch const& _switch); | ||||
| 	std::string operator()(assembly::ForLoop const& _forLoop); | ||||
| 	std::string operator()(assembly::Block const& _block); | ||||
|  | ||||
| @ -104,6 +104,11 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(If const& _if) | ||||
| { | ||||
| 	return (*this)(_if.body); | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(Switch const& _switch) | ||||
| { | ||||
| 	bool success = true; | ||||
|  | ||||
| @ -59,6 +59,7 @@ public: | ||||
| 	bool operator()(assembly::VariableDeclaration const& _variableDeclaration); | ||||
| 	bool operator()(assembly::FunctionDefinition const& _functionDefinition); | ||||
| 	bool operator()(assembly::FunctionCall const&) { return true; } | ||||
| 	bool operator()(assembly::If const& _if); | ||||
| 	bool operator()(assembly::Switch const& _switch); | ||||
| 	bool operator()(assembly::ForLoop const& _forLoop); | ||||
| 	bool operator()(assembly::Block const& _block); | ||||
|  | ||||
| @ -269,6 +269,21 @@ BOOST_AUTO_TEST_CASE(multiple_assignment) | ||||
| 	BOOST_CHECK(successParse(text)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ if true:bool {} }")); | ||||
| 	BOOST_CHECK(successParse("{ if false:bool { let x:u256 := 3:u256 } }")); | ||||
| 	BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement_invalid) | ||||
| { | ||||
| 	CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); | ||||
| 	CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); | ||||
| 	// TODO change this to an error once we check types.
 | ||||
| 	BOOST_CHECK(successParse("{ if 42:u256 { } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE_END() | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -251,6 +251,27 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl) | ||||
| 	CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ if 42 {} }")); | ||||
| 	BOOST_CHECK(successParse("{ if 42 { let x := 3 } }")); | ||||
| 	BOOST_CHECK(successParse("{ function f() -> x {} if f() { pop(f()) } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement_scope) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ let x := 2 if 42 { x := 3 } }")); | ||||
| 	CHECK_PARSE_ERROR("{ if 32 { let x := 3 } x := 2 }", DeclarationError, "Variable not found or variable not lvalue."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement_invalid) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if"); | ||||
| 	BOOST_CHECK("{ if calldatasize() {}"); | ||||
| 	CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); | ||||
| 	CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(switch_statement) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ switch 42 default {} }")); | ||||
| @ -275,7 +296,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) | ||||
| BOOST_AUTO_TEST_CASE(switch_invalid_expression) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); | ||||
| 	CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch."); | ||||
| 	CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch"); | ||||
| 	CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); | ||||
| } | ||||
| 
 | ||||
| @ -487,6 +508,11 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) | ||||
| 	parsePrintCompare(parsed); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_if) | ||||
| { | ||||
| 	parsePrintCompare("{\n    if 2\n    {\n        pop(mload(0))\n    }\n}"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_switch) | ||||
| { | ||||
| 	parsePrintCompare("{\n    switch 42\n    case 1 {\n    }\n    case 2 {\n    }\n    default {\n    }\n}"); | ||||
| @ -628,6 +654,11 @@ BOOST_AUTO_TEST_CASE(for_statement) | ||||
| 	BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(if_statement) | ||||
| { | ||||
| 	BOOST_CHECK(successAssemble("{ if 1 {} }")); | ||||
| 	BOOST_CHECK(successAssemble("{ let x := 0 if eq(calldatasize(), 0) { x := 1 } mstore(0, x) }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(large_constant) | ||||
| { | ||||
|  | ||||
| @ -8032,6 +8032,24 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) | ||||
| 	ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_if) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract C { | ||||
| 			function f(uint a) returns (uint b) { | ||||
| 				assembly { | ||||
| 					if gt(a, 1) { b := 2 } | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode, 0, "C"); | ||||
| 	ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); | ||||
| 	ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); | ||||
| 	ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); | ||||
| 	ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_switch) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user