From ec5bf1abe91975e381f39520bb2a3a92a7b6b9a2 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 29 Oct 2014 00:29:07 +0100 Subject: [PATCH 01/20] NEG->BNOT change --- libsolidity/Compiler.cpp | 12 ++++-------- test/solidityCompiler.cpp | 4 +--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index acb0a5cc7..4244a3e09 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -111,13 +111,7 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) append(eth::Instruction::NOT); break; case Token::BIT_NOT: // ~ - // ~a modeled as "a xor (0 - 1)" for now - append(eth::Instruction::PUSH1); - append(1); - append(eth::Instruction::PUSH1); - append(0); - append(eth::Instruction::SUB); - append(eth::Instruction::XOR); + append(eth::Instruction::BNOT); break; case Token::DELETE: // delete // a -> a xor a (= 0). @@ -149,7 +143,9 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) // unary add, so basically no-op break; case Token::SUB: // - - append(eth::Instruction::NEG); + append(eth::Instruction::PUSH1); + append(0); + append(eth::Instruction::SUB); break; default: assert(false); // invalid operation diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 6e12fecf8..02a2287d9 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -210,11 +210,9 @@ BOOST_AUTO_TEST_CASE(unary_operators) byte(eth::Instruction::PUSH1), 0x1, byte(eth::Instruction::SWAP1), byte(eth::Instruction::SUB), - byte(eth::Instruction::NEG), - byte(eth::Instruction::PUSH1), 0x1, byte(eth::Instruction::PUSH1), 0x0, byte(eth::Instruction::SUB), - byte(eth::Instruction::XOR), // bitwise not + byte(eth::Instruction::BNOT), byte(eth::Instruction::PUSH1), 0x2, byte(eth::Instruction::EQ), byte(eth::Instruction::NOT)}); From 2b6d66374dd58601612565bbfd7c67b7fe0a072b Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 25 Oct 2014 16:52:22 +0200 Subject: [PATCH 02/20] Compiler for assignments. --- libsolidity/AST.cpp | 13 ++++- libsolidity/AST.h | 9 ++- libsolidity/Compiler.cpp | 113 ++++++++++++++++++++++++++++++-------- libsolidity/Compiler.h | 16 +++++- libsolidity/Exceptions.h | 1 + solc/main.cpp | 60 +++++++++++--------- test/solidityCompiler.cpp | 62 +++++++++++++++++++-- 7 files changed, 214 insertions(+), 60 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 0b635339d..7b5b6a73c 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -326,6 +326,8 @@ void Assignment::checkTypeRequirements() //@todo lefthandside actually has to be assignable // add a feature to the type system to check that m_leftHandSide->checkTypeRequirements(); + if (!m_leftHandSide->isLvalue()) + BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); expectType(*m_rightHandSide, *m_leftHandSide->getType()); m_type = m_leftHandSide->getType(); if (m_assigmentOperator != Token::ASSIGN) @@ -338,8 +340,13 @@ void Assignment::checkTypeRequirements() void UnaryOperation::checkTypeRequirements() { - // INC, DEC, NOT, BIT_NOT, DELETE + // INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE m_subExpression->checkTypeRequirements(); + if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE) + { + if (!m_subExpression->isLvalue()) + BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); + } m_type = m_subExpression->getType(); if (!m_type->acceptsUnaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); @@ -441,9 +448,9 @@ void Identifier::checkTypeRequirements() if (variable) { if (!variable->getType()) - BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type " - "could be determined.")); + BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type could be determined.")); m_type = variable->getType(); + m_isLvalue = true; return; } //@todo can we unify these with TypeName::toType()? diff --git a/libsolidity/AST.h b/libsolidity/AST.h index db6637aea..856c222eb 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -402,13 +402,17 @@ private: class Expression: public Statement { public: - Expression(Location const& _location): Statement(_location) {} + Expression(Location const& _location): Statement(_location), m_isLvalue(false) {} std::shared_ptr const& getType() const { return m_type; } + bool isLvalue() const { return m_isLvalue; } protected: //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). std::shared_ptr m_type; + //! Whether or not this expression is an lvalue, i.e. something that can be assigned to. + //! This is set during calls to @a checkTypeRequirements() + bool m_isLvalue; }; /// @} @@ -492,6 +496,9 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getExpression() const { return *m_expression; } + std::vector> const& getArguments() const { return m_arguments; } + /// Returns true if this is not an actual function call, but an explicit type conversion /// or constructor call. bool isTypeConversion() const; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 4244a3e09..dbb38324d 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -81,22 +81,29 @@ AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context, return compiler.getAssemblyItems(); } -void ExpressionCompiler::endVisit(Assignment& _assignment) +bool ExpressionCompiler::visit(Assignment& _assignment) { + m_currentLValue = nullptr; + _assignment.getLeftHandSide().accept(*this); + Expression& rightHandSide = _assignment.getRightHandSide(); Token::Value op = _assignment.getAssignmentOperator(); if (op != Token::ASSIGN) { // compound assignment - // @todo retrieve lvalue value rightHandSide.accept(*this); Type const& resultType = *_assignment.getType(); cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); } else + { + append(eth::Instruction::POP); //@todo do not retrieve the value in the first place rightHandSide.accept(*this); - // @todo store value + } + + storeInLValue(_assignment); + return false; } void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) @@ -114,30 +121,31 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) append(eth::Instruction::BNOT); break; case Token::DELETE: // delete + { // a -> a xor a (= 0). - // @todo this should also be an assignment // @todo semantics change for complex types append(eth::Instruction::DUP1); append(eth::Instruction::XOR); + storeInLValue(_unaryOperation); break; + } case Token::INC: // ++ (pre- or postfix) - // @todo this should also be an assignment - if (_unaryOperation.isPrefixOperation()) - { - append(eth::Instruction::PUSH1); - append(1); - append(eth::Instruction::ADD); - } - break; case Token::DEC: // -- (pre- or postfix) - // @todo this should also be an assignment - if (_unaryOperation.isPrefixOperation()) + if (!_unaryOperation.isPrefixOperation()) + append(eth::Instruction::DUP1); + append(eth::Instruction::PUSH1); + append(1); + if (_unaryOperation.getOperator() == Token::INC) + append(eth::Instruction::ADD); + else { - append(eth::Instruction::PUSH1); - append(1); append(eth::Instruction::SWAP1); //@todo avoid this append(eth::Instruction::SUB); } + if (_unaryOperation.isPrefixOperation()) + storeInLValue(_unaryOperation); + else + moveToLValue(_unaryOperation); break; case Token::ADD: // + // unary add, so basically no-op @@ -190,28 +198,38 @@ void ExpressionCompiler::endVisit(FunctionCall& _functionCall) { if (_functionCall.isTypeConversion()) { - //@todo binary representation for all supported types (bool and int) is the same, so no-op - // here for now. + //@todo we only have integers and bools for now which cannot be explicitly converted + assert(_functionCall.getArguments().size() == 1); + cleanHigherOrderBitsIfNeeded(*_functionCall.getArguments().front()->getType(), + *_functionCall.getType()); } else { - //@todo + //@todo: arguments are already on the stack + // push return label (below arguments?) + // jump to function label + // discard all but the first function return argument } } -void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) +void ExpressionCompiler::endVisit(MemberAccess&) { } -void ExpressionCompiler::endVisit(IndexAccess& _indexAccess) +void ExpressionCompiler::endVisit(IndexAccess&) { } void ExpressionCompiler::endVisit(Identifier& _identifier) { - + m_currentLValue = _identifier.getReferencedDeclaration(); + unsigned stackPos = stackPositionOfLValue(); + if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Stack too deep.")); + appendDup(stackPos + 1); } void ExpressionCompiler::endVisit(Literal& _literal) @@ -224,7 +242,7 @@ void ExpressionCompiler::endVisit(Literal& _literal) bytes value = _literal.getType()->literalToBigEndian(_literal); assert(value.size() <= 32); assert(!value.empty()); - append(static_cast(eth::Instruction::PUSH1) + static_cast(value.size() - 1)); + appendPush(value.size()); append(value); break; } @@ -391,6 +409,24 @@ uint32_t ExpressionCompiler::appendConditionalJump() return label; } +void ExpressionCompiler::appendPush(unsigned _number) +{ + assert(1 <= _number && _number <= 32); + append(eth::Instruction(unsigned(eth::Instruction::PUSH1) + _number - 1)); +} + +void ExpressionCompiler::appendDup(unsigned _number) +{ + assert(1 <= _number && _number <= 16); + append(eth::Instruction(unsigned(eth::Instruction::DUP1) + _number - 1)); +} + +void ExpressionCompiler::appendSwap(unsigned _number) +{ + assert(1 <= _number && _number <= 16); + append(eth::Instruction(unsigned(eth::Instruction::SWAP1) + _number - 1)); +} + void ExpressionCompiler::append(bytes const& _data) { m_assemblyItems.reserve(m_assemblyItems.size() + _data.size()); @@ -398,6 +434,37 @@ void ExpressionCompiler::append(bytes const& _data) append(b); } +void ExpressionCompiler::storeInLValue(Expression const& _expression) +{ + assert(m_currentLValue); + moveToLValue(_expression); + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + if (stackPos >= 1) + appendDup(stackPos); +} + +void ExpressionCompiler::moveToLValue(Expression const& _expression) +{ + assert(m_currentLValue); + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + else if (stackPos > 0) + { + appendSwap(stackPos); + append(eth::Instruction::POP); + } +} + +unsigned ExpressionCompiler::stackPositionOfLValue() const +{ + return 8; // @todo ask the context and track stack changes due to m_assemblyItems +} + } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index ac5e10ec9..1c5523e08 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -87,7 +87,7 @@ private: class ExpressionCompiler: public ASTVisitor { public: - ExpressionCompiler(CompilerContext& _compilerContext): m_context(_compilerContext) {} + ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {} /// Compile the given expression and (re-)populate the assembly item list. void compile(Expression& _expression); @@ -98,7 +98,7 @@ public: static AssemblyItems compileExpression(CompilerContext& _context, Expression& _expression); private: - virtual void endVisit(Assignment& _assignment) override; + virtual bool visit(Assignment& _assignment) override; virtual void endVisit(UnaryOperation& _unaryOperation) override; virtual bool visit(BinaryOperation& _binaryOperation) override; virtual void endVisit(FunctionCall& _functionCall) override; @@ -123,6 +123,9 @@ private: /// Appends a JUMPI instruction to a new label and returns the label uint32_t appendConditionalJump(); + void appendPush(unsigned _number); + void appendDup(unsigned _number); + void appendSwap(unsigned _number); /// Append elements to the current instruction list. void append(eth::Instruction const& _instruction) { m_assemblyItems.push_back(AssemblyItem(_instruction)); } @@ -131,6 +134,15 @@ private: void appendLabelref(byte _label) { m_assemblyItems.push_back(AssemblyItem::labelRef(_label)); } void appendLabel(byte _label) { m_assemblyItems.push_back(AssemblyItem::label(_label)); } + /// Stores the value on top of the stack in the current lvalue and copies that value to the + /// top of the stack again + void storeInLValue(Expression const& _expression); + /// The same as storeInLValue but do not again retrieve the value to the top of the stack. + void moveToLValue(Expression const& _expression); + /// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack. + unsigned stackPositionOfLValue() const; + + Declaration* m_currentLValue; AssemblyItems m_assemblyItems; CompilerContext& m_context; }; diff --git a/libsolidity/Exceptions.h b/libsolidity/Exceptions.h index 5a48c47dd..1d981e7c0 100644 --- a/libsolidity/Exceptions.h +++ b/libsolidity/Exceptions.h @@ -34,6 +34,7 @@ namespace solidity struct ParserError: virtual Exception {}; struct TypeError: virtual Exception {}; struct DeclarationError: virtual Exception {}; +struct CompilerError: virtual Exception {}; typedef boost::error_info errinfo_sourcePosition; typedef boost::error_info errinfo_sourceLocation; diff --git a/solc/main.cpp b/solc/main.cpp index f5c4c8a0f..24f07d952 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -34,25 +34,24 @@ #include #include +using namespace std; using namespace dev; using namespace solidity; void help() { - std::cout - << "Usage solc [OPTIONS] " << std::endl - << "Options:" << std::endl - << " -h,--help Show this help message and exit." << std::endl - << " -V,--version Show the version and exit." << std::endl; + cout << "Usage solc [OPTIONS] " << endl + << "Options:" << endl + << " -h,--help Show this help message and exit." << endl + << " -V,--version Show the version and exit." << endl; exit(0); } void version() { - std::cout - << "solc, the solidity complier commandline interface " << dev::Version << std::endl - << " by Christian , (c) 2014." << std::endl - << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << std::endl; + cout << "solc, the solidity complier commandline interface " << dev::Version << endl + << " by Christian , (c) 2014." << endl + << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; exit(0); } @@ -87,10 +86,10 @@ private: int main(int argc, char** argv) { - std::string infile; + string infile; for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; + string arg = argv[i]; if (arg == "-h" || arg == "--help") help(); else if (arg == "-V" || arg == "--version") @@ -98,13 +97,13 @@ int main(int argc, char** argv) else infile = argv[i]; } - std::string sourceCode; + string sourceCode; if (infile.empty()) { - std::string s; - while (!std::cin.eof()) + string s; + while (!cin.eof()) { - getline(std::cin, s); + getline(cin, s); sourceCode.append(s); } } @@ -112,7 +111,7 @@ int main(int argc, char** argv) sourceCode = asString(dev::contents(infile)); ASTPointer ast; - std::shared_ptr scanner = std::make_shared(CharStream(sourceCode)); + shared_ptr scanner = make_shared(CharStream(sourceCode)); Parser parser; try { @@ -120,39 +119,48 @@ int main(int argc, char** argv) } catch (ParserError const& exception) { - SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Parser error", *scanner); + SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Parser error", *scanner); return -1; } - dev::solidity::NameAndTypeResolver resolver; + NameAndTypeResolver resolver; try { resolver.resolveNamesAndTypes(*ast.get()); } catch (DeclarationError const& exception) { - SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Declaration error", *scanner); + SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Declaration error", *scanner); return -1; } catch (TypeError const& exception) { - SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Type error", *scanner); + SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Type error", *scanner); return -1; } - std::cout << "Syntax tree for the contract:" << std::endl; + cout << "Syntax tree for the contract:" << endl; dev::solidity::ASTPrinter printer(ast, sourceCode); - printer.print(std::cout); + printer.print(cout); FirstExpressionExtractor extractor(*ast); CompilerContext context; ExpressionCompiler compiler(context); - compiler.compile(*extractor.getExpression()); + try + { + compiler.compile(*extractor.getExpression()); + } + catch (CompilerError const& exception) + { + SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Compiler error", *scanner); + return -1; + } + bytes instructions = compiler.getAssembledBytecode(); - // debug - std::cout << "Bytecode for the first expression: " << std::endl; - std::cout << eth::disassemble(instructions) << std::endl; + + cout << "Bytecode for the first expression: " << endl; + cout << eth::disassemble(instructions) << endl; return 0; } diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 02a2287d9..591330dd6 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -200,22 +200,74 @@ BOOST_AUTO_TEST_CASE(arithmetics) BOOST_AUTO_TEST_CASE(unary_operators) { char const* sourceCode = "contract test {\n" - " function f() { var x = !(~+-(--(++1++)--) == 2); }" + " 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::PUSH1), 0x0, + 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::BNOT), + 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::EQ), - byte(eth::Instruction::NOT)}); + byte(eth::Instruction::MUL)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } From 9ae4efd311795e66cc13c055bd1c1ec4c73ca693 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 01:16:11 +0100 Subject: [PATCH 03/20] Bugfix: Allow empty return statements without type checking. --- libsolidity/AST.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 7b5b6a73c..099222fa1 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -294,6 +294,8 @@ void Break::checkTypeRequirements() void Return::checkTypeRequirements() { assert(m_returnParameters); + if (!m_expression) + return; if (m_returnParameters->getParameters().size() != 1) BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement " "than in returns declaration.")); From ea6d58a0d14bea806671ba78b2369c81e25d59c4 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 01:20:32 +0100 Subject: [PATCH 04/20] 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. --- libevmface/Instruction.h | 9 + liblll/Assembly.cpp | 2 +- liblll/Assembly.h | 4 + libsolidity/AST.cpp | 43 ++- libsolidity/AST.h | 65 +++- libsolidity/ASTForward.h | 1 + libsolidity/ASTPrinter.cpp | 12 + libsolidity/ASTPrinter.h | 2 + libsolidity/ASTVisitor.h | 2 + libsolidity/CMakeLists.txt | 4 +- libsolidity/Compiler.cpp | 509 +++++++--------------------- libsolidity/Compiler.h | 141 ++------ libsolidity/CompilerUtilities.cpp | 60 ++++ libsolidity/CompilerUtilities.h | 83 +++++ libsolidity/ExpressionCompiler.cpp | 408 ++++++++++++++++++++++ libsolidity/ExpressionCompiler.h | 79 +++++ libsolidity/NameAndTypeResolver.cpp | 32 +- libsolidity/NameAndTypeResolver.h | 17 +- libsolidity/Parser.cpp | 21 +- libsolidity/Parser.h | 1 + libsolidity/Types.cpp | 18 +- libsolidity/Types.h | 6 +- solc/main.cpp | 41 +-- test/solidityCompiler.cpp | 323 +++++++----------- test/solidityExpressionCompiler.cpp | 348 +++++++++++++++++++ test/solidityParser.cpp | 2 +- 26 files changed, 1434 insertions(+), 799 deletions(-) create mode 100644 libsolidity/CompilerUtilities.cpp create mode 100644 libsolidity/CompilerUtilities.h create mode 100644 libsolidity/ExpressionCompiler.cpp create mode 100644 libsolidity/ExpressionCompiler.h create mode 100644 test/solidityExpressionCompiler.cpp diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index ea355fab1..9115367d7 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -175,6 +175,15 @@ enum class Instruction: uint8_t SUICIDE = 0xff ///< halt execution and register account for later deletion }; +/// Returs the PUSH<_number> instruction +inline Instruction pushInstruction(unsigned _number) { assert(1 <= _number && _number <= 32); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); } + +/// Returs the DUP<_number> instruction +inline Instruction dupInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::DUP1) + _number - 1); } + +/// Returs the SWAP<_number> instruction +inline Instruction swapInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); } + /// Information structure for a particular instruction. struct InstructionInfo { diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 5b10138d1..7ad84682f 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -54,6 +54,7 @@ unsigned Assembly::bytesRequired() const switch (i.m_type) { case Operation: + case Tag: // 1 byte for the JUMPDEST ret++; break; case PushString: @@ -69,7 +70,6 @@ unsigned Assembly::bytesRequired() const case PushData: case PushSub: ret += 1 + br; - case Tag:; default:; } if (dev::bytesRequired(ret) <= br) diff --git a/liblll/Assembly.h b/liblll/Assembly.h index 8ab3062dc..38baee0c5 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -105,7 +105,11 @@ public: void injectStart(AssemblyItem const& _i); std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); } + int deposit() const { return m_deposit; } + void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assert(m_deposit >= 0); } + void setDeposit(int _deposit) { m_deposit = _deposit; assert(m_deposit >= 0); } + bytes assemble() const; Assembly& optimise(bool _enable); std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "") const; diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 099222fa1..5391e71b7 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -167,6 +167,14 @@ void Return::accept(ASTVisitor& _visitor) _visitor.endVisit(*this); } +void ExpressionStatement::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + if (m_expression) + m_expression->accept(_visitor); + _visitor.endVisit(*this); +} + void VariableDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -255,14 +263,6 @@ TypeError ASTNode::createTypeError(string const& _description) return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } -void Statement::expectType(Expression& _expression, const Type& _expectedType) -{ - _expression.checkTypeRequirements(); - if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType)) - BOOST_THROW_EXCEPTION(_expression.createTypeError("Type not implicitly convertible to expected type.")); - //@todo provide more information to the exception -} - void Block::checkTypeRequirements() { for (shared_ptr const& statement: m_statements) @@ -271,7 +271,7 @@ void Block::checkTypeRequirements() void IfStatement::checkTypeRequirements() { - expectType(*m_condition, BoolType()); + m_condition->expectType(BoolType()); m_trueBody->checkTypeRequirements(); if (m_falseBody) m_falseBody->checkTypeRequirements(); @@ -279,7 +279,7 @@ void IfStatement::checkTypeRequirements() void WhileStatement::checkTypeRequirements() { - expectType(*m_condition, BoolType()); + m_condition->expectType(BoolType()); m_body->checkTypeRequirements(); } @@ -301,7 +301,7 @@ void Return::checkTypeRequirements() "than in returns declaration.")); // this could later be changed such that the paramaters type is an anonymous struct type, // but for now, we only allow one return parameter - expectType(*m_expression, *m_returnParameters->getParameters().front()->getType()); + m_expression->expectType(*m_returnParameters->getParameters().front()->getType()); } void VariableDefinition::checkTypeRequirements() @@ -313,7 +313,7 @@ void VariableDefinition::checkTypeRequirements() if (m_value) { if (m_variable->getType()) - expectType(*m_value, *m_variable->getType()); + m_value->expectType(*m_variable->getType()); else { // no type declared and no previous assignment, infer the type @@ -330,7 +330,7 @@ void Assignment::checkTypeRequirements() m_leftHandSide->checkTypeRequirements(); if (!m_leftHandSide->isLvalue()) BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); - expectType(*m_rightHandSide, *m_leftHandSide->getType()); + m_rightHandSide->expectType(*m_leftHandSide->getType()); m_type = m_leftHandSide->getType(); if (m_assigmentOperator != Token::ASSIGN) { @@ -340,6 +340,19 @@ void Assignment::checkTypeRequirements() } } +void ExpressionStatement::checkTypeRequirements() +{ + m_expression->checkTypeRequirements(); +} + +void Expression::expectType(const Type& _expectedType) +{ + checkTypeRequirements(); + if (!getType()->isImplicitlyConvertibleTo(_expectedType)) + BOOST_THROW_EXCEPTION(createTypeError("Type not implicitly convertible to expected type.")); + //@todo provide more information to the exception +} + void UnaryOperation::checkTypeRequirements() { // INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE @@ -411,10 +424,10 @@ void FunctionCall::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call.")); // @todo actually the return type should be an anonymous struct, // but we change it to the type of the first return value until we have structs - if (fun.getReturnParameterList()->getParameters().empty()) + if (fun.getReturnParameters().empty()) m_type = make_shared(); else - m_type = fun.getReturnParameterList()->getParameters().front()->getType(); + m_type = fun.getReturnParameters().front()->getType(); } } diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 856c222eb..7af8d521b 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -144,7 +144,7 @@ public: ASTNode(_location), m_parameters(_parameters) {} virtual void accept(ASTVisitor& _visitor) override; - std::vector> const& getParameters() { return m_parameters; } + std::vector> const& getParameters() const { return m_parameters; } private: std::vector> m_parameters; @@ -167,15 +167,20 @@ public: bool isDeclaredConst() const { return m_isDeclaredConst; } std::vector> const& getParameters() const { return m_parameters->getParameters(); } ParameterList& getParameterList() { return *m_parameters; } + std::vector> const& getReturnParameters() const { return m_returnParameters->getParameters(); } ASTPointer const& getReturnParameterList() const { return m_returnParameters; } Block& getBody() { return *m_body; } + void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } + std::vectorconst& getLocalVariables() const { return m_localVariables; } private: bool m_isPublic; ASTPointer m_parameters; bool m_isDeclaredConst; ASTPointer m_returnParameters; ASTPointer m_body; + + std::vector m_localVariables; }; /// Declaration of a variable. This can be used in various places, e.g. in function parameter @@ -285,11 +290,6 @@ public: //! This includes checking that operators are applicable to their arguments but also that //! the number of function call arguments matches the number of formal parameters and so forth. virtual void checkTypeRequirements() = 0; - -protected: - //! Helper function, check that the inferred type for @a _expression is @a _expectedType or at - //! least implicitly convertible to @a _expectedType. If not, throw exception. - void expectType(Expression& _expression, Type const& _expectedType); }; /// Brace-enclosed block containing zero or more statements. @@ -318,6 +318,9 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getCondition() const { return *m_condition; } + Statement& getTrueStatement() const { return *m_trueBody; } + Statement* getFalseStatement() const { return m_falseBody.get(); } private: ASTPointer m_condition; ASTPointer m_trueBody; @@ -342,6 +345,8 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getCondition() const { return *m_condition; } + Statement& getBody() const { return *m_body; } private: ASTPointer m_condition; ASTPointer m_body; @@ -372,6 +377,8 @@ public: virtual void checkTypeRequirements() override; void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } + ParameterList const& getFunctionReturnParameters() const { assert(m_returnParameters); return *m_returnParameters; } + Expression* getExpression() const { return m_expression.get(); } private: ASTPointer m_expression; //< value to return, optional @@ -392,21 +399,54 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + VariableDeclaration const& getDeclaration() const { return *m_variable; } + Expression* getExpression() const { return m_value.get(); } + private: ASTPointer m_variable; ASTPointer m_value; ///< the assigned value, can be missing }; -/// An expression, i.e. something that has a value (which can also be of type "void" in case -/// of function calls). -class Expression: public Statement +/** + * A statement that contains only an expression (i.e. an assignment, function call, ...). + */ +class ExpressionStatement: public Statement +{ +public: + ExpressionStatement(Location const& _location, ASTPointer _expression): + Statement(_location), m_expression(_expression) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Expression& getExpression() const { return *m_expression; } + +private: + ASTPointer m_expression; +}; + +/// @} + +/// Expressions +/// @{ + +/** + * An expression, i.e. something that has a value (which can also be of type "void" in case + * of some function calls). + * @abstract + */ +class Expression: public ASTNode { public: - Expression(Location const& _location): Statement(_location), m_isLvalue(false) {} + Expression(Location const& _location): ASTNode(_location), m_isLvalue(false) {} + virtual void checkTypeRequirements() = 0; std::shared_ptr const& getType() const { return m_type; } bool isLvalue() const { return m_isLvalue; } + /// Helper function, infer the type via @ref checkTypeRequirements and then check that it + /// is implicitly convertible to @a _expectedType. If not, throw exception. + void expectType(Type const& _expectedType); + protected: //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). std::shared_ptr m_type; @@ -415,11 +455,6 @@ protected: bool m_isLvalue; }; -/// @} - -/// Expressions -/// @{ - /// Assignment, can also be a compound assignment. /// Examples: (a = 7 + 8) or (a *= 2) class Assignment: public Expression diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index c9a780f5f..2b0bd8869 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -53,6 +53,7 @@ class Continue; class Break; class Return; class VariableDefinition; +class ExpressionStatement; class Expression; class Assignment; class UnaryOperation; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 9b545ac9e..eb9d92f08 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -171,6 +171,13 @@ bool ASTPrinter::visit(VariableDefinition& _node) return goDeeper(); } +bool ASTPrinter::visit(ExpressionStatement& _node) +{ + writeLine("ExpressionStatement"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Expression& _node) { writeLine("Expression"); @@ -358,6 +365,11 @@ void ASTPrinter::endVisit(VariableDefinition&) m_indentation--; } +void ASTPrinter::endVisit(ExpressionStatement&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Expression&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index 74e0837ff..722c80c9e 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -58,6 +58,7 @@ public: bool visit(Break& _node) override; bool visit(Return& _node) override; bool visit(VariableDefinition& _node) override; + bool visit(ExpressionStatement& _node) override; bool visit(Expression& _node) override; bool visit(Assignment& _node) override; bool visit(UnaryOperation& _node) override; @@ -89,6 +90,7 @@ public: void endVisit(Break&) override; void endVisit(Return&) override; void endVisit(VariableDefinition&) override; + void endVisit(ExpressionStatement&) override; void endVisit(Expression&) override; void endVisit(Assignment&) override; void endVisit(UnaryOperation&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index a667ad392..2a765e47b 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -58,6 +58,7 @@ public: virtual bool visit(Break&) { return true; } virtual bool visit(Return&) { return true; } virtual bool visit(VariableDefinition&) { return true; } + virtual bool visit(ExpressionStatement&) { return true; } virtual bool visit(Expression&) { return true; } virtual bool visit(Assignment&) { return true; } virtual bool visit(UnaryOperation&) { return true; } @@ -89,6 +90,7 @@ public: virtual void endVisit(Break&) { } virtual void endVisit(Return&) { } virtual void endVisit(VariableDefinition&) { } + virtual void endVisit(ExpressionStatement&) { } virtual void endVisit(Expression&) { } virtual void endVisit(Assignment&) { } virtual void endVisit(UnaryOperation&) { } diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 757d0cc06..f425bba48 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -16,8 +16,8 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} devcore) -target_link_libraries(${EXECUTABLE} evmface) +# @todo we only depend on Assembly, not on all of lll +target_link_libraries(${EXECUTABLE} evmface devcore lll) install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index dbb38324d..fea885607 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -17,455 +17,192 @@ /** * @author Christian * @date 2014 - * Solidity AST to EVM bytecode compiler. + * Solidity compiler. */ -#include -#include +#include #include #include +#include +using namespace std; namespace dev { namespace solidity { - -void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position) -{ - assert(m_labelPositions.find(_label) == m_labelPositions.end()); - m_labelPositions[_label] = _position; -} - -uint32_t CompilerContext::getLabelPosition(uint32_t _label) const -{ - auto iter = m_labelPositions.find(_label); - assert(iter != m_labelPositions.end()); - return iter->second; -} - -void ExpressionCompiler::compile(Expression& _expression) +bytes Compiler::compile(ContractDefinition& _contract) { - m_assemblyItems.clear(); - _expression.accept(*this); + Compiler compiler; + compiler.compileContract(_contract); + return compiler.m_context.getAssembledBytecode(); } -bytes ExpressionCompiler::getAssembledBytecode() const +void Compiler::compileContract(ContractDefinition& _contract) { - bytes assembled; - assembled.reserve(m_assemblyItems.size()); + m_context = CompilerContext(); // clear it just in case - // resolve label references - for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos) - { - AssemblyItem const& item = m_assemblyItems[pos]; - if (item.getType() == AssemblyItem::Type::LABEL) - m_context.setLabelPosition(item.getLabel(), pos + 1); - } + //@todo constructor + //@todo register state variables - for (AssemblyItem const& item: m_assemblyItems) - { - if (item.getType() == AssemblyItem::Type::LABELREF) - assembled.push_back(m_context.getLabelPosition(item.getLabel())); - else - assembled.push_back(item.getData()); - } - - return assembled; + for (ASTPointer const& function: _contract.getDefinedFunctions()) + m_context.addFunction(*function); + appendFunctionSelector(_contract.getDefinedFunctions()); + for (ASTPointer const& function: _contract.getDefinedFunctions()) + function->accept(*this); } -AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context, - Expression& _expression) +void Compiler::appendFunctionSelector(std::vector> const&) { - ExpressionCompiler compiler(_context); - compiler.compile(_expression); - return compiler.getAssemblyItems(); + // filter public functions, and sort by name. Then select function from first byte, + // unpack arguments from calldata, push to stack and jump. Pack return values to + // output and return. } -bool ExpressionCompiler::visit(Assignment& _assignment) +bool Compiler::visit(FunctionDefinition& _function) { - m_currentLValue = nullptr; - _assignment.getLeftHandSide().accept(*this); + //@todo to simplify this, the colling convention could by changed such that + // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] + // although note that this reduces the size of the visible stack - Expression& rightHandSide = _assignment.getRightHandSide(); - Token::Value op = _assignment.getAssignmentOperator(); - if (op != Token::ASSIGN) - { - // compound assignment - rightHandSide.accept(*this); - Type const& resultType = *_assignment.getType(); - cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); - appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); - } - else - { - append(eth::Instruction::POP); //@todo do not retrieve the value in the first place - rightHandSide.accept(*this); - } + m_context.startNewFunction(); + m_returnTag = m_context.newTag(); + m_breakTags.clear(); + m_continueTags.clear(); - storeInLValue(_assignment); - return false; -} - -void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) -{ - //@todo type checking and creating code for an operator should be in the same place: - // the operator should know how to convert itself and to which types it applies, so - // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that - // represents the operator - switch (_unaryOperation.getOperator()) - { - case Token::NOT: // ! - append(eth::Instruction::NOT); - break; - case Token::BIT_NOT: // ~ - append(eth::Instruction::BNOT); - break; - case Token::DELETE: // delete - { - // a -> a xor a (= 0). - // @todo semantics change for complex types - append(eth::Instruction::DUP1); - append(eth::Instruction::XOR); - storeInLValue(_unaryOperation); - break; - } - case Token::INC: // ++ (pre- or postfix) - case Token::DEC: // -- (pre- or postfix) - if (!_unaryOperation.isPrefixOperation()) - append(eth::Instruction::DUP1); - append(eth::Instruction::PUSH1); - append(1); - if (_unaryOperation.getOperator() == Token::INC) - append(eth::Instruction::ADD); - else - { - append(eth::Instruction::SWAP1); //@todo avoid this - append(eth::Instruction::SUB); - } - if (_unaryOperation.isPrefixOperation()) - storeInLValue(_unaryOperation); - else - moveToLValue(_unaryOperation); - break; - case Token::ADD: // + - // unary add, so basically no-op - break; - case Token::SUB: // - - append(eth::Instruction::PUSH1); - append(0); - append(eth::Instruction::SUB); - break; - default: - assert(false); // invalid operation - } -} + m_context << m_context.getFunctionEntryLabel(_function); -bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) -{ - Expression& leftExpression = _binaryOperation.getLeftExpression(); - Expression& rightExpression = _binaryOperation.getRightExpression(); - Type const& resultType = *_binaryOperation.getType(); - Token::Value const op = _binaryOperation.getOperator(); + // stack upon entry: [return address] [arg0] [arg1] ... [argn] + // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] - if (op == Token::AND || op == Token::OR) - { - // special case: short-circuiting - appendAndOrOperatorCode(_binaryOperation); - } - else if (Token::isCompareOp(op)) - { - leftExpression.accept(*this); - rightExpression.accept(*this); + unsigned const numArguments = _function.getParameters().size(); + unsigned const numReturnValues = _function.getReturnParameters().size(); + unsigned const numLocalVariables = _function.getLocalVariables().size(); - // the types to compare have to be the same, but the resulting type is always bool - assert(*leftExpression.getType() == *rightExpression.getType()); - appendCompareOperatorCode(op, *leftExpression.getType()); - } - else - { - leftExpression.accept(*this); - cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType); - rightExpression.accept(*this); - cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType); - appendOrdinaryBinaryOperatorCode(op, resultType); - } + for (ASTPointer const& variable: _function.getParameters() + _function.getReturnParameters()) + m_context.addVariable(*variable); + for (VariableDeclaration const* localVariable: _function.getLocalVariables()) + m_context.addVariable(*localVariable); + m_context.initializeLocalVariables(numReturnValues + numLocalVariables); - // do not visit the child nodes, we already did that explicitly - return false; -} + _function.getBody().accept(*this); -void ExpressionCompiler::endVisit(FunctionCall& _functionCall) -{ - if (_functionCall.isTypeConversion()) - { - //@todo we only have integers and bools for now which cannot be explicitly converted - assert(_functionCall.getArguments().size() == 1); - cleanHigherOrderBitsIfNeeded(*_functionCall.getArguments().front()->getType(), - *_functionCall.getType()); - } - else - { - //@todo: arguments are already on the stack - // push return label (below arguments?) - // jump to function label - // discard all but the first function return argument - } -} + m_context << m_returnTag; -void ExpressionCompiler::endVisit(MemberAccess&) -{ + // Now we need to re-shuffle the stack. For this we keep a record of the stack layout + // that shows the target positions of the elements, where "-1" denotes that this element needs + // to be removed from the stack. + // Note that the fact that the return arguments are of increasing index is vital for this + // algorithm to work. -} + vector stackLayout; + stackLayout.push_back(numReturnValues); // target of return address + stackLayout += vector(numArguments, -1); // discard all arguments + for (unsigned i = 0; i < numReturnValues; ++i) + stackLayout.push_back(i); + stackLayout += vector(numLocalVariables, -1); -void ExpressionCompiler::endVisit(IndexAccess&) -{ + while (stackLayout.back() != int(stackLayout.size() - 1)) + if (stackLayout.back() < 0) + { + m_context << eth::Instruction::POP; + stackLayout.pop_back(); + } + else + { + m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1); + swap(stackLayout[stackLayout.back()], stackLayout.back()); + } -} + m_context << eth::Instruction::JUMP; -void ExpressionCompiler::endVisit(Identifier& _identifier) -{ - m_currentLValue = _identifier.getReferencedDeclaration(); - unsigned stackPos = stackPositionOfLValue(); - if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation()) - << errinfo_comment("Stack too deep.")); - appendDup(stackPos + 1); + return false; } -void ExpressionCompiler::endVisit(Literal& _literal) +bool Compiler::visit(IfStatement& _ifStatement) { - switch (_literal.getType()->getCategory()) - { - case Type::Category::INTEGER: - case Type::Category::BOOL: - { - bytes value = _literal.getType()->literalToBigEndian(_literal); - assert(value.size() <= 32); - assert(!value.empty()); - appendPush(value.size()); - append(value); - break; - } - default: - assert(false); // @todo - } + ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition()); + eth::AssemblyItem trueTag = m_context.appendConditionalJump(); + if (_ifStatement.getFalseStatement()) + _ifStatement.getFalseStatement()->accept(*this); + eth::AssemblyItem endTag = m_context.appendJump(); + m_context << trueTag; + _ifStatement.getTrueStatement().accept(*this); + m_context << endTag; + return false; } -void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _targetType) +bool Compiler::visit(WhileStatement& _whileStatement) { - // If the type of one of the operands is extended, we need to remove all - // higher-order bits that we might have ignored in previous operations. - // @todo: store in the AST whether the operand might have "dirty" higher - // order bits - - if (_typeOnStack == _targetType) - return; - if (_typeOnStack.getCategory() == Type::Category::INTEGER && - _targetType.getCategory() == Type::Category::INTEGER) - { - //@todo - } - else - { - // If we get here, there is either an implementation missing to clean higher oder bits - // for non-integer types that are explicitly convertible or we got here in error. - assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); - assert(false); // these types should not be convertible. - } -} + eth::AssemblyItem loopStart = m_context.newTag(); + eth::AssemblyItem loopEnd = m_context.newTag(); + m_continueTags.push_back(loopStart); + m_breakTags.push_back(loopEnd); -void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) -{ - Token::Value const op = _binaryOperation.getOperator(); - assert(op == Token::OR || op == Token::AND); - - _binaryOperation.getLeftExpression().accept(*this); - append(eth::Instruction::DUP1); - if (op == Token::AND) - append(eth::Instruction::NOT); - uint32_t endLabel = appendConditionalJump(); - _binaryOperation.getRightExpression().accept(*this); - appendLabel(endLabel); -} + m_context << loopStart; + ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition()); + m_context << eth::Instruction::NOT; + m_context.appendConditionalJumpTo(loopEnd); -void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) -{ - if (_operator == Token::EQ || _operator == Token::NE) - { - append(eth::Instruction::EQ); - if (_operator == Token::NE) - append(eth::Instruction::NOT); - } - else - { - IntegerType const* type = dynamic_cast(&_type); - assert(type); - bool const isSigned = type->isSigned(); + _whileStatement.getBody().accept(*this); - // note that EVM opcodes compare like "stack[0] < stack[1]", - // but our left value is at stack[1], so everyhing is reversed. - switch (_operator) - { - case Token::GTE: - append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT); - append(eth::Instruction::NOT); - break; - case Token::LTE: - append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT); - append(eth::Instruction::NOT); - break; - case Token::GT: - append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT); - break; - case Token::LT: - append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT); - break; - default: - assert(false); - } - } -} + m_context.appendJumpTo(loopStart); + m_context << loopEnd; -void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) -{ - if (Token::isArithmeticOp(_operator)) - appendArithmeticOperatorCode(_operator, _type); - else if (Token::isBitOp(_operator)) - appendBitOperatorCode(_operator); - else if (Token::isShiftOp(_operator)) - appendShiftOperatorCode(_operator); - else - assert(false); // unknown binary operator + m_continueTags.pop_back(); + m_breakTags.pop_back(); + return false; } -void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) +bool Compiler::visit(Continue&) { - IntegerType const* type = dynamic_cast(&_type); - assert(type); - bool const isSigned = type->isSigned(); - - switch (_operator) - { - case Token::ADD: - append(eth::Instruction::ADD); - break; - case Token::SUB: - append(eth::Instruction::SWAP1); - append(eth::Instruction::SUB); - break; - case Token::MUL: - append(eth::Instruction::MUL); - break; - case Token::DIV: - append(isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); - break; - case Token::MOD: - append(isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); - break; - default: - assert(false); - } + assert(!m_continueTags.empty()); + m_context.appendJumpTo(m_continueTags.back()); + return false; } -void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) +bool Compiler::visit(Break&) { - switch (_operator) - { - case Token::BIT_OR: - append(eth::Instruction::OR); - break; - case Token::BIT_AND: - append(eth::Instruction::AND); - break; - case Token::BIT_XOR: - append(eth::Instruction::XOR); - break; - default: - assert(false); - } + assert(!m_breakTags.empty()); + m_context.appendJumpTo(m_breakTags.back()); + return false; } -void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +bool Compiler::visit(Return& _return) { - switch (_operator) + //@todo modifications are needed to make this work with functions returning multiple values + if (Expression* expression = _return.getExpression()) { - case Token::SHL: - assert(false); //@todo - break; - case Token::SAR: - assert(false); //@todo - break; - default: - assert(false); + ExpressionCompiler::compileExpression(m_context, *expression); + VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front(); + ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType()); + int stackPosition = m_context.getStackPositionOfVariable(firstVariable); + m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP; } + m_context.appendJumpTo(m_returnTag); + return false; } -uint32_t ExpressionCompiler::appendConditionalJump() -{ - uint32_t label = m_context.dispenseNewLabel(); - append(eth::Instruction::PUSH1); - appendLabelref(label); - append(eth::Instruction::JUMPI); - return label; -} - -void ExpressionCompiler::appendPush(unsigned _number) -{ - assert(1 <= _number && _number <= 32); - append(eth::Instruction(unsigned(eth::Instruction::PUSH1) + _number - 1)); -} - -void ExpressionCompiler::appendDup(unsigned _number) -{ - assert(1 <= _number && _number <= 16); - append(eth::Instruction(unsigned(eth::Instruction::DUP1) + _number - 1)); -} - -void ExpressionCompiler::appendSwap(unsigned _number) -{ - assert(1 <= _number && _number <= 16); - append(eth::Instruction(unsigned(eth::Instruction::SWAP1) + _number - 1)); -} - -void ExpressionCompiler::append(bytes const& _data) -{ - m_assemblyItems.reserve(m_assemblyItems.size() + _data.size()); - for (byte b: _data) - append(b); -} - -void ExpressionCompiler::storeInLValue(Expression const& _expression) -{ - assert(m_currentLValue); - moveToLValue(_expression); - unsigned stackPos = stackPositionOfLValue(); - if (stackPos > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Stack too deep.")); - if (stackPos >= 1) - appendDup(stackPos); -} - -void ExpressionCompiler::moveToLValue(Expression const& _expression) +bool Compiler::visit(VariableDefinition& _variableDefinition) { - assert(m_currentLValue); - unsigned stackPos = stackPositionOfLValue(); - if (stackPos > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Stack too deep.")); - else if (stackPos > 0) + if (Expression* expression = _variableDefinition.getExpression()) { - appendSwap(stackPos); - append(eth::Instruction::POP); + ExpressionCompiler::compileExpression(m_context, *expression); + ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), + *_variableDefinition.getDeclaration().getType()); + int stackPosition = m_context.getStackPositionOfVariable(_variableDefinition.getDeclaration()); + m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP; } + return false; } -unsigned ExpressionCompiler::stackPositionOfLValue() const +bool Compiler::visit(ExpressionStatement& _expressionStatement) { - return 8; // @todo ask the context and track stack changes due to m_assemblyItems + Expression& expression = _expressionStatement.getExpression(); + ExpressionCompiler::compileExpression(m_context, expression); + if (expression.getType()->getCategory() != Type::Category::VOID) + m_context << eth::Instruction::POP; + return false; } - - } } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 1c5523e08..2ffaaa8d0 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -20,133 +20,40 @@ * Solidity AST to EVM bytecode compiler. */ -#include #include -#include -#include +#include namespace dev { namespace solidity { -/// A single item of compiled code that can be assembled to a single byte value in the final -/// bytecode. Its main purpose is to inject jump labels and label references into the opcode stream, -/// which can be resolved in the final step. -class AssemblyItem +class Compiler: private ASTVisitor { public: - enum class Type - { - CODE, //< m_data is opcode, m_label is empty. - DATA, //< m_data is actual data, m_label is empty - LABEL, //< m_data is JUMPDEST opcode, m_label is id of label - LABELREF //< m_data is empty, m_label is id of label - }; - - explicit AssemblyItem(eth::Instruction _instruction) : m_type(Type::CODE), m_data(byte(_instruction)) {} - explicit AssemblyItem(byte _data): m_type(Type::DATA), m_data(_data) {} - - /// Factory functions - static AssemblyItem labelRef(uint32_t _label) { return AssemblyItem(Type::LABELREF, 0, _label); } - static AssemblyItem label(uint32_t _label) { return AssemblyItem(Type::LABEL, byte(eth::Instruction::JUMPDEST), _label); } - - Type getType() const { return m_type; } - byte getData() const { return m_data; } - uint32_t getLabel() const { return m_label; } + /// Compile the given contract and return the EVM bytecode. + static bytes compile(ContractDefinition& _contract); private: - AssemblyItem(Type _type, byte _data, uint32_t _label): m_type(_type), m_data(_data), m_label(_label) {} - - Type m_type; - byte m_data; //< data to be written to the bytecode stream (or filled by a label if this is a LABELREF) - uint32_t m_label; //< the id of a label either referenced or defined by this item + Compiler(): m_returnTag(m_context.newTag()) {} + + void compileContract(ContractDefinition& _contract); + void appendFunctionSelector(const std::vector >& _functions); + + virtual bool visit(FunctionDefinition& _function) override; + virtual bool visit(IfStatement& _ifStatement) override; + virtual bool visit(WhileStatement& _whileStatement) override; + virtual bool visit(Continue& _continue) override; + virtual bool visit(Break& _break) override; + virtual bool visit(Return& _return) override; + virtual bool visit(VariableDefinition& _variableDefinition) override; + virtual bool visit(ExpressionStatement& _expressionStatement) override; + + bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } + + CompilerContext m_context; + std::vector m_breakTags; ///< tag to jump to for a "break" statement + std::vector m_continueTags; ///< tag to jump to for a "continue" statement + eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement }; -using AssemblyItems = std::vector; - - -/// Context to be shared by all units that compile the same contract. Its current usage only -/// concerns dispensing unique jump label IDs and storing their actual positions in the bytecode -/// stream. -class CompilerContext -{ -public: - CompilerContext(): m_nextLabel(0) {} - uint32_t dispenseNewLabel() { return m_nextLabel++; } - void setLabelPosition(uint32_t _label, uint32_t _position); - uint32_t getLabelPosition(uint32_t _label) const; - -private: - uint32_t m_nextLabel; - - std::map m_labelPositions; -}; - -/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream -/// of EVM instructions. It needs a compiler context that is the same for the whole compilation -/// unit. -class ExpressionCompiler: public ASTVisitor -{ -public: - ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {} - - /// Compile the given expression and (re-)populate the assembly item list. - void compile(Expression& _expression); - AssemblyItems const& getAssemblyItems() const { return m_assemblyItems; } - bytes getAssembledBytecode() const; - - /// Compile the given expression and return the assembly items right away. - static AssemblyItems compileExpression(CompilerContext& _context, Expression& _expression); - -private: - virtual bool visit(Assignment& _assignment) override; - virtual void endVisit(UnaryOperation& _unaryOperation) override; - virtual bool visit(BinaryOperation& _binaryOperation) override; - virtual void endVisit(FunctionCall& _functionCall) override; - virtual void endVisit(MemberAccess& _memberAccess) override; - virtual void endVisit(IndexAccess& _indexAccess) override; - virtual void endVisit(Identifier& _identifier) override; - virtual void endVisit(Literal& _literal) override; - - /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. - void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType); - - ///@{ - ///@name Append code for various operator types - void appendAndOrOperatorCode(BinaryOperation& _binaryOperation); - void appendCompareOperatorCode(Token::Value _operator, Type const& _type); - void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); - - void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); - void appendBitOperatorCode(Token::Value _operator); - void appendShiftOperatorCode(Token::Value _operator); - /// @} - - /// Appends a JUMPI instruction to a new label and returns the label - uint32_t appendConditionalJump(); - void appendPush(unsigned _number); - void appendDup(unsigned _number); - void appendSwap(unsigned _number); - - /// Append elements to the current instruction list. - void append(eth::Instruction const& _instruction) { m_assemblyItems.push_back(AssemblyItem(_instruction)); } - void append(byte _value) { m_assemblyItems.push_back(AssemblyItem(_value)); } - void append(bytes const& _data); - void appendLabelref(byte _label) { m_assemblyItems.push_back(AssemblyItem::labelRef(_label)); } - void appendLabel(byte _label) { m_assemblyItems.push_back(AssemblyItem::label(_label)); } - - /// Stores the value on top of the stack in the current lvalue and copies that value to the - /// top of the stack again - void storeInLValue(Expression const& _expression); - /// The same as storeInLValue but do not again retrieve the value to the top of the stack. - void moveToLValue(Expression const& _expression); - /// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack. - unsigned stackPositionOfLValue() const; - - Declaration* m_currentLValue; - AssemblyItems m_assemblyItems; - CompilerContext& m_context; -}; - - } } diff --git a/libsolidity/CompilerUtilities.cpp b/libsolidity/CompilerUtilities.cpp new file mode 100644 index 000000000..b8f57618b --- /dev/null +++ b/libsolidity/CompilerUtilities.cpp @@ -0,0 +1,60 @@ +/* + 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 + * Utilities for the solidity compiler. + */ + +#include +#include +#include +#include +#include + +using namespace std; + +namespace dev { +namespace solidity { + +void CompilerContext::initializeLocalVariables(unsigned _numVariables) +{ + if (_numVariables > 0) + { + *this << u256(0); + for (unsigned i = 1; i < _numVariables; ++i) + *this << eth::Instruction::DUP1; + m_asm.adjustDeposit(-_numVariables); + } +} + +int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) +{ + auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration); + assert(res != m_localVariables.end()); + return end(m_localVariables) - res - 1 + m_asm.deposit(); +} + +eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const +{ + auto res = m_functionEntryLabels.find(&_function); + assert(res != m_functionEntryLabels.end()); + return res->second.tag(); +} + +} +} diff --git a/libsolidity/CompilerUtilities.h b/libsolidity/CompilerUtilities.h new file mode 100644 index 000000000..90367903b --- /dev/null +++ b/libsolidity/CompilerUtilities.h @@ -0,0 +1,83 @@ +/* + 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 + * Utilities for the solidity compiler. + */ + +#pragma once + +#include +#include +#include + +namespace dev { +namespace solidity { + + +/** + * Context to be shared by all units that compile the same contract. + * It stores the generated bytecode and the position of identifiers in memory and on the stack. + */ +class CompilerContext +{ +public: + CompilerContext() {} + + void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } + void initializeLocalVariables(unsigned _numVariables); + void addVariable(VariableDeclaration const& _declaration) { m_localVariables.push_back(&_declaration); } + /// Returns the distance of the given local variable from the top of the stack. + int getStackPositionOfVariable(Declaration const& _declaration); + + void addFunction(FunctionDefinition const& _function) { m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); } + eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; + + void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } + + /// Appends a JUMPI instruction to a new tag and @returns the tag + eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + /// Appends a JUMPI instruction to @a _tag + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + /// Appends a JUMP to a new tag and @returns the tag + eth::AssemblyItem appendJump() { return m_asm.appendJump().tag(); } + /// Appends a JUMP to a specific tag + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + /// Appends pushing of a new tag and @returns the new tag. + eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + /// @returns a new tag without pushing any opcodes or data + eth::AssemblyItem newTag() { return m_asm.newTag(); } + + /// Append elements to the current instruction list and adjust @a m_stackOffset. + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } + CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + + bytes getAssembledBytecode() { return m_asm.assemble(); } +private: + eth::Assembly m_asm; + + /// Offsets of local variables on the stack. + std::vector m_localVariables; + /// Labels pointing to the entry points of funcitons. + std::map m_functionEntryLabels; +}; + +} +} diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp new file mode 100644 index 000000000..76fcc2982 --- /dev/null +++ b/libsolidity/ExpressionCompiler.cpp @@ -0,0 +1,408 @@ +/* + 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 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include +#include +#include +#include +#include + +using namespace std; + +namespace dev { +namespace solidity { + +void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression& _expression) +{ + ExpressionCompiler compiler(_context); + _expression.accept(compiler); +} + +bool ExpressionCompiler::visit(Assignment& _assignment) +{ + m_currentLValue = nullptr; + + Expression& rightHandSide = _assignment.getRightHandSide(); + rightHandSide.accept(*this); + Type const& resultType = *_assignment.getType(); + cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); + _assignment.getLeftHandSide().accept(*this); + + Token::Value op = _assignment.getAssignmentOperator(); + if (op != Token::ASSIGN) + { + // compound assignment + m_context << eth::Instruction::SWAP1; + appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); + } + else + m_context << eth::Instruction::POP; //@todo do not retrieve the value in the first place + + storeInLValue(_assignment); + return false; +} + +void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) +{ + //@todo type checking and creating code for an operator should be in the same place: + // the operator should know how to convert itself and to which types it applies, so + // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that + // represents the operator + switch (_unaryOperation.getOperator()) + { + case Token::NOT: // ! + m_context << eth::Instruction::NOT; + break; + case Token::BIT_NOT: // ~ + m_context << eth::Instruction::BNOT; + break; + case Token::DELETE: // delete + { + // a -> a xor a (= 0). + // @todo semantics change for complex types + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + storeInLValue(_unaryOperation); + break; + } + case Token::INC: // ++ (pre- or postfix) + case Token::DEC: // -- (pre- or postfix) + if (!_unaryOperation.isPrefixOperation()) + m_context << eth::Instruction::DUP1; + m_context << u256(1); + if (_unaryOperation.getOperator() == Token::INC) + m_context << eth::Instruction::ADD; + else + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap + if (_unaryOperation.isPrefixOperation()) + storeInLValue(_unaryOperation); + else + moveToLValue(_unaryOperation); + break; + case Token::ADD: // + + // unary add, so basically no-op + break; + case Token::SUB: // - + m_context << u256(0) << eth::Instruction::SUB; + break; + default: + assert(false); // invalid operation + } +} + +bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) +{ + Expression& leftExpression = _binaryOperation.getLeftExpression(); + Expression& rightExpression = _binaryOperation.getRightExpression(); + Type const& resultType = *_binaryOperation.getType(); + Token::Value const op = _binaryOperation.getOperator(); + + if (op == Token::AND || op == Token::OR) + { + // special case: short-circuiting + appendAndOrOperatorCode(_binaryOperation); + } + else if (Token::isCompareOp(op)) + { + leftExpression.accept(*this); + rightExpression.accept(*this); + + // the types to compare have to be the same, but the resulting type is always bool + assert(*leftExpression.getType() == *rightExpression.getType()); + appendCompareOperatorCode(op, *leftExpression.getType()); + } + else + { + leftExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType); + rightExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType); + appendOrdinaryBinaryOperatorCode(op, resultType); + } + + // do not visit the child nodes, we already did that explicitly + return false; +} + +bool ExpressionCompiler::visit(FunctionCall& _functionCall) +{ + if (_functionCall.isTypeConversion()) + { + //@todo we only have integers and bools for now which cannot be explicitly converted + assert(_functionCall.getArguments().size() == 1); + Expression& firstArgument = *_functionCall.getArguments().front(); + firstArgument.accept(*this); + cleanHigherOrderBitsIfNeeded(*firstArgument.getType(), *_functionCall.getType()); + } + else + { + // Calling convention: Caller pushes return address and arguments + // Callee removes them and pushes return values + m_currentLValue = nullptr; + _functionCall.getExpression().accept(*this); + FunctionDefinition const* function = dynamic_cast(m_currentLValue); + assert(function); + + eth::AssemblyItem returnLabel = m_context.pushNewTag(); + std::vector> const& arguments = _functionCall.getArguments(); + assert(arguments.size() == function->getParameters().size()); + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + cleanHigherOrderBitsIfNeeded(*arguments[i]->getType(), + *function->getParameters()[i]->getType()); + } + + m_context.appendJumpTo(m_context.getFunctionEntryLabel(*function)); + m_context << returnLabel; + + // callee adds return parameters, but removes arguments and return label + m_context.adjustStackOffset(function->getReturnParameters().size() - arguments.size() - 1); + + // @todo for now, the return value of a function is its first return value, so remove + // all others + for (unsigned i = 1; i < function->getReturnParameters().size(); ++i) + m_context << eth::Instruction::POP; + } + return false; +} + +void ExpressionCompiler::endVisit(MemberAccess&) +{ + +} + +void ExpressionCompiler::endVisit(IndexAccess&) +{ + +} + +void ExpressionCompiler::endVisit(Identifier& _identifier) +{ + m_currentLValue = _identifier.getReferencedDeclaration(); + switch (_identifier.getType()->getCategory()) + { + case Type::Category::BOOL: + case Type::Category::INTEGER: + case Type::Category::REAL: + { + //@todo we also have to check where to retrieve them from once we add storage variables + unsigned stackPos = stackPositionOfLValue(); + if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Stack too deep.")); + m_context << eth::dupInstruction(stackPos + 1); + break; + } + default: + break; + } +} + +void ExpressionCompiler::endVisit(Literal& _literal) +{ + switch (_literal.getType()->getCategory()) + { + case Type::Category::INTEGER: + case Type::Category::BOOL: + m_context << _literal.getType()->literalValue(_literal); + break; + default: + assert(false); // @todo + } +} + +void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType) +{ + // If the type of one of the operands is extended, we need to remove all + // higher-order bits that we might have ignored in previous operations. + // @todo: store in the AST whether the operand might have "dirty" higher + // order bits + + if (_typeOnStack == _targetType) + return; + if (_typeOnStack.getCategory() == Type::Category::INTEGER && + _targetType.getCategory() == Type::Category::INTEGER) + { + //@todo + } + else + { + // If we get here, there is either an implementation missing to clean higher oder bits + // for non-integer types that are explicitly convertible or we got here in error. + assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); + assert(false); // these types should not be convertible. + } +} + +void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) +{ + Token::Value const op = _binaryOperation.getOperator(); + assert(op == Token::OR || op == Token::AND); + + _binaryOperation.getLeftExpression().accept(*this); + m_context << eth::Instruction::DUP1; + if (op == Token::AND) + m_context << eth::Instruction::NOT; + eth::AssemblyItem endLabel = m_context.appendConditionalJump(); + _binaryOperation.getRightExpression().accept(*this); + m_context << endLabel; +} + +void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) +{ + if (_operator == Token::EQ || _operator == Token::NE) + { + m_context << eth::Instruction::EQ; + if (_operator == Token::NE) + m_context << eth::Instruction::NOT; + } + else + { + IntegerType const* type = dynamic_cast(&_type); + assert(type); + bool const isSigned = type->isSigned(); + + // note that EVM opcodes compare like "stack[0] < stack[1]", + // but our left value is at stack[1], so everyhing is reversed. + switch (_operator) + { + case Token::GTE: + m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) + << eth::Instruction::NOT; + break; + case Token::LTE: + m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) + << eth::Instruction::NOT; + break; + case Token::GT: + m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + break; + case Token::LT: + m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + break; + default: + assert(false); + } + } +} + +void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) +{ + if (Token::isArithmeticOp(_operator)) + appendArithmeticOperatorCode(_operator, _type); + else if (Token::isBitOp(_operator)) + appendBitOperatorCode(_operator); + else if (Token::isShiftOp(_operator)) + appendShiftOperatorCode(_operator); + else + assert(false); // unknown binary operator +} + +void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) +{ + IntegerType const* type = dynamic_cast(&_type); + assert(type); + bool const isSigned = type->isSigned(); + + switch (_operator) + { + case Token::ADD: + m_context << eth::Instruction::ADD; + break; + case Token::SUB: + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + break; + case Token::MUL: + m_context << eth::Instruction::MUL; + break; + case Token::DIV: + m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + break; + case Token::MOD: + m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + default: + assert(false); + } +} + +void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::BIT_OR: + m_context << eth::Instruction::OR; + break; + case Token::BIT_AND: + m_context << eth::Instruction::AND; + break; + case Token::BIT_XOR: + m_context << eth::Instruction::XOR; + break; + default: + assert(false); + } +} + +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::SHL: + assert(false); //@todo + break; + case Token::SAR: + assert(false); //@todo + break; + default: + assert(false); + } +} + +void ExpressionCompiler::storeInLValue(Expression const& _expression) +{ + moveToLValue(_expression); + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + m_context << eth::dupInstruction(stackPos + 1); +} + +void ExpressionCompiler::moveToLValue(Expression const& _expression) +{ + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + else if (stackPos > 0) + m_context << eth::swapInstruction(stackPos) << eth::Instruction::POP; +} + +unsigned ExpressionCompiler::stackPositionOfLValue() const +{ + assert(m_currentLValue); + return m_context.getStackPositionOfVariable(*m_currentLValue); +} + +} +} diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h new file mode 100644 index 000000000..28a04f525 --- /dev/null +++ b/libsolidity/ExpressionCompiler.h @@ -0,0 +1,79 @@ +/* + 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 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include +#include + +namespace dev { +namespace solidity { + +/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream +/// of EVM instructions. It needs a compiler context that is the same for the whole compilation +/// unit. +class ExpressionCompiler: private ASTVisitor +{ +public: + /// Compile the given @a _expression into the @a _context. + static void compileExpression(CompilerContext& _context, Expression& _expression); + + /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. + static void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType); + +private: + ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {} + + virtual bool visit(Assignment& _assignment) override; + virtual void endVisit(UnaryOperation& _unaryOperation) override; + virtual bool visit(BinaryOperation& _binaryOperation) override; + virtual bool visit(FunctionCall& _functionCall) override; + virtual void endVisit(MemberAccess& _memberAccess) override; + virtual void endVisit(IndexAccess& _indexAccess) override; + virtual void endVisit(Identifier& _identifier) override; + virtual void endVisit(Literal& _literal) override; + + ///@{ + ///@name Append code for various operator types + void appendAndOrOperatorCode(BinaryOperation& _binaryOperation); + void appendCompareOperatorCode(Token::Value _operator, Type const& _type); + void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); + + void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); + void appendBitOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator); + /// @} + + /// Stores the value on top of the stack in the current lvalue and copies that value to the + /// top of the stack again + void storeInLValue(Expression const& _expression); + /// The same as storeInLValue but do not again retrieve the value to the top of the stack. + void moveToLValue(Expression const& _expression); + /// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack. + unsigned stackPositionOfLValue() const; + void adjustStackOffset(eth::Instruction _instruction); + + Declaration* m_currentLValue; + CompilerContext& m_context; +}; + + +} +} diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 9626ca848..4b77ed132 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -55,12 +55,15 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) m_currentScope = &m_scopes[function.get()]; function->getBody().checkTypeRequirements(); } + m_currentScope = &m_scopes[nullptr]; } -void NameAndTypeResolver::reset() +Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const { - m_scopes.clear(); - m_currentScope = nullptr; + auto iterator = m_scopes.find(_scope); + if (iterator == end(m_scopes)) + return nullptr; + return iterator->second.resolveName(_name, false); } Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) @@ -68,8 +71,13 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name return m_currentScope->resolveName(_name, _recursive); } +void NameAndTypeResolver::reset() +{ + m_scopes.clear(); + m_currentScope = nullptr; +} -DeclarationRegistrationHelper::DeclarationRegistrationHelper(map& _scopes, +DeclarationRegistrationHelper::DeclarationRegistrationHelper(map& _scopes, ASTNode& _astRoot): m_scopes(_scopes), m_currentScope(&m_scopes[nullptr]) { @@ -101,27 +109,33 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&) bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); + m_currentFunction = &_function; return true; } void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) { + m_currentFunction = nullptr; closeCurrentScope(); } -bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) +void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition) { - registerDeclaration(_declaration, false); - return true; + // Register the local variables with the function + // This does not fit here perfectly, but it saves us another AST visit. + assert(m_currentFunction); + m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration()); } -void DeclarationRegistrationHelper::endVisit(VariableDeclaration&) +bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) { + registerDeclaration(_declaration, false); + return true; } void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node) { - map::iterator iter; + map::iterator iter; bool newlyAdded; tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); assert(newlyAdded); diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h index 7abcbb0c5..cdc334a6e 100644 --- a/libsolidity/NameAndTypeResolver.h +++ b/libsolidity/NameAndTypeResolver.h @@ -41,6 +41,14 @@ public: NameAndTypeResolver() {} void resolveNamesAndTypes(ContractDefinition& _contract); + + /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, + /// the global scope is used (i.e. the one containing only the contract). + /// @returns a pointer to the declaration on success or nullptr on failure. + Declaration* resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const; + + /// Resolves a name in the "current" scope. Should only be called during the initial + /// resolving phase. Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); private: @@ -48,7 +56,7 @@ private: //! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and //! StructDefinition (@todo not yet implemented), where nullptr denotes the global scope. - std::map m_scopes; + std::map m_scopes; Scope* m_currentScope; }; @@ -58,7 +66,7 @@ private: class DeclarationRegistrationHelper: private ASTVisitor { public: - DeclarationRegistrationHelper(std::map& _scopes, ASTNode& _astRoot); + DeclarationRegistrationHelper(std::map& _scopes, ASTNode& _astRoot); private: bool visit(ContractDefinition& _contract); @@ -67,15 +75,16 @@ private: void endVisit(StructDefinition& _struct); bool visit(FunctionDefinition& _function); void endVisit(FunctionDefinition& _function); + void endVisit(VariableDefinition& _variableDefinition); bool visit(VariableDeclaration& _declaration); - void endVisit(VariableDeclaration& _declaration); void enterNewSubScope(ASTNode& _node); void closeCurrentScope(); void registerDeclaration(Declaration& _declaration, bool _opensScope); - std::map& m_scopes; + std::map& m_scopes; Scope* m_currentScope; + FunctionDefinition* m_currentFunction; }; //! Resolves references to declarations (of variables and types) and also establishes the link diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 1ea413ee9..6ef6ebc31 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -266,9 +266,11 @@ ASTPointer Parser::parseStatement() // starting from here, all statements must be terminated by a semicolon case Token::CONTINUE: statement = ASTNodeFactory(*this).createNode(); + m_scanner->next(); break; case Token::BREAK: statement = ASTNodeFactory(*this).createNode(); + m_scanner->next(); break; case Token::RETURN: { @@ -283,9 +285,9 @@ ASTPointer Parser::parseStatement() } break; default: - // distinguish between variable definition (and potentially assignment) and expressions + // distinguish between variable definition (and potentially assignment) and expression statement // (which include assignments to other expressions and pre-declared variables) - // We have a variable definition if we ge a keyword that specifies a type name, or + // We have a variable definition if we get a keyword that specifies a type name, or // in the case of a user-defined type, we have two identifiers following each other. if (m_scanner->getCurrentToken() == Token::MAPPING || m_scanner->getCurrentToken() == Token::VAR || @@ -293,8 +295,8 @@ ASTPointer Parser::parseStatement() (m_scanner->getCurrentToken() == Token::IDENTIFIER && m_scanner->peekNextToken() == Token::IDENTIFIER)) statement = parseVariableDefinition(); - else // "ordinary" expression - statement = parseExpression(); + else // "ordinary" expression statement + statement = parseExpressionStatement(); } expectToken(Token::SEMICOLON); return statement; @@ -349,6 +351,14 @@ ASTPointer Parser::parseVariableDefinition() return nodeFactory.createNode(variable, value); } +ASTPointer Parser::parseExpressionStatement() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); + return nodeFactory.createNode(expression); +} + ASTPointer Parser::parseExpression() { ASTNodeFactory nodeFactory(*this); @@ -453,8 +463,7 @@ ASTPointer Parser::parsePrimaryExpression() { case Token::TRUE_LITERAL: case Token::FALSE_LITERAL: - expression = nodeFactory.createNode(token, ASTPointer()); - m_scanner->next(); + expression = nodeFactory.createNode(token, getLiteralAndAdvance()); break; case Token::NUMBER: case Token::STRING_LITERAL: diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index eabc22746..307a0d6a1 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -58,6 +58,7 @@ private: ASTPointer parseIfStatement(); ASTPointer parseWhileStatement(); ASTPointer parseVariableDefinition(); + ASTPointer parseExpressionStatement(); ASTPointer parseExpression(); ASTPointer parseBinaryExpression(int _minPrecedence = 4); ASTPointer parseUnaryExpression(); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 05b12df09..165787c56 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -159,14 +159,12 @@ std::string IntegerType::toString() const return prefix + dev::toString(m_bits); } -bytes IntegerType::literalToBigEndian(const Literal& _literal) const +u256 IntegerType::literalValue(const Literal& _literal) const { bigint value(_literal.getValue()); - if (!isSigned() && value < 0) - return bytes(); // @todo this should already be caught by "smallestTypeforLiteral" - //@todo check that the number of bits is correct - //@todo does "toCompactBigEndian" work for signed numbers? - return toCompactBigEndian(value); + //@todo check that the number is not too large + //@todo does this work for signed numbers? + return u256(value); } bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -182,14 +180,14 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const return isImplicitlyConvertibleTo(_convertTo); } -bytes BoolType::literalToBigEndian(const Literal& _literal) const +u256 BoolType::literalValue(const Literal& _literal) const { if (_literal.getToken() == Token::TRUE_LITERAL) - return bytes(1, 1); + return u256(1); else if (_literal.getToken() == Token::FALSE_LITERAL) - return bytes(1, 0); + return u256(0); else - return NullBytes; + assert(false); } bool ContractType::operator==(const Type& _other) const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 4d56b5abb..828f48095 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -69,7 +69,7 @@ public: virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } virtual std::string toString() const = 0; - virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; } + virtual u256 literalValue(Literal const&) const { assert(false); } }; /// Any kind of integer type including hash and address. @@ -94,7 +94,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; - virtual bytes literalToBigEndian(Literal const& _literal) const override; + virtual u256 literalValue(Literal const& _literal) const override; int getNumBits() const { return m_bits; } bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } @@ -122,7 +122,7 @@ public: } virtual std::string toString() const override { return "bool"; } - virtual bytes literalToBigEndian(Literal const& _literal) const override; + virtual u256 literalValue(Literal const& _literal) const override; }; /// The type of a contract instance, there is one distinct type for each contract definition. diff --git a/solc/main.cpp b/solc/main.cpp index 24f07d952..945b53db5 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -55,35 +55,6 @@ void version() exit(0); } - -/// 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; -}; - int main(int argc, char** argv) { string infile; @@ -143,13 +114,10 @@ int main(int argc, char** argv) dev::solidity::ASTPrinter printer(ast, sourceCode); printer.print(cout); - FirstExpressionExtractor extractor(*ast); - - CompilerContext context; - ExpressionCompiler compiler(context); + bytes instructions; try { - compiler.compile(*extractor.getExpression()); + instructions = Compiler::compile(*ast); } catch (CompilerError const& exception) { @@ -157,10 +125,9 @@ int main(int argc, char** argv) return -1; } - bytes instructions = compiler.getAssembledBytecode(); - - cout << "Bytecode for the first expression: " << endl; + cout << "EVM assembly: " << endl; cout << eth::disassemble(instructions) << endl; + cout << "Binary: " << toHex(instructions) << endl; return 0; } diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 591330dd6..c9cf464d9 100644 --- a/test/solidityCompiler.cpp +++ b/test/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 expectation({byte(eth::Instruction::PUSH1), 0x1}); + bytes code = compileContract(sourceCode); + + 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 expectation({byte(eth::Instruction::PUSH1), 0x0}); + bytes code = compileContract(sourceCode); + + 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 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); - - 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)}); + bytes code = compileContract(sourceCode); + + 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(assignment) +BOOST_AUTO_TEST_CASE(loops) { char const* sourceCode = "contract test {\n" - " function f(uint a, uint b) { (a += b) * 2; }" + " function f() { while(true){1;break;2;continue;3;return;4;} }" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileContract(sourceCode); + + 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)}); - 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/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp new file mode 100644 index 000000000..2a58d670a --- /dev/null +++ b/test/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/test/solidityParser.cpp b/test/solidityParser.cpp index 025cd74d1..701a6e76c 100644 --- a/test/solidityParser.cpp +++ b/test/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)); From 6f3afc8583b73e020135701d3df8cf5e24c18fb3 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 12:42:04 +0100 Subject: [PATCH 05/20] Renamed file. --- libsolidity/Compiler.h | 2 +- libsolidity/{CompilerUtilities.cpp => CompilerContext.cpp} | 0 libsolidity/{CompilerUtilities.h => CompilerContext.h} | 0 libsolidity/ExpressionCompiler.cpp | 1 + libsolidity/ExpressionCompiler.h | 4 ++-- test/solidityExpressionCompiler.cpp | 1 + 6 files changed, 5 insertions(+), 3 deletions(-) rename libsolidity/{CompilerUtilities.cpp => CompilerContext.cpp} (100%) rename libsolidity/{CompilerUtilities.h => CompilerContext.h} (100%) diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 2ffaaa8d0..5f6d9b4e9 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -21,7 +21,7 @@ */ #include -#include +#include namespace dev { namespace solidity { diff --git a/libsolidity/CompilerUtilities.cpp b/libsolidity/CompilerContext.cpp similarity index 100% rename from libsolidity/CompilerUtilities.cpp rename to libsolidity/CompilerContext.cpp diff --git a/libsolidity/CompilerUtilities.h b/libsolidity/CompilerContext.h similarity index 100% rename from libsolidity/CompilerUtilities.h rename to libsolidity/CompilerContext.h diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 76fcc2982..53d2d95d9 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -25,6 +25,7 @@ #include #include #include +#include using namespace std; diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 28a04f525..a930723cc 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -21,11 +21,12 @@ */ #include -#include namespace dev { namespace solidity { +class CompilerContext; // forward + /// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream /// of EVM instructions. It needs a compiler context that is the same for the whole compilation /// unit. @@ -68,7 +69,6 @@ private: void moveToLValue(Expression const& _expression); /// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack. unsigned stackPositionOfLValue() const; - void adjustStackOffset(eth::Instruction _instruction); Declaration* m_currentLValue; CompilerContext& m_context; diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index 2a58d670a..252d2c15e 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include From 4d654d4e78408a9bb7837604e0efc22d73c7a15b Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 18:15:25 +0100 Subject: [PATCH 06/20] Function selector and variable (un)packing. --- libsolidity/Compiler.cpp | 100 ++++++++++++++++++++++++++++++++-- libsolidity/Compiler.h | 16 ++++-- libsolidity/CompilerContext.h | 4 +- libsolidity/Types.h | 8 +++ solc/main.cpp | 8 ++- test/solidityCompiler.cpp | 58 +++++++++++++------- 6 files changed, 160 insertions(+), 34 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index fea885607..7e40db15f 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -51,16 +51,106 @@ void Compiler::compileContract(ContractDefinition& _contract) function->accept(*this); } -void Compiler::appendFunctionSelector(std::vector> const&) +void Compiler::appendFunctionSelector(vector> const& _functions) { - // filter public functions, and sort by name. Then select function from first byte, - // unpack arguments from calldata, push to stack and jump. Pack return values to - // output and return. + // sort all public functions and store them together with a tag for their argument decoding section + map> publicFunctions; + for (ASTPointer const& f: _functions) + if (f->isPublic()) + publicFunctions.insert(make_pair(f->getName(), make_pair(f.get(), m_context.newTag()))); + + //@todo remove constructor + + if (publicFunctions.size() > 255) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract.")); + + //@todo check for calldatasize? + // retrieve the first byte of the call data + m_context << u256(0) << eth::Instruction::CALLDATALOAD << u256(0) << eth::Instruction::BYTE; + // check that it is not too large + m_context << eth::Instruction::DUP1 << u256(publicFunctions.size() - 1) << eth::Instruction::LT; + eth::AssemblyItem returnTag = m_context.appendConditionalJump(); + + // otherwise, jump inside jump table (each entry of the table has size 4) + m_context << u256(4) << eth::Instruction::MUL; + eth::AssemblyItem jumpTableStart = m_context.pushNewTag(); + m_context << eth::Instruction::ADD << eth::Instruction::JUMP; + + // jump table @todo it could be that the optimizer destroys this + m_context << jumpTableStart; + for (pair> const& f: publicFunctions) + m_context.appendJumpTo(f.second.second) << eth::Instruction::JUMPDEST; + + m_context << returnTag << eth::Instruction::RETURN; + + for (pair> const& f: publicFunctions) + { + m_context << f.second.second; + appendFunctionCallSection(*f.second.first); + } +} + +void Compiler::appendFunctionCallSection(FunctionDefinition const& _function) +{ + eth::AssemblyItem returnTag = m_context.pushNewTag(); + + appendCalldataUnpacker(_function); + + m_context.appendJumpTo(m_context.getFunctionEntryLabel(_function)); + m_context << returnTag; + + appendReturnValuePacker(_function); +} + +void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function) +{ + // We do not check the calldata size, everything is zero-padded. + unsigned dataOffset = 1; + + //@todo this can be done more efficiently, saving some CALLDATALOAD calls + for (ASTPointer const& var: _function.getParameters()) + { + unsigned const numBytes = var->getType()->getCalldataEncodedSize(); + if (numBytes == 0) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(var->getLocation()) + << errinfo_comment("Type not yet supported.")); + if (numBytes == 32) + m_context << u256(dataOffset) << eth::Instruction::CALLDATALOAD; + else + m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset) + << eth::Instruction::CALLDATALOAD << eth::Instruction::DIV; + dataOffset += numBytes; + } +} + +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) + { + unsigned numBytes = parameters[i]->getType()->getCalldataEncodedSize(); + if (numBytes == 0) + BOOST_THROW_EXCEPTION(CompilerError() + << 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; + dataOffset += numBytes; + } + // note that the stack is not cleaned up here + m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; } bool Compiler::visit(FunctionDefinition& _function) { - //@todo to simplify this, the colling convention could by changed such that + //@todo to simplify this, the calling convention could by changed such that // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 5f6d9b4e9..ebd786658 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -20,6 +20,7 @@ * Solidity AST to EVM bytecode compiler. */ +#include #include #include @@ -29,14 +30,20 @@ namespace solidity { class Compiler: private ASTVisitor { public: + Compiler(): m_returnTag(m_context.newTag()) {} + + void compileContract(ContractDefinition& _contract); + bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } + void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } + /// Compile the given contract and return the EVM bytecode. static bytes compile(ContractDefinition& _contract); private: - Compiler(): m_returnTag(m_context.newTag()) {} - - void compileContract(ContractDefinition& _contract); - void appendFunctionSelector(const std::vector >& _functions); + void appendFunctionSelector(std::vector > const& _functions); + void appendFunctionCallSection(FunctionDefinition const& _function); + void appendCalldataUnpacker(FunctionDefinition const& _function); + void appendReturnValuePacker(FunctionDefinition const& _function); virtual bool visit(FunctionDefinition& _function) override; virtual bool visit(IfStatement& _ifStatement) override; @@ -47,7 +54,6 @@ private: virtual bool visit(VariableDefinition& _variableDefinition) override; virtual bool visit(ExpressionStatement& _expressionStatement) override; - bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } CompilerContext m_context; std::vector m_breakTags; ///< tag to jump to for a "break" statement diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 90367903b..cce5838ef 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -22,6 +22,7 @@ #pragma once +#include #include #include #include @@ -69,7 +70,8 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } - bytes getAssembledBytecode() { return m_asm.assemble(); } + void streamAssembly(std::ostream& _stream) const { _stream << m_asm; } + bytes getAssembledBytecode() const { return m_asm.assemble(); } private: eth::Assembly m_asm; diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 828f48095..d7c3b241f 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -68,6 +68,10 @@ public: virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } + /// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding + /// is not a simple big-endian encoding or the type cannot be stored on the stack. + virtual unsigned getCalldataEncodedSize() const { return 0; } + virtual std::string toString() const = 0; virtual u256 literalValue(Literal const&) const { assert(false); } }; @@ -93,6 +97,8 @@ public: virtual bool operator==(Type const& _other) const override; + virtual unsigned getCalldataEncodedSize() const { return m_bits / 8; } + virtual std::string toString() const override; virtual u256 literalValue(Literal const& _literal) const override; @@ -121,6 +127,8 @@ public: return _operator == Token::NOT || _operator == Token::DELETE; } + virtual unsigned getCalldataEncodedSize() const { return 1; } + virtual std::string toString() const override { return "bool"; } virtual u256 literalValue(Literal const& _literal) const override; }; diff --git a/solc/main.cpp b/solc/main.cpp index 945b53db5..1cf466c72 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -115,9 +115,11 @@ int main(int argc, char** argv) printer.print(cout); bytes instructions; + Compiler compiler; try { - instructions = Compiler::compile(*ast); + compiler.compileContract(*ast); + instructions = compiler.getAssembledBytecode(); } catch (CompilerError const& exception) { @@ -125,7 +127,9 @@ int main(int argc, char** argv) return -1; } - cout << "EVM assembly: " << endl; + cout << "EVM assembly:" << endl; + compiler.streamAssembly(cout); + cout << "Opcodes:" << endl; cout << eth::disassemble(instructions) << endl; cout << "Binary: " << toHex(instructions) << endl; diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index c9cf464d9..4272354c3 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -22,14 +22,14 @@ */ #include - +#include +#include #include #include #include #include #include #include -#include using namespace std; using namespace dev::eth; @@ -52,10 +52,22 @@ bytes compileContract(const string& _sourceCode) NameAndTypeResolver resolver; BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); - bytes instructions = Compiler::compile(*contract); + Compiler compiler; + compiler.compileContract(*contract); // debug - //cout << eth::disassemble(instructions) << endl; - return instructions; + //compiler.streamAssembly(cout); + return compiler.getAssembledBytecode(); +} + +/// 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) +{ + BOOST_REQUIRE(_compiledCode.size() >= _offset + _expectation.size()); + auto checkStart = _compiledCode.begin() + _offset; + BOOST_CHECK_EQUAL_COLLECTIONS(checkStart, checkStart + _expectation.size(), + _expectation.begin(), _expectation.end()); } } // end anonymous namespace @@ -69,6 +81,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) "}\n"; bytes code = compileContract(sourceCode); + unsigned boilerplateSize = 39; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, // initialize local variable x byte(Instruction::PUSH1), 0x2, @@ -77,7 +90,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) byte(Instruction::JUMPDEST), byte(Instruction::POP), byte(Instruction::JUMP)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_CASE(different_argument_numbers) @@ -88,12 +101,13 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) "}\n"; bytes code = compileContract(sourceCode); + unsigned boilerplateSize = 76; 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::PUSH1), 0xa + boilerplateSize, // jump to return byte(Instruction::JUMP), byte(Instruction::JUMPDEST), byte(Instruction::SWAP4), // store d and fetch return address @@ -105,11 +119,11 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) 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), 0x20 + boilerplateSize, // ret address byte(Instruction::PUSH1), 0x1, byte(Instruction::PUSH1), 0x2, byte(Instruction::PUSH1), 0x3, - byte(Instruction::PUSH1), 0x1, + byte(Instruction::PUSH1), 0x1 + boilerplateSize, // stack here: ret e h 0x20 1 2 3 0x1 byte(Instruction::JUMP), byte(Instruction::JUMPDEST), @@ -129,7 +143,7 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) // f(1,2,3) e ret byte(Instruction::JUMP) // end of g }); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_CASE(ifStatement) @@ -139,27 +153,28 @@ BOOST_AUTO_TEST_CASE(ifStatement) "}\n"; bytes code = compileContract(sourceCode); + unsigned boilerplateSize = 39; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, byte(Instruction::DUP1), - byte(Instruction::PUSH1), 0x1b, // "true" target + byte(Instruction::PUSH1), 0x1b + boilerplateSize, // "true" target byte(Instruction::JUMPI), // new check "else if" condition byte(Instruction::DUP1), byte(Instruction::NOT), - byte(Instruction::PUSH1), 0x13, + byte(Instruction::PUSH1), 0x13 + boilerplateSize, byte(Instruction::JUMPI), // "else" body byte(Instruction::PUSH1), 0x4f, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x17, // exit path of second part + byte(Instruction::PUSH1), 0x17 + boilerplateSize, // 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::PUSH1), 0x1f + boilerplateSize, byte(Instruction::JUMP), // "if" body byte(Instruction::JUMPDEST), @@ -169,7 +184,7 @@ BOOST_AUTO_TEST_CASE(ifStatement) byte(Instruction::JUMPDEST), byte(Instruction::POP), byte(Instruction::JUMP)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_CASE(loops) @@ -179,33 +194,34 @@ BOOST_AUTO_TEST_CASE(loops) "}\n"; bytes code = compileContract(sourceCode); + unsigned boilerplateSize = 39; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x1, byte(Instruction::NOT), - byte(Instruction::PUSH1), 0x21, + byte(Instruction::PUSH1), 0x21 + boilerplateSize, byte(Instruction::JUMPI), byte(Instruction::PUSH1), 0x1, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x21, + byte(Instruction::PUSH1), 0x21 + boilerplateSize, byte(Instruction::JUMP), // break byte(Instruction::PUSH1), 0x2, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x2 + boilerplateSize, byte(Instruction::JUMP), // continue byte(Instruction::PUSH1), 0x3, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x22, + byte(Instruction::PUSH1), 0x22 + boilerplateSize, byte(Instruction::JUMP), // return byte(Instruction::PUSH1), 0x4, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x2 + boilerplateSize, byte(Instruction::JUMP), byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), byte(Instruction::JUMP)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + checkCodePresentAt(code, expectation, boilerplateSize); } BOOST_AUTO_TEST_SUITE_END() From adcf06236704a103300b454cbd4af2c79396b12e Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 30 Oct 2014 22:52:15 +0100 Subject: [PATCH 07/20] Actual contract creator and add solidity to AlethZero interface. --- alethzero/CMakeLists.txt | 2 +- alethzero/MainWin.cpp | 17 ++++++++++++ libsolidity/Compiler.cpp | 13 ++++++++++ libsolidity/Compiler.h | 2 ++ libsolidity/CompilerContext.h | 4 +++ libsolidity/CompilerStack.cpp | 49 +++++++++++++++++++++++++++++++++++ libsolidity/CompilerStack.h | 43 ++++++++++++++++++++++++++++++ libsolidity/Scanner.cpp | 5 ---- libsolidity/Scanner.h | 3 ++- test/solidityCompiler.cpp | 35 +++++++++++++------------ 10 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 libsolidity/CompilerStack.cpp create mode 100644 libsolidity/CompilerStack.h diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index 4ad06f7a6..7f9cbb812 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -53,7 +53,7 @@ else () endif () qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) -target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) +target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll solidity evmface devcore) if (APPLE) # First have qt5 install plugins and frameworks diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 2853536b9..3c874d8a2 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -35,6 +35,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -1560,6 +1563,20 @@ void Main::on_data_textChanged() { m_data = fromHex(src); } + else if (src.substr(0, 8) == "contract") // improve this heuristic + { + shared_ptr scanner = make_shared(); + try + { + m_data = dev::solidity::CompilerStack::compile(src, scanner); + } + catch (dev::Exception const& exception) + { + ostringstream error; + solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", *scanner); + errors.push_back(error.str()); + } + } else { m_data = dev::eth::compileLLL(src, m_enableOptimizer, &errors); diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 7e40db15f..b1e3c3da3 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -46,9 +46,22 @@ void Compiler::compileContract(ContractDefinition& _contract) for (ASTPointer const& function: _contract.getDefinedFunctions()) m_context.addFunction(*function); + appendFunctionSelector(_contract.getDefinedFunctions()); for (ASTPointer const& function: _contract.getDefinedFunctions()) function->accept(*this); + + packIntoContractCreator(); +} + +void Compiler::packIntoContractCreator() +{ + CompilerContext creatorContext; + eth::AssemblyItem sub = creatorContext.addSubroutine(m_context.getAssembly()); + // stack contains sub size + creatorContext << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY; + creatorContext << u256(0) << eth::Instruction::RETURN; + swap(m_context, creatorContext); } void Compiler::appendFunctionSelector(vector> const& _functions) diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index ebd786658..d10374a9b 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -40,6 +40,8 @@ public: static bytes compile(ContractDefinition& _contract); 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); diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index cce5838ef..46c4c72ab 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -63,6 +63,9 @@ public: eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data eth::AssemblyItem newTag() { return m_asm.newTag(); } + /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) + /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. + eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } /// Append elements to the current instruction list and adjust @a m_stackOffset. CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } @@ -70,6 +73,7 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + eth::Assembly const& getAssembly() const { return m_asm; } void streamAssembly(std::ostream& _stream) const { _stream << m_asm; } bytes getAssembledBytecode() const { return m_asm.assemble(); } private: diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp new file mode 100644 index 000000000..bbd693ae5 --- /dev/null +++ b/libsolidity/CompilerStack.cpp @@ -0,0 +1,49 @@ +/* + 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 + * Full-stack compiler that converts a source code string to bytecode. + */ + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +bytes CompilerStack::compile(std::string const& _sourceCode, shared_ptr _scanner) +{ + if (!_scanner) + _scanner = make_shared(); + _scanner->reset(CharStream(_sourceCode)); + + ASTPointer contract = Parser().parse(_scanner); + NameAndTypeResolver().resolveNamesAndTypes(*contract); + return Compiler::compile(*contract); +} + +} +} diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h new file mode 100644 index 000000000..9f3f81c04 --- /dev/null +++ b/libsolidity/CompilerStack.h @@ -0,0 +1,43 @@ +/* + 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 + * Full-stack compiler that converts a source code string to bytecode. + */ + +#pragma once + +#include +#include +#include + +namespace dev { +namespace solidity { + +class Scanner; // forward + +class CompilerStack +{ +public: + /// Compile the given @a _sourceCode to bytecode. If a scanner is provided, it is used for + /// scanning the source code - this is useful for printing exception information. + static bytes compile(std::string const& _sourceCode, std::shared_ptr _scanner = std::shared_ptr()); +}; + +} +} diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 3148de52e..d8defb50a 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -103,11 +103,6 @@ int HexValue(char c) } } // end anonymous namespace -Scanner::Scanner(CharStream const& _source) -{ - reset(_source); -} - void Scanner::reset(CharStream const& _source) { m_source = _source; diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h index fbaba9ed6..7754d71b4 100644 --- a/libsolidity/Scanner.h +++ b/libsolidity/Scanner.h @@ -110,7 +110,8 @@ public: bool complete_; }; - explicit Scanner(CharStream const& _source); + Scanner() { reset(CharStream()); } + explicit Scanner(CharStream const& _source) { reset(_source); } /// Resets the scanner as if newly constructed with _input as input. void reset(CharStream const& _source); diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 4272354c3..c0d4e32df 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 39; + unsigned boilerplateSize = 51; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, // initialize local variable x byte(Instruction::PUSH1), 0x2, @@ -101,13 +101,14 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 76; + unsigned shift = 76; + 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 + boilerplateSize, // jump to return + byte(Instruction::PUSH1), 0xa + shift, // jump to return byte(Instruction::JUMP), byte(Instruction::JUMPDEST), byte(Instruction::SWAP4), // store d and fetch return address @@ -119,11 +120,11 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) byte(Instruction::JUMPDEST), // beginning of g byte(Instruction::PUSH1), 0x0, byte(Instruction::DUP1), // initialized e and h - byte(Instruction::PUSH1), 0x20 + boilerplateSize, // ret address + byte(Instruction::PUSH1), 0x20 + shift, // ret address byte(Instruction::PUSH1), 0x1, byte(Instruction::PUSH1), 0x2, byte(Instruction::PUSH1), 0x3, - byte(Instruction::PUSH1), 0x1 + boilerplateSize, + byte(Instruction::PUSH1), 0x1 + shift, // stack here: ret e h 0x20 1 2 3 0x1 byte(Instruction::JUMP), byte(Instruction::JUMPDEST), @@ -153,28 +154,29 @@ BOOST_AUTO_TEST_CASE(ifStatement) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 39; + unsigned shift = 39; + unsigned boilerplateSize = 51; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, byte(Instruction::DUP1), - byte(Instruction::PUSH1), 0x1b + boilerplateSize, // "true" target + byte(Instruction::PUSH1), 0x1b + shift, // "true" target byte(Instruction::JUMPI), // new check "else if" condition byte(Instruction::DUP1), byte(Instruction::NOT), - byte(Instruction::PUSH1), 0x13 + boilerplateSize, + byte(Instruction::PUSH1), 0x13 + shift, byte(Instruction::JUMPI), // "else" body byte(Instruction::PUSH1), 0x4f, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x17 + boilerplateSize, // exit path of second part + 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 + boilerplateSize, + byte(Instruction::PUSH1), 0x1f + shift, byte(Instruction::JUMP), // "if" body byte(Instruction::JUMPDEST), @@ -194,28 +196,29 @@ BOOST_AUTO_TEST_CASE(loops) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 39; + unsigned shift = 39; + unsigned boilerplateSize = 51; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x1, byte(Instruction::NOT), - byte(Instruction::PUSH1), 0x21 + boilerplateSize, + byte(Instruction::PUSH1), 0x21 + shift, byte(Instruction::JUMPI), byte(Instruction::PUSH1), 0x1, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x21 + boilerplateSize, + byte(Instruction::PUSH1), 0x21 + shift, byte(Instruction::JUMP), // break byte(Instruction::PUSH1), 0x2, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x2 + boilerplateSize, + byte(Instruction::PUSH1), 0x2 + shift, byte(Instruction::JUMP), // continue byte(Instruction::PUSH1), 0x3, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x22 + boilerplateSize, + byte(Instruction::PUSH1), 0x22 + shift, byte(Instruction::JUMP), // return byte(Instruction::PUSH1), 0x4, byte(Instruction::POP), - byte(Instruction::PUSH1), 0x2 + boilerplateSize, + byte(Instruction::PUSH1), 0x2 + shift, byte(Instruction::JUMP), byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), From 5cc582c454c14a3691dc3f81322069960ce6d75c Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 3 Nov 2014 12:14:06 +0100 Subject: [PATCH 08/20] Bugfix: Swap before mod and div. --- libsolidity/ExpressionCompiler.cpp | 4 ++-- test/solidityExpressionCompiler.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 003d561a7..871b64184 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -336,10 +336,10 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty m_context << eth::Instruction::MUL; break; case Token::DIV: - m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + m_context << eth::Instruction::SWAP1 << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); break; case Token::MOD: - m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + m_context << eth::Instruction::SWAP1 << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); break; default: assert(false); diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index d28628fcd..83a7b2bbf 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -212,7 +212,9 @@ BOOST_AUTO_TEST_CASE(arithmetics) 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()); From 30e4cda0e9bddf58c0f89bcf3b21a90a2557fea6 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 31 Oct 2014 17:47:43 +0100 Subject: [PATCH 09/20] 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), From 331ed8c872883e1aff634d44f26c049ef29ca198 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 3 Nov 2014 16:10:07 +0100 Subject: [PATCH 10/20] Correct Solidity error formatting in AlethZero. --- alethzero/MainWin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 3c874d8a2..55c240379 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1559,6 +1559,7 @@ void Main::on_data_textChanged() string src = ui->data->toPlainText().toStdString(); vector errors; QString lll; + QString solidity; if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0) { m_data = fromHex(src); @@ -1574,7 +1575,7 @@ void Main::on_data_textChanged() { ostringstream error; solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", *scanner); - errors.push_back(error.str()); + solidity = "

