diff options
author | Gav Wood <g@ethdev.com> | 2014-11-06 06:10:25 +0800 |
---|---|---|
committer | Gav Wood <g@ethdev.com> | 2014-11-06 06:10:25 +0800 |
commit | 4fd530646f6c0b9d8c0e70f802e544c1e8dee89e (patch) | |
tree | acb15d46aad2b104792eda9ce9586aec14a18e7c | |
parent | d59a648985274fa587e9f0b69f681ea240ac8d1c (diff) | |
parent | 693bc9027513f83a148df1a2c2da089a6b5e4ffd (diff) | |
download | dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar.gz dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar.bz2 dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar.lz dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar.xz dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.tar.zst dexon-solidity-4fd530646f6c0b9d8c0e70f802e544c1e8dee89e.zip |
Merge pull request #461 from chriseth/sol_contractCompiler
Solidity contract compiler
-rw-r--r-- | solidityCompiler.cpp | 307 | ||||
-rw-r--r-- | solidityEndToEndTest.cpp | 229 | ||||
-rw-r--r-- | solidityExpressionCompiler.cpp | 352 |
3 files changed, 736 insertions, 152 deletions
diff --git a/solidityCompiler.cpp b/solidityCompiler.cpp index e024043e..17de9af5 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), 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), 0x20 + shift, // ret address + byte(Instruction::PUSH1), 0x1, + byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x3, + byte(Instruction::PUSH1), 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), 0x1b + shift, // "true" target + byte(Instruction::JUMPI), + // new check "else if" condition + byte(Instruction::DUP1), + byte(Instruction::ISZERO), + byte(Instruction::PUSH1), 0x13 + shift, + byte(Instruction::JUMPI), + // "else" body + byte(Instruction::PUSH1), 0x4f, + byte(Instruction::POP), + byte(Instruction::PUSH1), 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), 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), 0x21 + shift, + byte(Instruction::JUMPI), + byte(Instruction::PUSH1), 0x1, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x21 + shift, + byte(Instruction::JUMP), // break + byte(Instruction::PUSH1), 0x2, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x2 + shift, + byte(Instruction::JUMP), // continue + byte(Instruction::PUSH1), 0x3, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x22 + shift, + byte(Instruction::JUMP), // return + byte(Instruction::PUSH1), 0x4, + byte(Instruction::POP), + byte(Instruction::PUSH1), 0x2 + shift, + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMPDEST), + byte(Instruction::JUMP)}); + + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_SUITE_END() diff --git a/solidityEndToEndTest.cpp b/solidityEndToEndTest.cpp new file mode 100644 index 00000000..b28b8499 --- /dev/null +++ b/solidityEndToEndTest.cpp @@ -0,0 +1,229 @@ + +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Unit tests for the solidity expression compiler, testing the behaviour of the code. + */ + +#include <string> +#include <boost/test/unit_test.hpp> +#include <libethereum/State.h> +#include <libethereum/Executive.h> +#include <libsolidity/CompilerStack.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +class ExecutionFramework +{ +public: + ExecutionFramework() { g_logVerbosity = 0; } + + bytes compileAndRun(std::string const& _sourceCode) + { + bytes code = dev::solidity::CompilerStack::compile(_sourceCode); + sendMessage(code, true); + return m_output; + } + + bytes callFunction(byte _index, bytes const& _data) + { + sendMessage(bytes(1, _index) + _data, false); + return m_output; + } + + bytes callFunction(byte _index, u256 const& _argument1) + { + callFunction(_index, toBigEndian(_argument1)); + return m_output; + } + +private: + void sendMessage(bytes const& _data, bool _isCreation) + { + eth::Executive executive(m_state); + eth::Transaction t = _isCreation ? eth::Transaction(0, m_gasPrice, m_gas, _data) + : eth::Transaction(0, m_gasPrice, m_gas, m_contractAddress, _data); + bytes transactionRLP = t.rlp(); + try + { + // this will throw since the transaction is invalid, but it should nevertheless store the transaction + executive.setup(&transactionRLP); + } + catch (...) {} + if (_isCreation) + { + BOOST_REQUIRE(!executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address())); + m_contractAddress = executive.newAddress(); + BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); + } + else + BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address())); + BOOST_REQUIRE(executive.go()); + executive.finalize(); + m_output = executive.out().toBytes(); + } + + Address m_contractAddress; + eth::State m_state; + u256 const m_gasPrice = 100 * eth::szabo; + u256 const m_gas = 1000000; + bytes m_output; +}; + +BOOST_AUTO_TEST_SUITE(SolidityCompilerEndToEndTest) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* sourceCode = "contract test {\n" + " function f(uint a) returns(uint d) { return a * 7; }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + u256 a = 0x200030004; + bytes result = framework.callFunction(0, a); + BOOST_CHECK(result == toBigEndian(a * 7)); +} + +BOOST_AUTO_TEST_CASE(empty_contract) +{ + char const* sourceCode = "contract test {\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, bytes()).empty()); +} + +BOOST_AUTO_TEST_CASE(recursive_calls) +{ + char const* sourceCode = "contract test {\n" + " function f(uint n) returns(uint nfac) {\n" + " if (n <= 1) return 1;\n" + " else return n * f(n - 1);\n" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2))); + BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6))); + BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24))); +} + +BOOST_AUTO_TEST_CASE(while_loop) +{ + char const* sourceCode = "contract test {\n" + " function f(uint n) returns(uint nfac) {\n" + " nfac = 1;\n" + " var i = 2;\n" + " while (i <= n) nfac *= i++;\n" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2))); + BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6))); + BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24))); +} + +BOOST_AUTO_TEST_CASE(calling_other_functions) +{ + // note that the index of a function is its index in the sorted sequence of functions + char const* sourceCode = "contract collatz {\n" + " function run(uint x) returns(uint y) {\n" + " while ((y = x) > 1) {\n" + " if (x % 2 == 0) x = evenStep(x);\n" + " else x = oddStep(x);\n" + " }\n" + " }\n" + " function evenStep(uint x) returns(uint y) {\n" + " return x / 2;\n" + " }\n" + " function oddStep(uint x) returns(uint y) {\n" + " return 3 * x + 1;\n" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(2, u256(0)) == toBigEndian(u256(0))); + BOOST_CHECK(framework.callFunction(2, u256(1)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(2, u256(2)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(2, u256(8)) == toBigEndian(u256(1))); + BOOST_CHECK(framework.callFunction(2, u256(127)) == toBigEndian(u256(1))); +} + +BOOST_AUTO_TEST_CASE(many_local_variables) +{ + char const* sourceCode = "contract test {\n" + " function run(uint x1, uint x2, uint x3) returns(uint y) {\n" + " var a = 0x1; var b = 0x10; var c = 0x100;\n" + " y = a + b + c + x1 + x2 + x3;\n" + " y += b + x2;\n" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, toBigEndian(u256(0x1000)) + toBigEndian(u256(0x10000)) + toBigEndian(u256(0x100000))) + == toBigEndian(u256(0x121121))); +} + +BOOST_AUTO_TEST_CASE(multiple_return_values) +{ + char const* sourceCode = "contract test {\n" + " function run(bool x1, uint x2) returns(uint y1, bool y2, uint y3) {\n" + " y1 = x2; y2 = x1;\n" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, bytes(1, 1) + toBigEndian(u256(0xcd))) + == toBigEndian(u256(0xcd)) + bytes(1, 1) + toBigEndian(u256(0))); +} + +BOOST_AUTO_TEST_CASE(short_circuiting) +{ + char const* sourceCode = "contract test {\n" + " function run(uint x) returns(uint y) {\n" + " x == 0 || ((x = 8) > 0);\n" + " return x;" + " }\n" + "}\n"; + ExecutionFramework framework; + framework.compileAndRun(sourceCode); + BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(0))); + BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(8))); +} + +//@todo test smaller types + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces + diff --git a/solidityExpressionCompiler.cpp b/solidityExpressionCompiler.cpp new file mode 100644 index 00000000..561cc3bd --- /dev/null +++ b/solidityExpressionCompiler.cpp @@ -0,0 +1,352 @@ + +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Unit tests for the solidity expression compiler. + */ + +#include <string> + +#include <libdevcore/Log.h> +#include <libsolidity/Scanner.h> +#include <libsolidity/Parser.h> +#include <libsolidity/NameAndTypeResolver.h> +#include <libsolidity/CompilerContext.h> +#include <libsolidity/ExpressionCompiler.h> +#include <libsolidity/AST.h> +#include <boost/test/unit_test.hpp> + +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<string> 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<vector<string>> _functions = {}, + vector<vector<string>> _localVariables = {}) +{ + Parser parser; + ASTPointer<ContractDefinition> contract; + 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; + for (vector<string> const& function: _functions) + context.addFunction(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); + for (vector<string> const& variable: _localVariables) + context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver))); + + ExpressionCompiler::compileExpression(context, *extractor.getExpression()); + + for (vector<string> const& function: _functions) + context << context.getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(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::ISZERO)}); + 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::ISZERO), // 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::POP), + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::EQ), + byte(eth::Instruction::ISZERO), // after this we have 2 != 9 + byte(eth::Instruction::JUMPDEST), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::EQ), + byte(eth::Instruction::ISZERO)}); + 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::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()); +} + +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::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()); +} + +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), 0x0a, + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x01, + byte(eth::Instruction::ADD), + // Stack here: a b <ret label> (a+1) + byte(eth::Instruction::DUP3), + byte(eth::Instruction::PUSH1), 0x14, + 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 + |