From 30e4cda0e9bddf58c0f89bcf3b21a90a2557fea6 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 31 Oct 2014 17:47:43 +0100 Subject: [PATCH] Some tests and bugfixes for the compiler. --- libethereum/Account.h | 2 +- libsolidity/Compiler.cpp | 32 ++--- libsolidity/Compiler.h | 1 - libsolidity/ExpressionCompiler.cpp | 1 + test/solidityEndToEndTest.cpp | 210 ++++++++++++++++++++++++++++ test/solidityExpressionCompiler.cpp | 3 +- 6 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 test/solidityEndToEndTest.cpp diff --git a/libethereum/Account.h b/libethereum/Account.h index ce7004083..7e2fc38ab 100644 --- a/libethereum/Account.h +++ b/libethereum/Account.h @@ -82,7 +82,7 @@ public: /// Construct an alive Account, with given endowment, for either a normal (non-contract) account or for a /// contract account in the /// conception phase, where the code is not yet known. - Account(u256 _balance, NewAccountType _t): m_isAlive(true), m_balance(_balance), m_codeHash(_t == NormalCreation ? c_contractConceptionCodeHash : EmptySHA3) {} + Account(u256 _balance, NewAccountType _t): m_isAlive(true), m_balance(_balance), m_codeHash(_t == NormalCreation ? EmptySHA3 : c_contractConceptionCodeHash) {} /// Explicit constructor for wierd cases of construction of a normal account. Account(u256 _nonce, u256 _balance): m_isAlive(true), m_nonce(_nonce), m_balance(_balance) {} diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 7909e0701..a44c177d7 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -94,25 +94,20 @@ void Compiler::appendFunctionSelector(vector> con for (pair> const& f: publicFunctions) m_context.appendJumpTo(f.second.second) << eth::Instruction::JUMPDEST; - m_context << returnTag << eth::Instruction::RETURN; + m_context << returnTag << eth::Instruction::STOP; for (pair> const& f: publicFunctions) { + FunctionDefinition const& function = *f.second.first; m_context << f.second.second; - appendFunctionCallSection(*f.second.first); - } -} - -void Compiler::appendFunctionCallSection(FunctionDefinition const& _function) -{ - eth::AssemblyItem returnTag = m_context.pushNewTag(); - appendCalldataUnpacker(_function); + eth::AssemblyItem returnTag = m_context.pushNewTag(); + appendCalldataUnpacker(function); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(function)); + m_context << returnTag; - m_context.appendJumpTo(m_context.getFunctionEntryLabel(_function)); - m_context << returnTag; - - appendReturnValuePacker(_function); + appendReturnValuePacker(function); + } } void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function) @@ -142,7 +137,7 @@ void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) //@todo this can be also done more efficiently unsigned dataOffset = 0; vector> const& parameters = _function.getReturnParameters(); - for (unsigned i = 0 ; i < parameters.size(); ++i) + for (unsigned i = 0; i < parameters.size(); ++i) { unsigned numBytes = parameters[i]->getType()->getCalldataEncodedSize(); if (numBytes == 0) @@ -150,11 +145,9 @@ void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) << errinfo_sourceLocation(parameters[i]->getLocation()) << errinfo_comment("Type not yet supported.")); m_context << eth::dupInstruction(parameters.size() - i); - if (numBytes == 32) - m_context << u256(dataOffset) << eth::Instruction::MSTORE; - else - m_context << u256(dataOffset) << (u256(1) << ((32 - numBytes) * 8)) - << eth::Instruction::MUL << eth::Instruction::MSTORE; + if (numBytes != 32) + m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; + m_context << u256(dataOffset) << eth::Instruction::MSTORE; dataOffset += numBytes; } // note that the stack is not cleaned up here @@ -215,6 +208,7 @@ bool Compiler::visit(FunctionDefinition& _function) m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1); swap(stackLayout[stackLayout.back()], stackLayout.back()); } + //@todo assert that everything is in place now m_context << eth::Instruction::JUMP; diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index d10374a9b..4e4d90d45 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -43,7 +43,6 @@ private: /// Creates a new compiler context / assembly and packs the current code into the data part. void packIntoContractCreator(); void appendFunctionSelector(std::vector > const& _functions); - void appendFunctionCallSection(FunctionDefinition const& _function); void appendCalldataUnpacker(FunctionDefinition const& _function); void appendReturnValuePacker(FunctionDefinition const& _function); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 871b64184..f9cbee377 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -264,6 +264,7 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperati if (op == Token::AND) m_context << eth::Instruction::ISZERO; eth::AssemblyItem endLabel = m_context.appendConditionalJump(); + m_context << eth::Instruction::POP; _binaryOperation.getRightExpression().accept(*this); m_context << endLabel; } diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp new file mode 100644 index 000000000..7bb01fa12 --- /dev/null +++ b/test/solidityEndToEndTest.cpp @@ -0,0 +1,210 @@ + +/* + 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, testing the behaviour of the code. + */ + +#include +#include +#include +#include +#include + +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); + eth::Executive ex(m_state); + BOOST_REQUIRE(!ex.create(Address(), 0, m_gasPrice, m_gas, &code, Address())); + BOOST_REQUIRE(ex.go()); + ex.finalize(); + m_contractAddress = ex.newAddress(); + return ex.out().toBytes(); + } + + bytes callFunction(byte _index, bytes const& _data) + { + bytes data = bytes(1, _index) + _data; + eth::Executive ex(m_state); + BOOST_REQUIRE(!ex.call(m_contractAddress, Address(), 0, m_gasPrice, &data, m_gas, Address())); + BOOST_REQUIRE(ex.go()); + ex.finalize(); + return ex.out().toBytes(); + } + + bytes callFunction(byte _index, u256 const& _argument) + { + return callFunction(_index, toBigEndian(_argument)); + } + +private: + Address m_contractAddress; + eth::State m_state; + u256 const m_gasPrice = 100 * eth::szabo; + u256 const m_gas = 1000000; +}; + +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/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index 83a7b2bbf..043a69496 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -177,8 +177,9 @@ BOOST_AUTO_TEST_CASE(short_circuiting) 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::PUSH1), 0x15, 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),