Solidity

" + QString::fromStdString(error.str()).toHtmlEscaped() + "
"; } } else @@ -1611,7 +1612,7 @@ void Main::on_data_textChanged() for (auto const& i: errors) errs.append("
" + QString::fromStdString(i).toHtmlEscaped() + "
"); } - ui->code->setHtml(errs + lll + "

Code

" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped()); + ui->code->setHtml(errs + lll + solidity + "

Code

" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped()); ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 0)); if (!ui->gas->isEnabled()) ui->gas->setValue(m_backupGas); From 7e542f55d30e03c02ac51e0902b5cc8d1bf36740 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 3 Nov 2014 16:29:55 +0100 Subject: [PATCH 11/20] Test adjustments. --- test/solidityCompiler.cpp | 7 +++---- test/solidityExpressionCompiler.cpp | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 84aac6661..17de9af53 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. @@ -101,7 +100,7 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 76; + unsigned shift = 75; unsigned boilerplateSize = 88; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, // initialize return variable d @@ -154,7 +153,7 @@ BOOST_AUTO_TEST_CASE(ifStatement) "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 39; + unsigned shift = 38; unsigned boilerplateSize = 51; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, @@ -196,7 +195,7 @@ BOOST_AUTO_TEST_CASE(loops) "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 39; + unsigned shift = 38; unsigned boilerplateSize = 51; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index 043a69496..561cc3bda 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -177,7 +177,7 @@ 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), 0x15, + byte(eth::Instruction::PUSH1), 0x14, byte(eth::Instruction::JUMPI), // short-circuit if it is true byte(eth::Instruction::POP), byte(eth::Instruction::PUSH1), 0x2, @@ -320,13 +320,13 @@ BOOST_AUTO_TEST_CASE(function_call) {{"test", "f", "a"}, {"test", "f", "b"}}); // Stack: a, b - bytes expectation({byte(eth::Instruction::PUSH1), 0x0b, + bytes expectation({byte(eth::Instruction::PUSH1), 0x0a, 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::PUSH1), 0x14, byte(eth::Instruction::JUMP), byte(eth::Instruction::JUMPDEST), // Stack here: a b g(a+1, b) From 0f0a464b4e987a7cf76afc9d0ae407fb1d9fe70b Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 3 Nov 2014 18:39:16 +0100 Subject: [PATCH 12/20] Stylistic changes. --- libsolidity/AST.cpp | 4 ---- libsolidity/AST.h | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 5391e71b7..0ecb639f0 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -333,11 +333,9 @@ void Assignment::checkTypeRequirements() m_rightHandSide->expectType(*m_leftHandSide->getType()); m_type = m_leftHandSide->getType(); if (m_assigmentOperator != Token::ASSIGN) - { // compound assignment if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator))) BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); - } } void ExpressionStatement::checkTypeRequirements() @@ -358,10 +356,8 @@ void UnaryOperation::checkTypeRequirements() // INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE m_subExpression->checkTypeRequirements(); if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE) - { if (!m_subExpression->isLvalue()) BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); - } m_type = m_subExpression->getType(); if (!m_type->acceptsUnaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); diff --git a/libsolidity/AST.h b/libsolidity/AST.h index a283a09b3..bbb73b080 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -180,7 +180,8 @@ public: Block& getBody() { return *m_body; } void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } - std::vectorconst& getLocalVariables() const { return m_localVariables; } + std::vector const& getLocalVariables() const { return m_localVariables; } + private: bool m_isPublic; ASTPointer m_parameters; @@ -345,6 +346,7 @@ public: Expression& getCondition() const { return *m_condition; } Statement& getTrueStatement() const { return *m_trueBody; } Statement* getFalseStatement() const { return m_falseBody.get(); } + private: ASTPointer m_condition; ASTPointer m_trueBody; @@ -373,6 +375,7 @@ public: Expression& getCondition() const { return *m_condition; } Statement& getBody() const { return *m_body; } + private: ASTPointer m_condition; ASTPointer m_body; From 7d25844b9c2d72569e90e44e09a4240324477a64 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 4 Nov 2014 19:35:59 +0100 Subject: [PATCH 13/20] Fixed doxygen comment. --- libevmface/Instruction.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 0477f9ca1..8392c410b 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -176,13 +176,13 @@ enum class Instruction: uint8_t SUICIDE = 0xff ///< halt execution and register account for later deletion }; -/// Returs the PUSH<_number> instruction +/// @returns the PUSH<_number> instruction inline Instruction pushInstruction(unsigned _number) { assert(1 <= _number && _number <= 32); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); } -/// Returs the DUP<_number> instruction +/// @returns the DUP<_number> instruction inline Instruction dupInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::DUP1) + _number - 1); } -/// Returs the SWAP<_number> instruction +/// @returns the SWAP<_number> instruction inline Instruction swapInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); } /// Information structure for a particular instruction. From debab8c26fd7f71aa3c27d16181e91840acf5db4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 4 Nov 2014 19:52:04 +0100 Subject: [PATCH 14/20] Added doxygen comment. --- libsolidity/AST.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index bbb73b080..43aa9bf5e 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -345,6 +345,7 @@ public: Expression& getCondition() const { return *m_condition; } Statement& getTrueStatement() const { return *m_trueBody; } + /// @returns the "else" part of the if statement or nullptr if there is no "else" part. Statement* getFalseStatement() const { return m_falseBody.get(); } private: From 37216a246ae2ef73029c46861fc260cfe807b062 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 5 Nov 2014 14:20:56 +0100 Subject: [PATCH 15/20] Converted all asserts to exceptions. --- libsolidity/AST.cpp | 25 ++++++----- libsolidity/AST.h | 32 ++++++++++++--- libsolidity/Compiler.cpp | 6 ++- libsolidity/CompilerContext.cpp | 7 ++-- libsolidity/Exceptions.h | 1 + libsolidity/ExpressionCompiler.cpp | 58 +++++++++++++------------- libsolidity/NameAndTypeResolver.cpp | 16 +++++--- libsolidity/Scanner.cpp | 33 +++++++-------- libsolidity/Scanner.h | 2 +- libsolidity/Token.h | 64 +++++------------------------ libsolidity/Types.cpp | 14 ++++--- libsolidity/Types.h | 7 +++- solc/main.cpp | 45 ++++++++++++-------- 13 files changed, 157 insertions(+), 153 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 0ecb639f0..f9c49adc5 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -293,9 +293,10 @@ void Break::checkTypeRequirements() void Return::checkTypeRequirements() { - assert(m_returnParameters); if (!m_expression) return; + if (asserts(m_returnParameters)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not assigned.")); if (m_returnParameters->getParameters().size() != 1) BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement " "than in returns declaration.")); @@ -377,7 +378,6 @@ void BinaryOperation::checkTypeRequirements() m_type = make_shared(); else { - assert(Token::isBinaryOp(m_operator)); m_type = m_commonType; if (!m_commonType->acceptsBinaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); @@ -393,25 +393,22 @@ void FunctionCall::checkTypeRequirements() Type const* expressionType = m_expression->getType().get(); if (isTypeConversion()) { - TypeType const* type = dynamic_cast(expressionType); - assert(type); + TypeType const& type = dynamic_cast(*expressionType); //@todo for structs, we have to check the number of arguments to be equal to the // number of non-mapping members if (m_arguments.size() != 1) BOOST_THROW_EXCEPTION(createTypeError("More than one argument for " "explicit type conersion.")); - if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type->getActualType())) + if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType())) BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); - m_type = type->getActualType(); + m_type = type.getActualType(); } else { //@todo would be nice to create a struct type from the arguments // and then ask if that is implicitly convertible to the struct represented by the // function parameters - FunctionType const* function = dynamic_cast(expressionType); - assert(function); - FunctionDefinition const& fun = function->getFunction(); + FunctionDefinition const& fun = dynamic_cast(*expressionType).getFunction(); vector> const& parameters = fun.getParameters(); if (parameters.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); @@ -434,19 +431,21 @@ bool FunctionCall::isTypeConversion() const void MemberAccess::checkTypeRequirements() { - assert(false); // not yet implemented + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access not yet implemented.")); // m_type = ; } void IndexAccess::checkTypeRequirements() { - assert(false); // not yet implemented + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index access not yet implemented.")); // m_type = ; } void Identifier::checkTypeRequirements() { - assert(m_referencedDeclaration); + if (asserts(m_referencedDeclaration)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier not resolved.")); + //@todo these dynamic casts here are not really nice... // is i useful to have an AST visitor here? // or can this already be done in NameAndTypeResolver? @@ -487,7 +486,7 @@ void Identifier::checkTypeRequirements() m_type = make_shared(make_shared(*contractDef)); return; } - assert(false); // declaration reference of unknown/forbidden type + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type.")); } void ElementaryTypeNameExpression::checkTypeRequirements() diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 43aa9bf5e..f42ff47d0 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -243,7 +243,10 @@ class ElementaryTypeName: public TypeName { public: explicit ElementaryTypeName(Location const& _location, Token::Value _type): - TypeName(_location), m_type(_type) {} + TypeName(_location), m_type(_type) + { + if (asserts(Token::isElementaryTypeName(_type))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + } virtual void accept(ASTVisitor& _visitor) override; virtual std::shared_ptr toType() override { return Type::fromElementaryTypeName(m_type); } @@ -407,7 +410,12 @@ public: virtual void checkTypeRequirements() override; void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } - ParameterList const& getFunctionReturnParameters() const { assert(m_returnParameters); return *m_returnParameters; } + ParameterList const& getFunctionReturnParameters() const + { + if (asserts(m_returnParameters)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); + return *m_returnParameters; + } Expression* getExpression() const { return m_expression.get(); } private: @@ -495,7 +503,10 @@ public: Assignment(Location const& _location, ASTPointer const& _leftHandSide, Token::Value _assignmentOperator, ASTPointer const& _rightHandSide): Expression(_location), m_leftHandSide(_leftHandSide), - m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) {} + m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) + { + if (asserts(Token::isAssignmentOp(_assignmentOperator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + } virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; @@ -519,7 +530,10 @@ public: UnaryOperation(Location const& _location, Token::Value _operator, ASTPointer const& _subExpression, bool _isPrefix): Expression(_location), m_operator(_operator), - m_subExpression(_subExpression), m_isPrefix(_isPrefix) {} + m_subExpression(_subExpression), m_isPrefix(_isPrefix) + { + if (asserts(Token::isUnaryOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + } virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; @@ -541,7 +555,10 @@ class BinaryOperation: public Expression public: BinaryOperation(Location const& _location, ASTPointer const& _left, Token::Value _operator, ASTPointer const& _right): - Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) {} + Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) + { + if (asserts(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + } virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; @@ -658,7 +675,10 @@ class ElementaryTypeNameExpression: public PrimaryExpression { public: ElementaryTypeNameExpression(Location const& _location, Token::Value _typeToken): - PrimaryExpression(_location), m_typeToken(_typeToken) {} + PrimaryExpression(_location), m_typeToken(_typeToken) + { + if (asserts(Token::isElementaryTypeName(_typeToken))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + } virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index a44c177d7..654eceadb 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -252,14 +252,16 @@ bool Compiler::visit(WhileStatement& _whileStatement) bool Compiler::visit(Continue&) { - assert(!m_continueTags.empty()); + if (asserts(!m_continueTags.empty())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Jump tag not available for \"continue\".")); m_context.appendJumpTo(m_continueTags.back()); return false; } bool Compiler::visit(Break&) { - assert(!m_breakTags.empty()); + if (asserts(!m_breakTags.empty())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Jump tag not available for \"break\".")); m_context.appendJumpTo(m_breakTags.back()); return false; } diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index b8f57618b..44d0844c2 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -20,7 +20,6 @@ * Utilities for the solidity compiler. */ -#include #include #include #include @@ -45,14 +44,16 @@ void CompilerContext::initializeLocalVariables(unsigned _numVariables) int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) { auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration); - assert(res != m_localVariables.end()); + if (asserts(res != m_localVariables.end())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found on stack.")); return end(m_localVariables) - res - 1 + m_asm.deposit(); } eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const { auto res = m_functionEntryLabels.find(&_function); - assert(res != m_functionEntryLabels.end()); + if (asserts(res != m_functionEntryLabels.end())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function entry label not found.")); return res->second.tag(); } diff --git a/libsolidity/Exceptions.h b/libsolidity/Exceptions.h index 1d981e7c0..1903c1dc2 100644 --- a/libsolidity/Exceptions.h +++ b/libsolidity/Exceptions.h @@ -35,6 +35,7 @@ struct ParserError: virtual Exception {}; struct TypeError: virtual Exception {}; struct DeclarationError: virtual Exception {}; struct CompilerError: virtual Exception {}; +struct InternalCompilerError: virtual Exception {}; typedef boost::error_info errinfo_sourcePosition; typedef boost::error_info errinfo_sourceLocation; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f9cbee377..d23579b11 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -20,7 +20,6 @@ * Solidity AST to EVM bytecode compiler for expressions. */ -#include #include #include #include @@ -105,7 +104,8 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) m_context << u256(0) << eth::Instruction::SUB; break; default: - assert(false); // invalid operation + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + + string(Token::toString(_unaryOperation.getOperator())))); } } @@ -127,7 +127,8 @@ bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) rightExpression.accept(*this); // the types to compare have to be the same, but the resulting type is always bool - assert(*leftExpression.getType() == *rightExpression.getType()); + if (asserts(*leftExpression.getType() == *rightExpression.getType())) + BOOST_THROW_EXCEPTION(InternalCompilerError()); appendCompareOperatorCode(op, *leftExpression.getType()); } else @@ -148,7 +149,8 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) if (_functionCall.isTypeConversion()) { //@todo we only have integers and bools for now which cannot be explicitly converted - assert(_functionCall.getArguments().size() == 1); + if (asserts(_functionCall.getArguments().size() == 1)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); Expression& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); cleanHigherOrderBitsIfNeeded(*firstArgument.getType(), *_functionCall.getType()); @@ -159,28 +161,28 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) // Callee removes them and pushes return values m_currentLValue = nullptr; _functionCall.getExpression().accept(*this); - FunctionDefinition const* function = dynamic_cast(m_currentLValue); - assert(function); + FunctionDefinition const& function = dynamic_cast(*m_currentLValue); eth::AssemblyItem returnLabel = m_context.pushNewTag(); std::vector> const& arguments = _functionCall.getArguments(); - assert(arguments.size() == function->getParameters().size()); + if (asserts(arguments.size() == function.getParameters().size())) + BOOST_THROW_EXCEPTION(InternalCompilerError()); for (unsigned i = 0; i < arguments.size(); ++i) { arguments[i]->accept(*this); cleanHigherOrderBitsIfNeeded(*arguments[i]->getType(), - *function->getParameters()[i]->getType()); + *function.getParameters()[i]->getType()); } - m_context.appendJumpTo(m_context.getFunctionEntryLabel(*function)); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(function)); m_context << returnLabel; // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(function->getReturnParameters().size() - arguments.size() - 1); + m_context.adjustStackOffset(function.getReturnParameters().size() - arguments.size() - 1); // @todo for now, the return value of a function is its first return value, so remove // all others - for (unsigned i = 1; i < function->getReturnParameters().size(); ++i) + for (unsigned i = 1; i < function.getReturnParameters().size(); ++i) m_context << eth::Instruction::POP; } return false; @@ -227,7 +229,7 @@ void ExpressionCompiler::endVisit(Literal& _literal) m_context << _literal.getType()->literalValue(_literal); break; default: - assert(false); // @todo + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer and boolean literals implemented for now.")); } } @@ -249,15 +251,15 @@ void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, { // If we get here, there is either an implementation missing to clean higher oder bits // for non-integer types that are explicitly convertible or we got here in error. - assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); - assert(false); // these types should not be convertible. + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); } } void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) { Token::Value const op = _binaryOperation.getOperator(); - assert(op == Token::OR || op == Token::AND); + if (asserts(op == Token::OR || op == Token::AND)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); _binaryOperation.getLeftExpression().accept(*this); m_context << eth::Instruction::DUP1; @@ -279,9 +281,8 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type } else { - IntegerType const* type = dynamic_cast(&_type); - assert(type); - bool const isSigned = type->isSigned(); + IntegerType const& type = dynamic_cast(_type); + bool const isSigned = type.isSigned(); // note that EVM opcodes compare like "stack[0] < stack[1]", // but our left value is at stack[1], so everyhing is reversed. @@ -302,7 +303,7 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); break; default: - assert(false); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator.")); } } } @@ -316,14 +317,13 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator else if (Token::isShiftOp(_operator)) appendShiftOperatorCode(_operator); else - assert(false); // unknown binary operator + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator.")); } void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) { - IntegerType const* type = dynamic_cast(&_type); - assert(type); - bool const isSigned = type->isSigned(); + IntegerType const& type = dynamic_cast(_type); + bool const isSigned = type.isSigned(); switch (_operator) { @@ -343,7 +343,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty m_context << eth::Instruction::SWAP1 << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); break; default: - assert(false); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator.")); } } @@ -361,22 +361,21 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) m_context << eth::Instruction::XOR; break; default: - assert(false); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator.")); } } void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented.")); switch (_operator) { case Token::SHL: - assert(false); //@todo break; case Token::SAR: - assert(false); //@todo break; default: - assert(false); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator.")); } } @@ -402,7 +401,8 @@ void ExpressionCompiler::moveToLValue(Expression const& _expression) unsigned ExpressionCompiler::stackPositionOfLValue() const { - assert(m_currentLValue); + if (asserts(m_currentLValue)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not available on request.")); return m_context.getStackPositionOfVariable(*m_currentLValue); } diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 4b77ed132..0578e5996 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -20,7 +20,6 @@ * Parser part that determines the declarations corresponding to names and the types of expressions. */ -#include #include #include #include @@ -123,7 +122,8 @@ void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefini { // Register the local variables with the function // This does not fit here perfectly, but it saves us another AST visit. - assert(m_currentFunction); + if (asserts(m_currentFunction)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable definition without function.")); m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration()); } @@ -138,19 +138,22 @@ void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node) map::iterator iter; bool newlyAdded; tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); - assert(newlyAdded); + if (asserts(newlyAdded)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to add new scope.")); m_currentScope = &iter->second; } void DeclarationRegistrationHelper::closeCurrentScope() { - assert(m_currentScope); + if (asserts(m_currentScope)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Closed non-existing scope.")); m_currentScope = m_currentScope->getEnclosingScope(); } void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) { - assert(m_currentScope); + if (asserts(m_currentScope)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration registered without scope.")); if (!m_currentScope->registerDeclaration(_declaration)) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_declaration.getLocation()) << errinfo_comment("Identifier already declared.")); @@ -177,7 +180,8 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool ReferencesResolver::visit(Return& _return) { - assert(m_returnParameters); + if (asserts(m_returnParameters)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not set.")); _return.setFunctionReturnParameters(*m_returnParameters); return true; } diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index d8defb50a..c36820317 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -50,7 +50,6 @@ * Solidity scanner. */ -#include #include #include #include @@ -113,11 +112,10 @@ void Scanner::reset(CharStream const& _source) } -bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength) +bool Scanner::scanHexByte(char& o_scannedByte) { - assert(_expectedLength <= 4); // prevent overflow char x = 0; - for (int i = 0; i < _expectedLength; i++) + for (int i = 0; i < 2; i++) { int d = HexValue(m_char); if (d < 0) @@ -128,7 +126,7 @@ bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength) x = x * 16 + d; advance(); } - o_scannedNumber = x; + o_scannedByte = x; return true; } @@ -175,7 +173,8 @@ Token::Value Scanner::skipSingleLineComment() Token::Value Scanner::skipMultiLineComment() { - assert(m_char == '*'); + if (asserts(m_char == '*')) + BOOST_THROW_EXCEPTION(InternalCompilerError()); advance(); while (!isSourcePastEndOfInput()) { @@ -418,15 +417,11 @@ bool Scanner::scanEscape() case 't': c = '\t'; break; - case 'u': - if (!scanHexNumber(c, 4)) - return false; - break; case 'v': c = '\v'; break; case 'x': - if (!scanHexNumber(c, 2)) + if (!scanHexByte(c)) return false; break; } @@ -468,7 +463,9 @@ void Scanner::scanDecimalDigits() Token::Value Scanner::scanNumber(bool _periodSeen) { - assert(IsDecimalDigit(m_char)); // the first digit of the number or the fraction + // the first digit of the number or the fraction + if (asserts(IsDecimalDigit(m_char))) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Number does not start with decimal digit.")); enum { DECIMAL, HEX, OCTAL, IMPLICIT_OCTAL, BINARY } kind = DECIMAL; LiteralScope literal(this); if (_periodSeen) @@ -510,7 +507,8 @@ Token::Value Scanner::scanNumber(bool _periodSeen) // scan exponent, if any if (m_char == 'e' || m_char == 'E') { - assert(kind != HEX); // 'e'/'E' must be scanned as part of the hex number + if (asserts(kind != HEX)) // 'e'/'E' must be scanned as part of the hex number + BOOST_THROW_EXCEPTION(InternalCompilerError()); if (kind != DECIMAL) return Token::ILLEGAL; // scan exponent addLiteralCharAndAdvance(); @@ -606,7 +604,8 @@ Token::Value Scanner::scanNumber(bool _periodSeen) static Token::Value KeywordOrIdentifierToken(string const& input) { - assert(!input.empty()); + if (asserts(!input.empty())) + BOOST_THROW_EXCEPTION(InternalCompilerError()); int const kMinLength = 2; int const kMaxLength = 10; if (input.size() < kMinLength || input.size() > kMaxLength) @@ -634,7 +633,8 @@ case ch: Token::Value Scanner::scanIdentifierOrKeyword() { - assert(IsIdentifierStart(m_char)); + if (asserts(IsIdentifierStart(m_char))) + BOOST_THROW_EXCEPTION(InternalCompilerError()); LiteralScope literal(this); addLiteralCharAndAdvance(); // Scan the rest of the identifier characters. @@ -656,7 +656,8 @@ char CharStream::advanceAndGet() char CharStream::rollback(size_t _amount) { - assert(m_pos >= _amount); + if (asserts(m_pos >= _amount)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); m_pos -= _amount; return get(); } diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h index c2dfb476f..537c2434e 100644 --- a/libsolidity/Scanner.h +++ b/libsolidity/Scanner.h @@ -169,7 +169,7 @@ private: /// If the next character is _next, advance and return _then, otherwise return _else. inline Token::Value selectToken(char _next, Token::Value _then, Token::Value _else); - bool scanHexNumber(char& o_scannedNumber, int _expectedLength); + bool scanHexByte(char& o_scannedByte); /// Scans a single JavaScript token. void scanToken(); diff --git a/libsolidity/Token.h b/libsolidity/Token.h index c54f387c7..0fb9b670f 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -42,9 +42,9 @@ #pragma once -#include #include #include +#include namespace dev { @@ -81,8 +81,6 @@ namespace solidity T(SEMICOLON, ";", 0) \ T(PERIOD, ".", 0) \ T(CONDITIONAL, "?", 3) \ - T(INC, "++", 0) \ - T(DEC, "--", 0) \ T(ARROW, "=>", 0) \ \ /* Assignment operators. */ \ @@ -136,6 +134,8 @@ namespace solidity /* being contiguous and sorted in the same order! */ \ T(NOT, "!", 0) \ T(BIT_NOT, "~", 0) \ + T(INC, "++", 0) \ + T(DEC, "--", 0) \ K(DELETE, "delete", 0) \ \ /* Keywords */ \ @@ -224,7 +224,8 @@ public: // (e.g. "LT" for the token LT). static char const* getName(Value tok) { - assert(tok < NUM_TOKENS); // tok is unsigned + if (asserts(tok < NUM_TOKENS)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); return m_name[tok]; } @@ -249,55 +250,10 @@ public: isEqualityOp(op) || isInequalityOp(op); } - static Value negateCompareOp(Value op) - { - assert(isArithmeticCompareOp(op)); - switch (op) - { - case EQ: - return NE; - case NE: - return EQ; - case LT: - return GTE; - case GT: - return LTE; - case LTE: - return GT; - case GTE: - return LT; - default: - assert(false); // should not get here - return op; - } - } - - static Value reverseCompareOp(Value op) - { - assert(isArithmeticCompareOp(op)); - switch (op) - { - case EQ: - return EQ; - case NE: - return NE; - case LT: - return GT; - case GT: - return LT; - case LTE: - return GTE; - case GTE: - return LTE; - default: - assert(false); // should not get here - return op; - } - } - static Value AssignmentToBinaryOp(Value op) { - assert(isAssignmentOp(op) && op != ASSIGN); + if (asserts(isAssignmentOp(op) && op != ASSIGN)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR)); } @@ -311,7 +267,8 @@ public: // have a (unique) string (e.g. an IDENTIFIER). static char const* toString(Value tok) { - assert(tok < NUM_TOKENS); // tok is unsigned. + if (asserts(tok < NUM_TOKENS)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); return m_string[tok]; } @@ -319,7 +276,8 @@ public: // operators; returns 0 otherwise. static int precedence(Value tok) { - assert(tok < NUM_TOKENS); // tok is unsigned. + if (asserts(tok < NUM_TOKENS)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); return m_precedence[tok]; } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 4f5563391..a4d70e3a0 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -20,7 +20,6 @@ * Solidity data types */ -#include #include #include #include @@ -33,6 +32,9 @@ namespace solidity std::shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) { + if (asserts(Token::isElementaryTypeName(_typeToken))) + BOOST_THROW_EXCEPTION(InternalCompilerError()); + if (Token::INT <= _typeToken && _typeToken <= Token::HASH256) { int offset = _typeToken - Token::INT; @@ -52,7 +54,8 @@ std::shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) else if (_typeToken == Token::BOOL) return std::make_shared(); else - assert(false); // @todo add other tyes + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + + std::string(Token::toString(_typeToken)) + " to type.")); return std::shared_ptr(); } @@ -63,7 +66,7 @@ std::shared_ptr Type::fromUserDefinedTypeName(UserDefinedTypeName const& _ std::shared_ptr Type::fromMapping(Mapping const&) { - assert(false); //@todo not yet implemented + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Mapping types not yet implemented.")); return std::shared_ptr(); } @@ -94,7 +97,8 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): { if (isAddress()) _bits = 160; - assert(_bits > 0 && _bits <= 256 && _bits % 8 == 0); + if (asserts(_bits > 0 && _bits <= 256 && _bits % 8 == 0)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid bit number for integer type: " + dev::toString(_bits))); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -187,7 +191,7 @@ u256 BoolType::literalValue(Literal const& _literal) const else if (_literal.getToken() == Token::FALSE_LITERAL) return u256(0); else - assert(false); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal.")); } bool ContractType::operator==(Type const& _other) const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 190134d7a..4493b8037 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -75,7 +76,11 @@ public: virtual unsigned getCalldataEncodedSize() const { return 0; } virtual std::string toString() const = 0; - virtual u256 literalValue(Literal const&) const { assert(false); } + virtual u256 literalValue(Literal const&) const + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested " + "for type without literals.")); + } }; /** diff --git a/solc/main.cpp b/solc/main.cpp index 1cf466c72..04fee2905 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -84,21 +84,27 @@ int main(int argc, char** argv) ASTPointer ast; shared_ptr scanner = make_shared(CharStream(sourceCode)); Parser parser; + bytes instructions; + Compiler compiler; try { ast = parser.parse(scanner); + + NameAndTypeResolver resolver; + resolver.resolveNamesAndTypes(*ast.get()); + + cout << "Syntax tree for the contract:" << endl; + dev::solidity::ASTPrinter printer(ast, sourceCode); + printer.print(cout); + + compiler.compileContract(*ast); + instructions = compiler.getAssembledBytecode(); } catch (ParserError const& exception) { SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Parser error", *scanner); return -1; } - - NameAndTypeResolver resolver; - try - { - resolver.resolveNamesAndTypes(*ast.get()); - } catch (DeclarationError const& exception) { SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Declaration error", *scanner); @@ -109,23 +115,26 @@ int main(int argc, char** argv) SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Type error", *scanner); return -1; } - - cout << "Syntax tree for the contract:" << endl; - dev::solidity::ASTPrinter printer(ast, sourceCode); - printer.print(cout); - - bytes instructions; - Compiler compiler; - try - { - compiler.compileContract(*ast); - instructions = compiler.getAssembledBytecode(); - } catch (CompilerError const& exception) { SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Compiler error", *scanner); return -1; } + catch (InternalCompilerError const& exception) + { + cerr << "Internal compiler error: " << boost::diagnostic_information(exception) << endl; + return -1; + } + catch (Exception const& exception) + { + cerr << "Exception during compilation: " << boost::diagnostic_information(exception) << endl; + return -1; + } + catch (...) + { + cerr << "Unknown exception during compilation." << endl; + return -1; + } cout << "EVM assembly:" << endl; compiler.streamAssembly(cout); From 052b74b1dcb0478c9bb08229c14481cae9d19f9f Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 5 Nov 2014 15:04:33 +0100 Subject: [PATCH 16/20] Stylistic corrections. --- libsolidity/AST.cpp | 2 +- libsolidity/CompilerContext.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index f9c49adc5..026aef975 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -344,7 +344,7 @@ void ExpressionStatement::checkTypeRequirements() m_expression->checkTypeRequirements(); } -void Expression::expectType(const Type& _expectedType) +void Expression::expectType(Type const& _expectedType) { checkTypeRequirements(); if (!getType()->isImplicitlyConvertibleTo(_expectedType)) diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index 44d0844c2..99cf090e0 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -41,7 +41,7 @@ void CompilerContext::initializeLocalVariables(unsigned _numVariables) } } -int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) +int CompilerContext::getStackPositionOfVariable(Declaration const& _declaration) { auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration); if (asserts(res != m_localVariables.end())) @@ -49,7 +49,7 @@ int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) return end(m_localVariables) - res - 1 + m_asm.deposit(); } -eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const +eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const { auto res = m_functionEntryLabels.find(&_function); if (asserts(res != m_functionEntryLabels.end())) From b813038c3c506fb66632c290dee38ae0ab0ebcfe Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 5 Nov 2014 15:21:20 +0100 Subject: [PATCH 17/20] assert and exception corrections in solidity-external files. --- alethzero/MainWin.cpp | 4 ++++ libevmface/Instruction.h | 23 ++++++++++++++++++++--- liblll/Assembly.h | 10 +++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 70f4b3017..dd417fbd3 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1585,6 +1585,10 @@ void Main::on_data_textChanged() solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", *scanner); solidity = "

