diff options
Diffstat (limited to 'solidityCompiler.cpp')
-rw-r--r-- | solidityCompiler.cpp | 307 |
1 files changed, 155 insertions, 152 deletions
diff --git a/solidityCompiler.cpp b/solidityCompiler.cpp index e024043e..ba2db67e 100644 --- a/solidityCompiler.cpp +++ b/solidityCompiler.cpp @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. @@ -18,18 +17,21 @@ /** * @author Christian <c@ethdev.com> * @date 2014 - * Unit tests for the name and type resolution of the solidity parser. + * Unit tests for the solidity compiler. */ #include <string> - +#include <iostream> +#include <boost/test/unit_test.hpp> #include <libdevcore/Log.h> #include <libsolidity/Scanner.h> #include <libsolidity/Parser.h> #include <libsolidity/NameAndTypeResolver.h> #include <libsolidity/Compiler.h> #include <libsolidity/AST.h> -#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::eth; namespace dev { @@ -41,186 +43,187 @@ 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(std::string const& _sourceCode) +bytes compileContract(const string& _sourceCode) { Parser parser; ASTPointer<ContractDefinition> contract; - BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared<Scanner>(CharStream(_sourceCode)))); + BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared<Scanner>(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(); + Compiler compiler; + compiler.compileContract(*contract); // debug - //std::cout << eth::disassemble(instructions) << std::endl; - return instructions; + //compiler.streamAssembly(cout); + return compiler.getAssembledBytecode(); } -} // end anonymous namespace - -BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) - -BOOST_AUTO_TEST_CASE(literal_true) +/// Checks that @a _compiledCode is present starting from offset @a _offset in @a _expectation. +/// This is necessary since the compiler will add boilerplate add the beginning that is not +/// tested here. +void checkCodePresentAt(bytes const& _compiledCode, bytes const& _expectation, unsigned _offset) { - 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_REQUIRE(_compiledCode.size() >= _offset + _expectation.size()); + auto checkStart = _compiledCode.begin() + _offset; + BOOST_CHECK_EQUAL_COLLECTIONS(checkStart, checkStart + _expectation.size(), + _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); +} // end anonymous namespace - 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_SUITE(SolidityCompiler) -BOOST_AUTO_TEST_CASE(comparison) +BOOST_AUTO_TEST_CASE(smoke_test) { char const* sourceCode = "contract test {\n" - " function f() { var x = (0x10aa < 0x11aa) != true; }" + " function f() { var x = 2; }\n" "}\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()); + bytes code = compileContract(sourceCode); + + unsigned boilerplateSize = 51; + 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)}); + checkCodePresentAt(code, expectation, boilerplateSize); } -BOOST_AUTO_TEST_CASE(short_circuiting) +BOOST_AUTO_TEST_CASE(different_argument_numbers) { char const* sourceCode = "contract test {\n" - " function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" + " 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 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()); + bytes code = compileContract(sourceCode); + + unsigned shift = 75; + unsigned boilerplateSize = 88; + 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), byte(0xa + shift), // 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), byte(0x29 + shift), // ret address + byte(Instruction::PUSH1), 0x1, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), + byte(Instruction::PUSH1), 0x2, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), + byte(Instruction::PUSH1), 0x3, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), + byte(Instruction::PUSH1), byte(0x1 + shift), + // 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 + }); + checkCodePresentAt(code, expectation, boilerplateSize); } -BOOST_AUTO_TEST_CASE(arithmetics) +BOOST_AUTO_TEST_CASE(ifStatement) { char const* sourceCode = "contract test {\n" - " function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }" + " function f() { bool x; if (x) 77; else if (!x) 78; else 79; }" "}\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::SWAP1), - byte(eth::Instruction::MOD), - byte(eth::Instruction::SWAP1), - byte(eth::Instruction::DIV), - byte(eth::Instruction::MUL)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + bytes code = compileContract(sourceCode); + + unsigned shift = 38; + unsigned boilerplateSize = 51; + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x0, + byte(Instruction::DUP1), + byte(Instruction::PUSH1), byte(0x1b + shift), // "true" target + byte(Instruction::JUMPI), + // new check "else if" condition + byte(Instruction::DUP1), + byte(Instruction::ISZERO), + byte(Instruction::PUSH1), byte(0x13 + shift), + byte(Instruction::JUMPI), + // "else" body + byte(Instruction::PUSH1), 0x4f, + byte(Instruction::POP), + byte(Instruction::PUSH1), byte(0x17 + shift), // 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), byte(0x1f + shift), + 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)}); + checkCodePresentAt(code, expectation, boilerplateSize); } -BOOST_AUTO_TEST_CASE(unary_operators) +BOOST_AUTO_TEST_CASE(loops) { char const* sourceCode = "contract test {\n" - " function f() { var x = !(~+-(--(++1++)--) == 2); }" + " function f() { while(true){1;break;2;continue;3;return;4;} }" "}\n"; - bytes code = compileFirstExpression(sourceCode); - - bytes expectation({byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::ADD), - byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::SWAP1), - byte(eth::Instruction::SUB), - byte(eth::Instruction::PUSH1), 0x0, - byte(eth::Instruction::SUB), - byte(eth::Instruction::NOT), - byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::EQ), - byte(eth::Instruction::ISZERO)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + bytes code = compileContract(sourceCode); + + unsigned shift = 38; + unsigned boilerplateSize = 51; + bytes expectation({byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::PUSH1), 0x1, + byte(Instruction::ISZERO), + byte(Instruction::PUSH1), byte(0x21 + shift), + byte(Instruction::JUMPI), + byte(Instruction::PUSH1), 0x1, + byte(Instruction::POP), + byte(Instruction::PUSH1), byte(0x21 + shift), + byte(Instruction::JUMP), // break + byte(Instruction::PUSH1), 0x2, + byte(Instruction::POP), + byte(Instruction::PUSH1), byte(0x2 + shift), + byte(Instruction::JUMP), // continue + byte(Instruction::PUSH1), 0x3, + byte(Instruction::POP), + byte(Instruction::PUSH1), byte(0x22 + shift), + byte(Instruction::JUMP), // return + byte(Instruction::PUSH1), 0x4, + byte(Instruction::POP), + byte(Instruction::PUSH1), byte(0x2 + shift), + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMP)}); + + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_SUITE_END() |