diff options
-rw-r--r-- | Changelog.md | 1 | ||||
-rw-r--r-- | docs/assembly.rst | 24 | ||||
-rw-r--r-- | docs/julia.rst | 12 | ||||
-rw-r--r-- | libjulia/backends/evm/EVMCodeTransform.cpp | 13 | ||||
-rw-r--r-- | libjulia/backends/evm/EVMCodeTransform.h | 1 | ||||
-rw-r--r-- | libsolidity/analysis/ViewPureChecker.cpp | 5 | ||||
-rw-r--r-- | libsolidity/codegen/ABIFunctions.cpp | 6 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmAnalysis.cpp | 16 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmAnalysis.h | 1 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmData.h | 2 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmDataForward.h | 3 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.cpp | 12 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.cpp | 5 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.h | 1 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScopeFiller.cpp | 5 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScopeFiller.h | 1 | ||||
-rw-r--r-- | test/libjulia/Parser.cpp | 15 | ||||
-rw-r--r-- | test/libsolidity/InlineAssembly.cpp | 33 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 18 |
19 files changed, 163 insertions, 11 deletions
diff --git a/Changelog.md b/Changelog.md index a362138e..27e72838 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/docs/assembly.rst b/docs/assembly.rst index 00bfb388..c233985b 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -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 diff --git a/docs/julia.rst b/docs/julia.rst index cf798363..309e6b36 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -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 diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 66f593e8..13d9d011 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -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? diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index 951c8a50..387720a2 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -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&); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 7f28c7d2..7e41fc16 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -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); diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d2cbac99..bb39cbbb 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -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) } diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index e5bdc90f..2804ddfc 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -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; diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 9b2a8f9c..e484b876 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -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); diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index b0dd85ca..a792a1b8 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -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 diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h index 4ead7ff5..d627b41a 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -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>; } } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 1f4df75b..8f171005 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -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) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index a5272808..0f183244 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -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); diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index 66520632..eadf81d9 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -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); diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index b70ae9ac..77ae9102 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -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; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index 80c03d2c..ed28abbf 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -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); diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index f8c1aa4d..9aa325a4 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -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() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index da3522b4..e9fb8431 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -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) { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c2f96aaa..05dc9ba3 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -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"( |