Solidity

" + QString::fromStdString(error.str()).toHtmlEscaped() + "
"; } + catch (...) + { + solidity = "

Solidity

Uncaught exception.
"; + } } else { diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 8392c410b..7cf205c03 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -32,6 +32,8 @@ namespace dev namespace eth { +class InvalidOpcode: public Exception {}; + /// Virtual machine bytecode instruction. enum class Instruction: uint8_t { @@ -177,13 +179,28 @@ enum class Instruction: uint8_t }; /// @returns the PUSH<_number> instruction -inline Instruction pushInstruction(unsigned _number) { assert(1 <= _number && _number <= 32); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); } +inline Instruction pushInstruction(unsigned _number) +{ + if (asserts(1 <= _number && _number <= 32)) + BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid PUSH instruction requested.")); + return Instruction(unsigned(Instruction::PUSH1) + _number - 1); +} /// @returns the DUP<_number> instruction -inline Instruction dupInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::DUP1) + _number - 1); } +inline Instruction dupInstruction(unsigned _number) +{ + if (asserts(1 <= _number && _number <= 16)) + BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid DUP instruction requested.")); + return Instruction(unsigned(Instruction::DUP1) + _number - 1); +} /// @returns the SWAP<_number> instruction -inline Instruction swapInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); } +inline Instruction swapInstruction(unsigned _number) +{ + if (asserts(1 <= _number && _number <= 16)) + BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid SWAP instruction requested.")); + return Instruction(unsigned(Instruction::SWAP1) + _number - 1); +} /// Information structure for a particular instruction. struct InstructionInfo diff --git a/liblll/Assembly.h b/liblll/Assembly.h index 38baee0c5..e39f1899b 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -45,8 +45,8 @@ public: AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {} AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {} - AssemblyItem tag() const { assert(m_type == PushTag || m_type == Tag); return AssemblyItem(Tag, m_data); } - AssemblyItem pushTag() const { assert(m_type == PushTag || m_type == Tag); return AssemblyItem(PushTag, m_data); } + AssemblyItem tag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(Tag, m_data); } + AssemblyItem pushTag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(PushTag, m_data); } AssemblyItemType type() const { return m_type; } u256 data() const { return m_data; } @@ -94,7 +94,7 @@ public: AssemblyItem const& back() { return m_items.back(); } std::string backString() const { return m_items.size() && m_items.back().m_type == PushString ? m_strings.at((h256)m_items.back().m_data) : std::string(); } - void onePath() { assert(!m_totalDeposit && !m_baseDeposit); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; } + void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; } void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; } void donePaths() { donePath(); m_totalDeposit = m_baseDeposit = 0; } void ignored() { m_baseDeposit = m_deposit; } @@ -107,8 +107,8 @@ public: std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); } int deposit() const { return m_deposit; } - void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assert(m_deposit >= 0); } - void setDeposit(int _deposit) { m_deposit = _deposit; assert(m_deposit >= 0); } + void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } + void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } bytes assemble() const; Assembly& optimise(bool _enable); From a6707cfadb6686255c3d262b8ed6432970f886c4 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 5 Nov 2014 17:50:59 +0100 Subject: [PATCH 18/20] Fix test framework after change to Transaction. --- test/solidityEndToEndTest.cpp | 47 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 7bb01fa12..5aeaf3e61 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -39,39 +39,56 @@ namespace test class ExecutionFramework { public: - ExecutionFramework() { g_logVerbosity = 0; } + ExecutionFramework(): m_executive(m_state) { 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(); + sendMessage(code, true); + m_contractAddress = m_executive.newAddress(); + BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); + return m_output; } 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(); + sendMessage(bytes(1, _index) + _data, false); + return m_output; } - bytes callFunction(byte _index, u256 const& _argument) + bytes callFunction(byte _index, u256 const& _argument1) { - return callFunction(_index, toBigEndian(_argument)); + callFunction(_index, toBigEndian(_argument1)); + return m_output; } private: + void sendMessage(bytes const& _data, bool _isCreation) + { + 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 + m_executive.setup(&transactionRLP); + } + catch (...) {} + if (_isCreation) + BOOST_REQUIRE(!m_executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address())); + else + BOOST_REQUIRE(!m_executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address())); + BOOST_REQUIRE(m_executive.go()); + m_executive.finalize(); + m_output = m_executive.out().toBytes(); + } + Address m_contractAddress; eth::State m_state; + eth::Executive m_executive; u256 const m_gasPrice = 100 * eth::szabo; u256 const m_gas = 1000000; + bytes m_output; }; BOOST_AUTO_TEST_SUITE(SolidityCompilerEndToEndTest) From 628e2048d8e10efbef882cb2ae4f1eee337445ff Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 5 Nov 2014 18:36:35 +0100 Subject: [PATCH 19/20] Corrected exception inheritance. --- libevmface/Instruction.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 7cf205c03..fadb5ab14 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -32,7 +32,7 @@ namespace dev namespace eth { -class InvalidOpcode: public Exception {}; +struct InvalidOpcode: virtual Exception {}; /// Virtual machine bytecode instruction. enum class Instruction: uint8_t From 8ee9bcf87a3eec0628137b4b303817c45d8a3cc4 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 5 Nov 2014 18:49:58 +0100 Subject: [PATCH 20/20] Further framework fix. --- test/solidityEndToEndTest.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 5aeaf3e61..b28b8499a 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -39,14 +39,12 @@ namespace test class ExecutionFramework { public: - ExecutionFramework(): m_executive(m_state) { g_logVerbosity = 0; } + ExecutionFramework() { g_logVerbosity = 0; } bytes compileAndRun(std::string const& _sourceCode) { bytes code = dev::solidity::CompilerStack::compile(_sourceCode); sendMessage(code, true); - m_contractAddress = m_executive.newAddress(); - BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); return m_output; } @@ -65,27 +63,31 @@ public: 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 - m_executive.setup(&transactionRLP); + executive.setup(&transactionRLP); } catch (...) {} if (_isCreation) - BOOST_REQUIRE(!m_executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address())); + { + 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(!m_executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address())); - BOOST_REQUIRE(m_executive.go()); - m_executive.finalize(); - m_output = m_executive.out().toBytes(); + 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; - eth::Executive m_executive; u256 const m_gasPrice = 100 * eth::szabo; u256 const m_gas = 1000000; bytes m_output;