From b0d4e0dedfdabe7c02bd1d7f3870d19fd498b74b Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 01:20:32 +0100 Subject: [PATCH] Contract compiler and also add ExpressionStatement to AST. ExpressionStatement functions as glue between Statements and Expressions. This way it is possible to detect when the border between statements and expressions is crossed while walking the AST. Note that ExpressionStatement is not the only border, almost every statement can contains expressions. --- solidityCompiler.cpp | 315 ++++++++++++----------------- solidityExpressionCompiler.cpp | 348 +++++++++++++++++++++++++++++++++ solidityParser.cpp | 2 +- 3 files changed, 475 insertions(+), 190 deletions(-) create mode 100644 solidityExpressionCompiler.cpp diff --git a/solidityCompiler.cpp b/solidityCompiler.cpp index 591330dd6..c9cf464d9 100644 --- a/solidityCompiler.cpp +++ b/solidityCompiler.cpp @@ -18,7 +18,7 @@ /** * @author Christian * @date 2014 - * Unit tests for the name and type resolution of the solidity parser. + * Unit tests for the solidity compiler. */ #include @@ -31,6 +31,9 @@ #include #include +using namespace std; +using namespace dev::eth; + namespace dev { namespace solidity @@ -41,233 +44,167 @@ namespace test namespace { -/// Helper class that extracts the first expression in an AST. -class FirstExpressionExtractor: private ASTVisitor -{ -public: - FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } - Expression* getExpression() const { return m_expression; } -private: - virtual bool visit(Expression& _expression) override { return checkExpression(_expression); } - virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } - virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } - virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } - virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } - virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } - virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } - virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); } - virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } - virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } - virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } - bool checkExpression(Expression& _expression) - { - if (m_expression == nullptr) - m_expression = &_expression; - return false; - } -private: - Expression* m_expression; -}; - -bytes compileFirstExpression(const std::string& _sourceCode) +bytes compileContract(const string& _sourceCode) { Parser parser; ASTPointer contract; - BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared(CharStream(_sourceCode)))); + BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared(CharStream(_sourceCode)))); NameAndTypeResolver resolver; BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); - FirstExpressionExtractor extractor(*contract); - BOOST_REQUIRE(extractor.getExpression() != nullptr); - CompilerContext context; - ExpressionCompiler compiler(context); - compiler.compile(*extractor.getExpression()); - bytes instructions = compiler.getAssembledBytecode(); + bytes instructions = Compiler::compile(*contract); // debug - //std::cout << eth::disassemble(instructions) << std::endl; + //cout << eth::disassemble(instructions) << endl; return instructions; } } // end anonymous namespace -BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) +BOOST_AUTO_TEST_SUITE(SolidityCompiler) -BOOST_AUTO_TEST_CASE(literal_true) +BOOST_AUTO_TEST_CASE(smoke_test) { char const* sourceCode = "contract test {\n" - " function f() { var x = true; }" + " function f() { var x = 2; }\n" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileContract(sourceCode); - bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x0, // initialize local variable x + byte(Instruction::PUSH1), 0x2, + byte(Instruction::SWAP1), + byte(Instruction::POP), + byte(Instruction::JUMPDEST), + byte(Instruction::POP), + byte(Instruction::JUMP)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(literal_false) +BOOST_AUTO_TEST_CASE(different_argument_numbers) { char const* sourceCode = "contract test {\n" - " function f() { var x = false; }" + " function f(uint a, uint b, uint c) returns(uint d) { return b; }\n" + " function g() returns (uint e, uint h) { h = f(1, 2, 3); }\n" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileContract(sourceCode); - bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x0, // initialize return variable d + byte(Instruction::DUP3), + byte(Instruction::SWAP1), // assign b to d + byte(Instruction::POP), + byte(Instruction::PUSH1), 0xa, // jump to return + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::SWAP4), // store d and fetch return address + byte(Instruction::SWAP3), // store return address + byte(Instruction::POP), + byte(Instruction::POP), + byte(Instruction::POP), + byte(Instruction::JUMP), // end of f + byte(Instruction::JUMPDEST), // beginning of g + byte(Instruction::PUSH1), 0x0, + byte(Instruction::DUP1), // initialized e and h + byte(Instruction::PUSH1), 0x20, // ret address + byte(Instruction::PUSH1), 0x1, + byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x3, + byte(Instruction::PUSH1), 0x1, + // stack here: ret e h 0x20 1 2 3 0x1 + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + // stack here: ret e h f(1,2,3) + byte(Instruction::DUP2), + byte(Instruction::POP), + byte(Instruction::SWAP1), + // stack here: ret e f(1,2,3) h + byte(Instruction::POP), + byte(Instruction::DUP1), // retrieve it again as "value of expression" + byte(Instruction::POP), // end of assignment + // stack here: ret e f(1,2,3) + byte(Instruction::JUMPDEST), + byte(Instruction::SWAP1), + // ret e f(1,2,3) + byte(Instruction::SWAP2), + // f(1,2,3) e ret + byte(Instruction::JUMP) // end of g + }); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(int_literal) +BOOST_AUTO_TEST_CASE(ifStatement) { char const* sourceCode = "contract test {\n" - " function f() { var x = 0x12345678901234567890; }" + " function f() { bool x; if (x) 77; else if (!x) 78; else 79; }" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileContract(sourceCode); - bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, - 0x12, 0x34, 0x56, 0x78, 0x90}); + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x0, + byte(Instruction::DUP1), + byte(Instruction::PUSH1), 0x1b, // "true" target + byte(Instruction::JUMPI), + // new check "else if" condition + byte(Instruction::DUP1), + byte(Instruction::NOT), + byte(Instruction::PUSH1), 0x13, + byte(Instruction::JUMPI), + // "else" body + byte(Instruction::PUSH1), 0x4f, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x17, // exit path of second part + byte(Instruction::JUMP), + // "else if" body + byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x4e, + byte(Instruction::POP), + byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x1f, + byte(Instruction::JUMP), + // "if" body + byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x4d, + byte(Instruction::POP), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::POP), + byte(Instruction::JUMP)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(comparison) +BOOST_AUTO_TEST_CASE(loops) { char const* sourceCode = "contract test {\n" - " function f() { var x = (0x10aa < 0x11aa) != true; }" + " function f() { while(true){1;break;2;continue;3;return;4;} }" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileContract(sourceCode); - bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa, - byte(eth::Instruction::PUSH2), 0x11, 0xaa, - byte(eth::Instruction::GT), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::EQ), - byte(eth::Instruction::NOT)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x1, + byte(Instruction::NOT), + byte(Instruction::PUSH1), 0x21, + byte(Instruction::JUMPI), + byte(Instruction::PUSH1), 0x1, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x21, + byte(Instruction::JUMP), // break + byte(Instruction::PUSH1), 0x2, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x2, + byte(Instruction::JUMP), // continue + byte(Instruction::PUSH1), 0x3, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x22, + byte(Instruction::JUMP), // return + byte(Instruction::PUSH1), 0x4, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x2, + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMP)}); -BOOST_AUTO_TEST_CASE(short_circuiting) -{ - char const* sourceCode = "contract test {\n" - " function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" - "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::PUSH1), 0xa, - byte(eth::Instruction::PUSH1), 0x8, - byte(eth::Instruction::ADD), - byte(eth::Instruction::PUSH1), 0x4, - byte(eth::Instruction::GT), - byte(eth::Instruction::NOT), // after this we have 10 + 8 >= 4 - byte(eth::Instruction::DUP1), - byte(eth::Instruction::PUSH1), 0x14, - byte(eth::Instruction::JUMPI), // short-circuit if it is true - byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::PUSH1), 0x9, - byte(eth::Instruction::EQ), - byte(eth::Instruction::NOT), // after this we have 2 != 9 - byte(eth::Instruction::JUMPDEST), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::EQ), - byte(eth::Instruction::NOT)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - -BOOST_AUTO_TEST_CASE(arithmetics) -{ - char const* sourceCode = "contract test {\n" - " function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }" - "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::PUSH1), 0x3, - byte(eth::Instruction::PUSH1), 0x4, - byte(eth::Instruction::PUSH1), 0x5, - byte(eth::Instruction::PUSH1), 0x6, - byte(eth::Instruction::PUSH1), 0x7, - byte(eth::Instruction::PUSH1), 0x8, - byte(eth::Instruction::PUSH1), 0x9, - byte(eth::Instruction::XOR), - byte(eth::Instruction::AND), - byte(eth::Instruction::OR), - byte(eth::Instruction::SWAP1), - byte(eth::Instruction::SUB), - byte(eth::Instruction::ADD), - byte(eth::Instruction::MOD), - byte(eth::Instruction::DIV), - byte(eth::Instruction::MUL)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - -BOOST_AUTO_TEST_CASE(unary_operators) -{ - char const* sourceCode = "contract test {\n" - " function f() { var x = !(~+-1 == 2); }" - "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::PUSH1), 0x0, - byte(eth::Instruction::SUB), - byte(eth::Instruction::BNOT), - byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::EQ), - byte(eth::Instruction::NOT)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - -BOOST_AUTO_TEST_CASE(unary_inc_dec) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a) { var x = ((a++ ^ ++a) ^ a--) ^ --a; }" - "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::DUP9), // will change as soon as we have real stack tracking - byte(eth::Instruction::DUP1), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::ADD), - byte(eth::Instruction::SWAP8), // will change - byte(eth::Instruction::POP), // first ++ - byte(eth::Instruction::DUP9), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::ADD), - byte(eth::Instruction::SWAP8), // will change - byte(eth::Instruction::POP), // second ++ - byte(eth::Instruction::DUP8), // will change - byte(eth::Instruction::XOR), - byte(eth::Instruction::DUP9), // will change - byte(eth::Instruction::DUP1), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::SWAP1), - byte(eth::Instruction::SUB), - byte(eth::Instruction::SWAP8), // will change - byte(eth::Instruction::POP), // first -- - byte(eth::Instruction::XOR), - byte(eth::Instruction::DUP9), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::SWAP1), - byte(eth::Instruction::SUB), - byte(eth::Instruction::SWAP8), // will change - byte(eth::Instruction::POP), // second ++ - byte(eth::Instruction::DUP8), // will change - byte(eth::Instruction::XOR)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - -BOOST_AUTO_TEST_CASE(assignment) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a, uint b) { (a += b) * 2; }" - "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::DUP9), // will change as soon as we have real stack tracking - byte(eth::Instruction::DUP9), - byte(eth::Instruction::ADD), - byte(eth::Instruction::SWAP8), // will change - byte(eth::Instruction::POP), // first ++ - byte(eth::Instruction::DUP8), - byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::MUL)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } diff --git a/solidityExpressionCompiler.cpp b/solidityExpressionCompiler.cpp new file mode 100644 index 000000000..2a58d670a --- /dev/null +++ b/solidityExpressionCompiler.cpp @@ -0,0 +1,348 @@ + +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Unit tests for the solidity expression compiler. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + +/// Helper class that extracts the first expression in an AST. +class FirstExpressionExtractor: private ASTVisitor +{ +public: + FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } + Expression* getExpression() const { return m_expression; } +private: + virtual bool visit(Expression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } + virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } + virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } + virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } + bool checkExpression(Expression& _expression) + { + if (m_expression == nullptr) + m_expression = &_expression; + return false; + } +private: + Expression* m_expression; +}; + +Declaration const& resolveDeclaration(vector const& _namespacedName, + NameAndTypeResolver const& _resolver) +{ + Declaration const* declaration = nullptr; + for (string const& namePart: _namespacedName) + BOOST_REQUIRE(declaration = _resolver.resolveName(namePart, declaration)); + BOOST_REQUIRE(declaration); + return *declaration; +} + +bytes compileFirstExpression(const string& _sourceCode, vector> _functions = {}, + vector> _localVariables = {}) +{ + Parser parser; + ASTPointer contract; + BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared(CharStream(_sourceCode)))); + NameAndTypeResolver resolver; + BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + FirstExpressionExtractor extractor(*contract); + BOOST_REQUIRE(extractor.getExpression() != nullptr); + + CompilerContext context; + for (vector const& function: _functions) + context.addFunction(dynamic_cast(resolveDeclaration(function, resolver))); + for (vector const& variable: _localVariables) + context.addVariable(dynamic_cast(resolveDeclaration(variable, resolver))); + + ExpressionCompiler::compileExpression(context, *extractor.getExpression()); + + for (vector const& function: _functions) + context << context.getFunctionEntryLabel(dynamic_cast(resolveDeclaration(function, resolver))); + bytes instructions = context.getAssembledBytecode(); + // debug + // cout << eth::disassemble(instructions) << endl; + return instructions; +} + +} // end anonymous namespace + +BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) + +BOOST_AUTO_TEST_CASE(literal_true) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(literal_false) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = false; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(int_literal) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = 0x12345678901234567890; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, + 0x12, 0x34, 0x56, 0x78, 0x90}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(comparison) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (0x10aa < 0x11aa) != true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa, + byte(eth::Instruction::PUSH2), 0x11, 0xaa, + byte(eth::Instruction::GT), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(short_circuiting) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0xa, + byte(eth::Instruction::PUSH1), 0x8, + byte(eth::Instruction::ADD), + byte(eth::Instruction::PUSH1), 0x4, + byte(eth::Instruction::GT), + byte(eth::Instruction::NOT), // after this we have 10 + 8 >= 4 + byte(eth::Instruction::DUP1), + byte(eth::Instruction::PUSH1), 0x14, + byte(eth::Instruction::JUMPI), // short-circuit if it is true + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT), // after this we have 2 != 9 + byte(eth::Instruction::JUMPDEST), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(arithmetics) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x3, + byte(eth::Instruction::PUSH1), 0x4, + byte(eth::Instruction::PUSH1), 0x5, + byte(eth::Instruction::PUSH1), 0x6, + byte(eth::Instruction::PUSH1), 0x7, + byte(eth::Instruction::PUSH1), 0x8, + byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::XOR), + byte(eth::Instruction::AND), + byte(eth::Instruction::OR), + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::SUB), + byte(eth::Instruction::ADD), + byte(eth::Instruction::MOD), + byte(eth::Instruction::DIV), + byte(eth::Instruction::MUL)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(unary_operators) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = !(~+-1 == 2); }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::PUSH1), 0x0, + byte(eth::Instruction::SUB), + byte(eth::Instruction::BNOT), + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(unary_inc_dec) +{ + char const* sourceCode = "contract test {\n" + " function f(uint a) { var x = ((a++ ^ ++a) ^ a--) ^ --a; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); + + // Stack: a, x + bytes expectation({byte(eth::Instruction::DUP2), + byte(eth::Instruction::DUP1), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::ADD), + // Stack here: a x a (a+1) + byte(eth::Instruction::SWAP3), + byte(eth::Instruction::POP), // first ++ + // Stack here: (a+1) x a + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::ADD), + // Stack here: (a+1) x a (a+2) + byte(eth::Instruction::SWAP3), + byte(eth::Instruction::POP), + // Stack here: (a+2) x a + byte(eth::Instruction::DUP3), // second ++ + byte(eth::Instruction::XOR), + // Stack here: (a+2) x a^(a+2) + byte(eth::Instruction::DUP3), + byte(eth::Instruction::DUP1), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::SUB), + // Stack here: (a+2) x a^(a+2) (a+2) (a+1) + byte(eth::Instruction::SWAP4), + byte(eth::Instruction::POP), // first -- + byte(eth::Instruction::XOR), + // Stack here: (a+1) x a^(a+2)^(a+2) + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::SUB), + // Stack here: (a+1) x a^(a+2)^(a+2) a + byte(eth::Instruction::SWAP3), + byte(eth::Instruction::POP), // second ++ + // Stack here: a x a^(a+2)^(a+2) + byte(eth::Instruction::DUP3), // will change + byte(eth::Instruction::XOR)}); + // Stack here: a x a^(a+2)^(a+2)^a + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(assignment) +{ + char const* sourceCode = "contract test {\n" + " function f(uint a, uint b) { (a += b) * 2; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); + + // Stack: a, b + bytes expectation({byte(eth::Instruction::DUP1), + byte(eth::Instruction::DUP3), + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::ADD), + // Stack here: a b a+b + byte(eth::Instruction::SWAP2), + byte(eth::Instruction::POP), + byte(eth::Instruction::DUP2), + // Stack here: a+b b a+b + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::MUL)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(function_call) +{ + char const* sourceCode = "contract test {\n" + " function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" + " function g(uint a, uint b) returns (uint c) {}\n" + "}\n"; + bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, + {{"test", "f", "a"}, {"test", "f", "b"}}); + + // Stack: a, b + bytes expectation({byte(eth::Instruction::PUSH1), 0x0b, + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x01, + byte(eth::Instruction::ADD), + // Stack here: a b (a+1) + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x15, + byte(eth::Instruction::JUMP), + byte(eth::Instruction::JUMPDEST), + // Stack here: a b g(a+1, b) + byte(eth::Instruction::PUSH1), 0x02, + byte(eth::Instruction::MUL), + // Stack here: a b g(a+1, b)*2 + byte(eth::Instruction::DUP3), + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::ADD), + // Stack here: a b a+g(a+1, b)*2 + byte(eth::Instruction::SWAP2), + byte(eth::Instruction::POP), + byte(eth::Instruction::DUP2), + byte(eth::Instruction::JUMPDEST)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces + diff --git a/solidityParser.cpp b/solidityParser.cpp index 025cd74d1..701a6e76c 100644 --- a/solidityParser.cpp +++ b/solidityParser.cpp @@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(while_loop) { char const* text = "contract test {\n" " function fun(uint256 a) {\n" - " uint256 x = (1 + 4).member(++67) || true;\n" + " while (true) { uint256 x = 1; break; continue; } x = 9;\n" " }\n" "}\n"; BOOST_CHECK_NO_THROW(parseText(text));