From 5026c5855ded326a5ea7e675d8c084ccda88c8ee Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 12 Dec 2014 16:49:26 +0100 Subject: [PATCH 01/57] Create contracts. --- libsolidity/AST.cpp | 38 ++++++++++++++++++++++++++---- libsolidity/AST.h | 28 ++++++++++++++++++++++ libsolidity/ASTForward.h | 1 + libsolidity/ASTPrinter.cpp | 13 ++++++++++ libsolidity/ASTPrinter.h | 2 ++ libsolidity/ASTVisitor.h | 4 ++++ libsolidity/AST_accept.h | 20 ++++++++++++++++ libsolidity/Compiler.cpp | 17 ++++++++----- libsolidity/Compiler.h | 6 +++-- libsolidity/CompilerContext.cpp | 8 +++++++ libsolidity/CompilerContext.h | 12 +++++++--- libsolidity/CompilerStack.cpp | 6 ++++- libsolidity/CompilerStack.h | 2 +- libsolidity/ExpressionCompiler.cpp | 38 ++++++++++++++++++++++++++++++ libsolidity/ExpressionCompiler.h | 1 + libsolidity/InterfaceHandler.cpp | 8 +++---- libsolidity/InterfaceHandler.h | 8 +++---- libsolidity/Parser.cpp | 12 +++++++++- libsolidity/Types.cpp | 13 ++++++++++ libsolidity/Types.h | 11 +++++++-- libsolidity/grammar.txt | 3 ++- test/solidityCompiler.cpp | 2 +- 22 files changed, 222 insertions(+), 31 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 8174d138a..3fcf8ba0b 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -54,6 +54,14 @@ vector ContractDefinition::getInterfaceFunctions() co return exportedFunctions; } +FunctionDefinition const* ContractDefinition::getConstructor() const +{ + for (ASTPointer const& f: m_definedFunctions) + if (f->getName() == getName()) + return f.get(); + return nullptr; +} + void StructDefinition::checkMemberTypes() const { for (ASTPointer const& member: getMembers()) @@ -235,13 +243,12 @@ void FunctionCall::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); m_type = type.getActualType(); } - else + else if (FunctionType const* functionType = dynamic_cast(expressionType)) { //@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& functionType = dynamic_cast(*expressionType); - TypePointers const& parameterTypes = functionType.getParameterTypes(); + TypePointers const& parameterTypes = functionType->getParameterTypes(); if (parameterTypes.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); for (size_t i = 0; i < m_arguments.size(); ++i) @@ -249,11 +256,13 @@ 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 (functionType.getReturnParameterTypes().empty()) + if (functionType->getReturnParameterTypes().empty()) m_type = make_shared(); else - m_type = functionType.getReturnParameterTypes().front(); + m_type = functionType->getReturnParameterTypes().front(); } + else + BOOST_THROW_EXCEPTION(createTypeError("Type is not callable.")); } bool FunctionCall::isTypeConversion() const @@ -261,6 +270,25 @@ bool FunctionCall::isTypeConversion() const return m_expression->getType()->getCategory() == Type::Category::TYPE; } +void NewExpression::checkTypeRequirements() +{ + m_contractName->checkTypeRequirements(); + for (ASTPointer const& argument: m_arguments) + argument->checkTypeRequirements(); + + m_contract = dynamic_cast(m_contractName->getReferencedDeclaration()); + if (!m_contract) + BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); + shared_ptr type = make_shared(*m_contract); + m_type = type; + TypePointers const& parameterTypes = type->getConstructorType()->getParameterTypes(); + if (parameterTypes.size() != m_arguments.size()) + BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for constructor call.")); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) + BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructor call.")); +} + void MemberAccess::checkTypeRequirements() { m_expression->checkTypeRequirements(); diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 19c4b4275..3a15bbebe 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -181,6 +181,9 @@ public: /// Returns the functions that make up the calling interface in the intended order. std::vector getInterfaceFunctions() const; + /// Returns the constructor or nullptr if no constructor was specified + FunctionDefinition const* getConstructor() const; + private: std::vector> m_definedStructs; std::vector> m_stateVariables; @@ -740,6 +743,31 @@ private: std::vector> m_arguments; }; +/** + * Expression that creates a new contract, e.g. "new SomeContract(1, 2)". + */ +class NewExpression: public Expression +{ +public: + NewExpression(Location const& _location, ASTPointer const& _contractName, + std::vector> const& _arguments): + Expression(_location), m_contractName(_contractName), m_arguments(_arguments) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + virtual void checkTypeRequirements() override; + + std::vector> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } + + /// Returns the referenced contract. Can only be called after type checking. + ContractDefinition const* getContract() const { return m_contract; } + +private: + ASTPointer m_contractName; + std::vector> m_arguments; + + ContractDefinition const* m_contract = nullptr; +}; + /** * Access to a member of an object. Example: x.name */ diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index 8b4bac1ce..d6c4c9f44 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -62,6 +62,7 @@ class Assignment; class UnaryOperation; class BinaryOperation; class FunctionCall; +class NewExpression; class MemberAccess; class IndexAccess; class PrimaryExpression; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index eddb51340..970822a9a 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -226,6 +226,14 @@ bool ASTPrinter::visit(FunctionCall const& _node) return goDeeper(); } +bool ASTPrinter::visit(NewExpression const& _node) +{ + writeLine("NewExpression"); + printType(_node); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(MemberAccess const& _node) { writeLine("MemberAccess to member " + _node.getMemberName()); @@ -402,6 +410,11 @@ void ASTPrinter::endVisit(FunctionCall const&) m_indentation--; } +void ASTPrinter::endVisit(NewExpression const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(MemberAccess const&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index 5a9187154..15b65e3f0 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -67,6 +67,7 @@ public: bool visit(UnaryOperation const& _node) override; bool visit(BinaryOperation const& _node) override; bool visit(FunctionCall const& _node) override; + bool visit(NewExpression const& _node) override; bool visit(MemberAccess const& _node) override; bool visit(IndexAccess const& _node) override; bool visit(PrimaryExpression const& _node) override; @@ -99,6 +100,7 @@ public: void endVisit(UnaryOperation const&) override; void endVisit(BinaryOperation const&) override; void endVisit(FunctionCall const&) override; + void endVisit(NewExpression const&) override; void endVisit(MemberAccess const&) override; void endVisit(IndexAccess const&) override; void endVisit(PrimaryExpression const&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index 4e1a49458..5728cd3d0 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -68,6 +68,7 @@ public: virtual bool visit(UnaryOperation&) { return true; } virtual bool visit(BinaryOperation&) { return true; } virtual bool visit(FunctionCall&) { return true; } + virtual bool visit(NewExpression&) { return true; } virtual bool visit(MemberAccess&) { return true; } virtual bool visit(IndexAccess&) { return true; } virtual bool visit(PrimaryExpression&) { return true; } @@ -102,6 +103,7 @@ public: virtual void endVisit(UnaryOperation&) { } virtual void endVisit(BinaryOperation&) { } virtual void endVisit(FunctionCall&) { } + virtual void endVisit(NewExpression&) { } virtual void endVisit(MemberAccess&) { } virtual void endVisit(IndexAccess&) { } virtual void endVisit(PrimaryExpression&) { } @@ -140,6 +142,7 @@ public: virtual bool visit(UnaryOperation const&) { return true; } virtual bool visit(BinaryOperation const&) { return true; } virtual bool visit(FunctionCall const&) { return true; } + virtual bool visit(NewExpression const&) { return true; } virtual bool visit(MemberAccess const&) { return true; } virtual bool visit(IndexAccess const&) { return true; } virtual bool visit(PrimaryExpression const&) { return true; } @@ -174,6 +177,7 @@ public: virtual void endVisit(UnaryOperation const&) { } virtual void endVisit(BinaryOperation const&) { } virtual void endVisit(FunctionCall const&) { } + virtual void endVisit(NewExpression const&) { } virtual void endVisit(MemberAccess const&) { } virtual void endVisit(IndexAccess const&) { } virtual void endVisit(PrimaryExpression const&) { } diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index 173273c6d..e0454d33e 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -419,6 +419,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void NewExpression::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_contractName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void NewExpression::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_contractName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + void MemberAccess::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 258336917..48d8be88a 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -33,9 +33,11 @@ using namespace std; namespace dev { namespace solidity { -void Compiler::compileContract(ContractDefinition const& _contract, vector const& _magicGlobals) +void Compiler::compileContract(ContractDefinition const& _contract, vector const& _magicGlobals, + map const& _contracts) { m_context = CompilerContext(); // clear it just in case + m_context.setCompiledContracts(_contracts); for (MagicVariableDeclaration const* variable: _magicGlobals) m_context.addMagicGlobal(*variable); @@ -50,12 +52,14 @@ void Compiler::compileContract(ContractDefinition const& _contract, vectorgetName() != _contract.getName()) // don't add the constructor here function->accept(*this); - packIntoContractCreator(_contract); + packIntoContractCreator(_contract, _contracts); } -void Compiler::packIntoContractCreator(ContractDefinition const& _contract) +void Compiler::packIntoContractCreator(ContractDefinition const& _contract, + map const& _contracts) { CompilerContext runtimeContext; + runtimeContext.setCompiledContracts(_contracts); swap(m_context, runtimeContext); registerStateVariables(_contract); @@ -67,11 +71,14 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract) constructor = function.get(); break; } + eth::AssemblyItem sub = m_context.addSubroutine(runtimeContext.getAssembly()); + // stack contains sub size if (constructor) { eth::AssemblyItem returnTag = m_context.pushNewTag(); m_context.addFunction(*constructor); // note that it cannot be called due to syntactic reasons - //@todo copy constructor arguments from calldata to memory prior to this + // copy constructor arguments + //@todo ask assembly for the size of the current program //@todo calling other functions inside the constructor should either trigger a parse error //or we should copy them here (register them above and call "accept") - detecting which // functions are referenced / called needs to be done in a recursive way. @@ -81,8 +88,6 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract) m_context << returnTag; } - eth::AssemblyItem sub = m_context.addSubroutine(runtimeContext.getAssembly()); - // stack contains sub size m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY; m_context << u256(0) << eth::Instruction::RETURN; } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 639e98410..57e40cdae 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -32,14 +32,16 @@ class Compiler: private ASTConstVisitor public: explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_returnTag(m_context.newTag()) {} - void compileContract(ContractDefinition const& _contract, std::vector const& _magicGlobals); + void compileContract(ContractDefinition const& _contract, std::vector const& _magicGlobals, + std::map const& _contracts); bytes getAssembledBytecode() { return m_context.getAssembledBytecode(m_optimize); } void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } private: /// Creates a new compiler context / assembly, packs the current code into the data part and /// adds the constructor code. - void packIntoContractCreator(ContractDefinition const& _contract); + void packIntoContractCreator(ContractDefinition const& _contract, + std::map const& _contracts); void appendFunctionSelector(ContractDefinition const& _contract); /// Creates code that unpacks the arguments for the given function, from memory if /// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes. diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index cd22c4e8b..47401436a 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -62,6 +62,14 @@ void CompilerContext::addFunction(FunctionDefinition const& _function) m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); } +bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const +{ + auto ret = m_compiledContracts.find(&_contract); + if (asserts(ret != m_compiledContracts.end())) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Compiled contract not found.")); + return *ret->second; +} + bool CompilerContext::isLocalVariable(Declaration const* _declaration) const { return m_localVariables.count(_declaration) > 0; diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 652e65a63..99f5ae94c 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -39,8 +39,6 @@ namespace solidity { class CompilerContext { public: - CompilerContext(): m_stateVariablesSize(0) {} - void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration); void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } @@ -48,6 +46,9 @@ public: void addAndInitializeVariable(VariableDeclaration const& _declaration); void addFunction(FunctionDefinition const& _function); + void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } + bytes const& getCompiledContract(ContractDefinition const& _contract) const; + void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration); } @@ -80,6 +81,8 @@ public: /// 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); } + /// Adds data to the data section, pushes a reference to the stack + eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } /// Append elements to the current instruction list and adjust @a m_stackOffset. CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } @@ -90,13 +93,16 @@ public: eth::Assembly const& getAssembly() const { return m_asm; } void streamAssembly(std::ostream& _stream) const { _stream << m_asm; } bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); } + private: eth::Assembly m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set m_magicGlobals; + /// Other already compiled contracts to be used in contract creation calls. + std::map m_compiledContracts; /// Size of the state variables, offset of next variable to be added. - u256 m_stateVariablesSize; + u256 m_stateVariablesSize = 0; /// Storage offsets of state variables std::map m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 7aaa79b1c..23f5fd68f 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -96,16 +96,20 @@ void CompilerStack::compile(bool _optimize) { if (!m_parseSuccessful) parse(); + + map contractBytecode; for (Source const* source: m_sourceOrder) for (ASTPointer const& node: source->ast->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { m_globalContext->setCurrentContract(*contract); shared_ptr compiler = make_shared(_optimize); - compiler->compileContract(*contract, m_globalContext->getMagicVariables()); + compiler->compileContract(*contract, m_globalContext->getMagicVariables(), + contractBytecode); Contract& compiledContract = m_contracts[contract->getName()]; compiledContract.bytecode = compiler->getAssembledBytecode(); compiledContract.compiler = move(compiler); + contractBytecode[compiledContract.contract] = &compiledContract.bytecode; } } diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index b6c34f1ac..5ad6f0a60 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -108,7 +108,7 @@ private: struct Contract { - ContractDefinition* contract; + ContractDefinition const* contract; std::shared_ptr compiler; bytes bytecode; std::shared_ptr interfaceHandler; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f872e0581..743503bbf 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -279,6 +279,44 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) return false; } +bool ExpressionCompiler::visit(NewExpression const& _newExpression) +{ + ContractType const* type = dynamic_cast(_newExpression.getType().get()); + if (asserts(type)) + BOOST_THROW_EXCEPTION(InternalCompilerError()); + TypePointers const& types = type->getConstructorType()->getParameterTypes(); + vector> arguments = _newExpression.getArguments(); + if (asserts(arguments.size() == types.size())) + BOOST_THROW_EXCEPTION(InternalCompilerError()); + + // copy the contracts code into memory + bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract()); + m_context << u256(bytecode.size()); + //@todo could be done by actually appending the Assembly, but then we probably need to compile + // multiple times. Will revisit once external fuctions are inlined. + m_context.appendData(bytecode); + //@todo copy to memory position 0, shift as soon as we use memory + m_context << u256(0) << eth::Instruction::CODECOPY; + + unsigned dataOffset = bytecode.size(); + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + appendTypeConversion(*arguments[i]->getType(), *types[i]); + unsigned const numBytes = types[i]->getCalldataEncodedSize(); + if (numBytes > 32) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(arguments[i]->getLocation()) + << errinfo_comment("Type " + types[i]->toString() + " not yet supported.")); + bool const leftAligned = types[i]->getCategory() == Type::Category::STRING; + CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); + dataOffset += numBytes; + } + // size, offset, endowment + m_context << u256(dataOffset) << u256(0) << u256(0) << eth::Instruction::CREATE; + return false; +} + void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { ASTString const& member = _memberAccess.getMemberName(); diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 0bba211ce..67b16aac0 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -60,6 +60,7 @@ private: virtual void endVisit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; virtual bool visit(FunctionCall const& _functionCall) override; + virtual bool visit(NewExpression const& _newExpression) override; virtual void endVisit(MemberAccess const& _memberAccess) override; virtual bool visit(IndexAccess const& _indexAccess) override; virtual void endVisit(Identifier const& _identifier) override; diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index f26088afa..1310f504a 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -15,7 +15,7 @@ InterfaceHandler::InterfaceHandler() m_lastTag = DocTagType::NONE; } -std::unique_ptr InterfaceHandler::getDocumentation(ContractDefinition& _contractDef, +std::unique_ptr InterfaceHandler::getDocumentation(ContractDefinition const& _contractDef, DocumentationType _type) { switch(_type) @@ -32,7 +32,7 @@ std::unique_ptr InterfaceHandler::getDocumentation(ContractDefiniti return nullptr; } -std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinition& _contractDef) +std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinition const& _contractDef) { Json::Value methods(Json::arrayValue); @@ -63,7 +63,7 @@ std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinitio return std::unique_ptr(new std::string(m_writer.write(methods))); } -std::unique_ptr InterfaceHandler::getUserDocumentation(ContractDefinition& _contractDef) +std::unique_ptr InterfaceHandler::getUserDocumentation(ContractDefinition const& _contractDef) { Json::Value doc; Json::Value methods(Json::objectValue); @@ -88,7 +88,7 @@ std::unique_ptr InterfaceHandler::getUserDocumentation(ContractDefi return std::unique_ptr(new std::string(m_writer.write(doc))); } -std::unique_ptr InterfaceHandler::getDevDocumentation(ContractDefinition& _contractDef) +std::unique_ptr InterfaceHandler::getDevDocumentation(ContractDefinition const& _contractDef) { // LTODO: Somewhere in this function warnings for mismatch of param names // should be thrown diff --git a/libsolidity/InterfaceHandler.h b/libsolidity/InterfaceHandler.h index b1cd4b562..d271a6697 100644 --- a/libsolidity/InterfaceHandler.h +++ b/libsolidity/InterfaceHandler.h @@ -67,23 +67,23 @@ public: /// types provided by @c DocumentationType /// @return A unique pointer contained string with the json /// representation of provided type - std::unique_ptr getDocumentation(ContractDefinition& _contractDef, + std::unique_ptr getDocumentation(ContractDefinition const& _contractDef, DocumentationType _type); /// Get the ABI Interface of the contract /// @param _contractDef The contract definition /// @return A unique pointer contained string with the json /// representation of the contract's ABI Interface - std::unique_ptr getABIInterface(ContractDefinition& _contractDef); + std::unique_ptr getABIInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition /// @return A unique pointer contained string with the json /// representation of the contract's user documentation - std::unique_ptr getUserDocumentation(ContractDefinition& _contractDef); + std::unique_ptr getUserDocumentation(ContractDefinition const& _contractDef); /// Get the Developer's documentation of the contract /// @param _contractDef The contract definition /// @return A unique pointer contained string with the json /// representation of the contract's developer documentation - std::unique_ptr getDevDocumentation(ContractDefinition& _contractDef); + std::unique_ptr getDevDocumentation(ContractDefinition const& _contractDef); private: void resetUser(); diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index b678b2fc0..21651eb14 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -437,7 +437,17 @@ ASTPointer Parser::parseUnaryExpression() { ASTNodeFactory nodeFactory(*this); Token::Value token = m_scanner->getCurrentToken(); - if (Token::isUnaryOp(token) || Token::isCountOp(token)) + if (token == Token::NEW) + { + expectToken(Token::NEW); + ASTPointer contractName = ASTNodeFactory(*this).createNode(expectIdentifierToken()); + expectToken(Token::LPAREN); + vector> arguments(parseFunctionCallArguments()); + expectToken(Token::RPAREN); + nodeFactory.markEndPosition(); + return nodeFactory.createNode(contractName, arguments); + } + else if (Token::isUnaryOp(token) || Token::isCountOp(token)) { // prefix expression m_scanner->next(); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index c2d488418..f1cd7c228 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -310,6 +310,19 @@ MemberList const& ContractType::getMembers() const return *m_members; } +shared_ptr const& ContractType::getConstructorType() const +{ + if (!m_constructorType) + { + FunctionDefinition const* constr = m_contract.getConstructor(); + if (constr) + m_constructorType = make_shared(*constr); + else + m_constructorType = make_shared(TypePointers(), TypePointers()); + } + return m_constructorType; +} + unsigned ContractType::getFunctionIndex(string const& _functionName) const { unsigned index = 0; diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 807e60c74..48539a1d7 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -39,6 +39,7 @@ namespace solidity // @todo realMxN, dynamic strings, text, arrays class Type; // forward +class FunctionType; // forward using TypePointer = std::shared_ptr; using TypePointers = std::vector; @@ -249,10 +250,16 @@ public: virtual MemberList const& getMembers() const override; + /// Returns the function type of the constructor. Note that the location part of the function type + /// is not used, as this type cannot be the type of a variable or expression. + std::shared_ptr const& getConstructorType() const; + unsigned getFunctionIndex(std::string const& _functionName) const; private: ContractDefinition const& m_contract; + /// Type of the constructor, @see getConstructorType. Lazily initialized. + mutable std::shared_ptr m_constructorType; /// List of member types, will be lazy-initialized because of recursive references. mutable std::unique_ptr m_members; }; @@ -339,8 +346,8 @@ public: virtual std::string toString() const override; virtual bool canLiveOutsideStorage() const override { return false; } - TypePointer getKeyType() const { return m_keyType; } - TypePointer getValueType() const { return m_valueType; } + TypePointer const& getKeyType() const { return m_keyType; } + TypePointer const& getValueType() const { return m_valueType; } private: TypePointer m_keyType; diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index c0ab06074..793e91882 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -25,11 +25,12 @@ Break = 'break' ';' Return = 'return' Expression? ';' VariableDefinition = VariableDeclaration ( = Expression )? ';' -Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | IndexAccess | +Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewExpression | IndexAccess | MemberAccess | PrimaryExpression // The expression syntax is actually much more complicated Assignment = Expression (AssignmentOp Expression) FunctionCall = Expression '(' ( Expression ( ',' Expression )* ) ')' +NewExpression = 'new' Identifier '(' ( Expression ( ',' Expression )* ) ')' MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expresison ']' PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index eae8f3142..29f61454a 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -56,7 +56,7 @@ bytes compileContract(const string& _sourceCode) BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); Compiler compiler; - compiler.compileContract(*contract, {}); + compiler.compileContract(*contract, {}, {}); // debug //compiler.streamAssembly(cout); return compiler.getAssembledBytecode(); From 7801b87ddb2b21f32af1495e367f11bc99c88766 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 15 Dec 2014 12:59:17 +0100 Subject: [PATCH 02/57] Packing and unpacking of constructor arguments. --- libevmcore/Assembly.cpp | 21 ++++++++++++++++++++- libevmcore/Assembly.h | 5 ++++- libsolidity/Compiler.cpp | 16 ++++++++++++---- libsolidity/CompilerContext.h | 2 ++ test/solidityEndToEndTest.cpp | 26 ++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index 059810af8..b78f03035 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -39,6 +39,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const case Push: return 1 + max(1, dev::bytesRequired(m_data)); case PushSubSize: + case PushProgramSize: return 4; // worst case: a 16MB program case PushTag: case PushData: @@ -59,7 +60,7 @@ int AssemblyItem::deposit() const { case Operation: return instructionInfo((Instruction)(byte)m_data).ret - instructionInfo((Instruction)(byte)m_data).args; - case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize: + case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize: case PushProgramSize: return 1; case Tag: return 0; @@ -146,6 +147,9 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) case PushSubSize: _out << " PUSHss[" << hex << h256(i.data()).abridged() << "]"; break; + case PushProgramSize: + _out << " PUSHSIZE"; + break; case NoOptimizeBegin: _out << " DoNotOptimze{{"; break; @@ -185,6 +189,9 @@ ostream& Assembly::streamRLP(ostream& _out, string const& _prefix) const case PushSubSize: _out << _prefix << " PUSH #[$" << h256(i.m_data).abridged() << "]" << endl; break; + case PushProgramSize: + _out << _prefix << " PUSHSIZE" << endl; + break; case Tag: _out << _prefix << "tag" << i.m_data << ": " << endl << _prefix << " JUMPDEST" << endl; break; @@ -303,6 +310,7 @@ Assembly& Assembly::optimise(bool _enable) { { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, + { { PushProgramSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } }, { { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, }; @@ -468,6 +476,7 @@ bytes Assembly::assemble() const vector tagPos(m_usedTags); map tagRef; multimap dataRef; + vector sizeRef; ///< Pointers to code locations where the size of the program is inserted unsigned bytesPerTag = dev::bytesRequired(totalBytes); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; @@ -526,6 +535,11 @@ bytes Assembly::assemble() const toBigEndian(s, byr); break; } + case PushProgramSize: + ret.push_back(tagPush); + sizeRef.push_back(ret.size()); + ret.resize(ret.size() + bytesPerTag); + break; case Tag: tagPos[(unsigned)i.m_data] = ret.size(); ret.push_back((byte)Instruction::JUMPDEST); @@ -561,5 +575,10 @@ bytes Assembly::assemble() const } } } + for (unsigned pos: sizeRef) + { + bytesRef r(ret.data() + pos, bytesPerTag); + toBigEndian(ret.size(), r); + } return ret; } diff --git a/libevmcore/Assembly.h b/libevmcore/Assembly.h index 5523710d4..81e4a9ff7 100644 --- a/libevmcore/Assembly.h +++ b/libevmcore/Assembly.h @@ -32,7 +32,7 @@ namespace dev namespace eth { -enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, Tag, PushData, NoOptimizeBegin, NoOptimizeEnd }; +enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData, NoOptimizeBegin, NoOptimizeEnd }; class Assembly; @@ -86,6 +86,9 @@ public: AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } + /// Pushes the final size of the current assembly itself. Use this when the code is modified + /// after compilation and CODESIZE is not an option. + void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 48d8be88a..f2877c1ce 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -77,12 +77,21 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, { eth::AssemblyItem returnTag = m_context.pushNewTag(); m_context.addFunction(*constructor); // note that it cannot be called due to syntactic reasons - // copy constructor arguments - //@todo ask assembly for the size of the current program + // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program + unsigned argumentSize = 0; + for (ASTPointer const& var: constructor->getParameters()) + argumentSize += var->getType()->getCalldataEncodedSize(); + if (argumentSize > 0) + { + m_context << u256(argumentSize); + m_context.appendProgramSize(); + m_context << u256(1); // copy it to byte one as expected for ABI calls + m_context << eth::Instruction::CODECOPY; + appendCalldataUnpacker(*constructor, true); + } //@todo calling other functions inside the constructor should either trigger a parse error //or we should copy them here (register them above and call "accept") - detecting which // functions are referenced / called needs to be done in a recursive way. - appendCalldataUnpacker(*constructor, true); m_context.appendJumpTo(m_context.getFunctionEntryLabel(*constructor)); constructor->accept(*this); m_context << returnTag; @@ -135,7 +144,6 @@ unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, b { // 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()) { diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 99f5ae94c..795f447ab 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -81,6 +81,8 @@ public: /// 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); } + /// Pushes the size of the final program + void appendProgramSize() { return m_asm.appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 15722abe1..b6f63aa72 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -1012,6 +1012,32 @@ BOOST_AUTO_TEST_CASE(strings_in_calls) BOOST_CHECK(callContractFunction(0, bytes({0, 'a', 1})) == bytes({0, 'a', 0, 0, 0})); } +BOOST_AUTO_TEST_CASE(constructor_arguments) +{ + char const* sourceCode = R"( + contract Helper { + string3 name; + bool flag; + function Helper(string3 x, bool f) { + name = x; + flag = f; + } + function getName() returns (string3 ret) { return name; } + function getFlag() returns (bool ret) { return flag; } + } + contract Main { + Helper h; + function Main() { + h = new Helper("abc", true); + } + function getFlag() returns (bool ret) { return h.getFlag(); } + function getName() returns (string3 ret) { return h.getName(); } + })"; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction(0) == bytes({0x01})); + BOOST_REQUIRE(callContractFunction(1) == bytes({'a', 'b', 'c'})); +} + BOOST_AUTO_TEST_SUITE_END() } From a3473ad88d39a1491d3b13da7efd11537eede1f1 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 15 Dec 2014 16:09:50 +0100 Subject: [PATCH 03/57] Check that constructor does not have "returns" directive. --- libsolidity/AST.cpp | 11 +++++++++++ libsolidity/AST.h | 4 ++++ libsolidity/NameAndTypeResolver.cpp | 6 +----- test/solidityNameAndTypeResolution.cpp | 9 +++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 3fcf8ba0b..5e344eadb 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -39,6 +39,17 @@ TypeError ASTNode::createTypeError(string const& _description) const return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } +void ContractDefinition::checkTypeRequirements() +{ + FunctionDefinition const* constructor = getConstructor(); + if (constructor && !constructor->getReturnParameters().empty()) + BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError( + "Non-empty \"returns\" directive for constructor.")); + + for (ASTPointer const& function: getDefinedFunctions()) + function->checkTypeRequirements(); +} + vector ContractDefinition::getInterfaceFunctions() const { vector exportedFunctions; diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 3a15bbebe..72f96394f 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -174,6 +174,10 @@ public: std::vector> const& getStateVariables() const { return m_stateVariables; } std::vector> const& getDefinedFunctions() const { return m_definedFunctions; } + /// Checks that the constructor does not have a "returns" statement and calls + /// checkTypeRequirements on all its functions. + void checkTypeRequirements(); + /// @return A shared pointer of an ASTString. /// Can contain a nullptr in which case indicates absence of documentation ASTPointer const& getDocumentation() const { return m_documentation; } diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 540b066eb..2ad276801 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -62,11 +62,7 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) // First, the parameter types of all functions need to be resolved before we can check // the types, since it is possible to call functions that are only defined later // in the source. - for (ASTPointer const& function: _contract.getDefinedFunctions()) - { - m_currentScope = &m_scopes[function.get()]; - function->checkTypeRequirements(); - } + _contract.checkTypeRequirements(); m_currentScope = &m_scopes[nullptr]; } diff --git a/test/solidityNameAndTypeResolution.cpp b/test/solidityNameAndTypeResolution.cpp index 03eaebb3a..0ea6e51b9 100644 --- a/test/solidityNameAndTypeResolution.cpp +++ b/test/solidityNameAndTypeResolution.cpp @@ -284,6 +284,15 @@ BOOST_AUTO_TEST_CASE(assignment_to_struct) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +BOOST_AUTO_TEST_CASE(returns_in_constructor) +{ + char const* text = "contract test {\n" + " function test() returns (uint a) {\n" + " }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() } From c00153e5c145cc7ac780195357a7ed6be9e9763a Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 16 Dec 2014 16:15:34 +0100 Subject: [PATCH 04/57] Stylistic changes. --- libevmcore/Assembly.cpp | 8 +++++++- libsolidity/AST.h | 2 +- libsolidity/Types.cpp | 6 +++--- libsolidity/grammar.txt | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index b78f03035..74bd015a6 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -60,7 +60,13 @@ int AssemblyItem::deposit() const { case Operation: return instructionInfo((Instruction)(byte)m_data).ret - instructionInfo((Instruction)(byte)m_data).args; - case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize: case PushProgramSize: + case Push: + case PushString: + case PushTag: + case PushData: + case PushSub: + case PushSubSize: + case PushProgramSize: return 1; case Tag: return 0; diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 72f96394f..4bb623b49 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -763,7 +763,7 @@ public: std::vector> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } /// Returns the referenced contract. Can only be called after type checking. - ContractDefinition const* getContract() const { return m_contract; } + ContractDefinition const* getContract() const { if (asserts(m_contract)) BOOST_THROW_EXCEPTION(InternalCompilerError()); else return m_contract; } private: ASTPointer m_contractName; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index f1cd7c228..eba11cb0d 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -314,9 +314,9 @@ shared_ptr const& ContractType::getConstructorType() const { if (!m_constructorType) { - FunctionDefinition const* constr = m_contract.getConstructor(); - if (constr) - m_constructorType = make_shared(*constr); + FunctionDefinition const* constructor = m_contract.getConstructor(); + if (constructor) + m_constructorType = make_shared(*constructor); else m_constructorType = make_shared(TypePointers(), TypePointers()); } diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 793e91882..a26f717a1 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -29,7 +29,7 @@ Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewE MemberAccess | PrimaryExpression // The expression syntax is actually much more complicated Assignment = Expression (AssignmentOp Expression) -FunctionCall = Expression '(' ( Expression ( ',' Expression )* ) ')' +FunctionCall = Expression '(' Expression ( ',' Expression )* ')' NewExpression = 'new' Identifier '(' ( Expression ( ',' Expression )* ) ')' MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expresison ']' From 76db957b529f5d436ba2d53787def77769f7729f Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Tue, 16 Dec 2014 18:00:39 +0100 Subject: [PATCH 05/57] fixed macdeployqt options order --- cmake/EthExecutableHelper.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/EthExecutableHelper.cmake b/cmake/EthExecutableHelper.cmake index 56aa3f6c3..9d273522e 100644 --- a/cmake/EthExecutableHelper.cmake +++ b/cmake/EthExecutableHelper.cmake @@ -61,12 +61,13 @@ macro(eth_install_executable EXECUTABLE) if (ETH_INSTALL_EXECUTABLE_QMLDIR) set(eth_qml_dir "-qmldir=${ETH_INSTALL_EXECUTABLE_QMLDIR}") + message(STATUS "${EXECUTABLE} qmldir: ${eth_qml_dir}") endif() if (APPLE) # First have qt5 install plugins and frameworks add_custom_command(TARGET ${EXECUTABLE} POST_BUILD - COMMAND ${MACDEPLOYQT_APP} ${eth_qml_dir} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTABLE}.app + COMMAND ${MACDEPLOYQT_APP} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTABLE}.app ${eth_qml_dir} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) # This tool and next will inspect linked libraries in order to determine which dependencies are required From 381c30ecd0cba487e523551dd1e7737847cb9f5f Mon Sep 17 00:00:00 2001 From: wanderer Date: Fri, 12 Dec 2014 09:35:18 -0500 Subject: [PATCH 06/57] changed output stacktrace format to json --- test/vm.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/test/vm.cpp b/test/vm.cpp index 49d6ed104..27e1da238 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -262,12 +262,45 @@ eth::OnOpFunc FakeExtVM::simpleTrace() dev::LogOutputStream(true) << o.str(); dev::LogOutputStream(false) << " | " << std::dec << ext.depth << " | " << ext.myAddress << " | #" << steps << " | " << std::hex << std::setw(4) << std::setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << std::dec << vm.gas() << " | -" << std::dec << gasCost << " | " << newMemSize << "x32" << " ]"; + /*creates json stack trace*/ if (eth::VMTraceChannel::verbosity <= g_logVerbosity) { + std::ostringstream ofile; + + u256s stack = vm.stack(); + ofile << endl << "{" << endl << " \"stack\":[" << endl; + for (vector::iterator i = stack.begin(); i != stack.end(); ++i){ + ofile << " \"" << (h256)*i << "\""; + if(next(i) != stack.end()){ + ofile << ","; + } + + ofile << endl; + } + + ofile << " ]," << endl; + ofile << " \"memory\": \""; + for(auto i: vm.memory()) + ofile << setfill('0') << setw(2) << hex << (unsigned)i; + ofile << "\"," << endl; + + ofile << " \"storage\": [" << endl; + for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second)){ + ofile << " [" << endl; + ofile << " \"" << std::showbase << std::hex << i.first << "\": \"" << i.second << "\"" << std::endl; + } + + ofile << " ]," << endl; + ofile << " \"depth\": " << dec << ext.depth << "," << endl; + ofile << " \"gas\":" << dec < Date: Sun, 14 Dec 2014 13:11:54 -0500 Subject: [PATCH 07/57] using json_spirit --- test/vm.cpp | 77 ++++++++++++++++++++++++++++++++--------------------- test/vm.h | 2 +- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/test/vm.cpp b/test/vm.cpp index 27e1da238..39ffdb7b2 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -265,42 +265,57 @@ eth::OnOpFunc FakeExtVM::simpleTrace() /*creates json stack trace*/ if (eth::VMTraceChannel::verbosity <= g_logVerbosity) { - std::ostringstream ofile; - - u256s stack = vm.stack(); - ofile << endl << "{" << endl << " \"stack\":[" << endl; - for (vector::iterator i = stack.begin(); i != stack.end(); ++i){ - ofile << " \"" << (h256)*i << "\""; - if(next(i) != stack.end()){ - ofile << ","; - } + Object o_step; - ofile << endl; - } + /*add the stack*/ + Array a_stack; + for (auto i: vm.stack()) + a_stack.push_back((string)i); - ofile << " ]," << endl; - ofile << " \"memory\": \""; - for(auto i: vm.memory()) - ofile << setfill('0') << setw(2) << hex << (unsigned)i; - ofile << "\"," << endl; + o_step.push_back(Pair( "stack", a_stack )); - ofile << " \"storage\": [" << endl; - for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second)){ - ofile << " [" << endl; - ofile << " \"" << std::showbase << std::hex << i.first << "\": \"" << i.second << "\"" << std::endl; + /*add the memory*/ + Array a_mem; + for(auto i: vm.memory()) + a_mem.push_back(i); + + o_step.push_back(Pair("memory", a_mem)); + + /*add the storage*/ + Object storage; + for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second)) + storage.push_back(Pair( (string)i.first , (string)i.second)); + + /*add all the other details*/ + o_step.push_back(Pair("storage", storage)); + o_step.push_back(Pair("depth", to_string(ext.depth))); + o_step.push_back(Pair("gas", (string)vm.gas())); + o_step.push_back(Pair("address", "0x" + toString(ext.myAddress ))); + o_step.push_back(Pair("step", steps )); + o_step.push_back(Pair("pc", (int)vm.curPC())); + o_step.push_back(Pair("opcode", instructionInfo(inst).name )); + + ifstream is( "./stackTrace.json"); + string istr((std::istreambuf_iterator(is) ), (std::istreambuf_iterator())); + is.close(); + Value iv; + Array a_trace; + + /*try to parse the current trace file*/ + try{ + read_string(istr, iv); + a_trace = iv.get_array(); } + catch(...){} + + /*add this step to the array of steps*/ + a_trace.push_back(o_step); + + ofstream os( "./stackTrace.json"); - ofile << " ]," << endl; - ofile << " \"depth\": " << dec << ext.depth << "," << endl; - ofile << " \"gas\":" << dec <. #include #include #include -#include "JsonSpiritHeaders.h" +#include #include #include #include From 4dd5240111dde6368df728fa752f9ae2d7efe53b Mon Sep 17 00:00:00 2001 From: wanderer Date: Tue, 16 Dec 2014 14:28:03 -0500 Subject: [PATCH 08/57] append JSON objects to log --- test/vm.cpp | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/test/vm.cpp b/test/vm.cpp index 39ffdb7b2..010eb4d75 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -295,26 +295,10 @@ eth::OnOpFunc FakeExtVM::simpleTrace() o_step.push_back(Pair("pc", (int)vm.curPC())); o_step.push_back(Pair("opcode", instructionInfo(inst).name )); - ifstream is( "./stackTrace.json"); - string istr((std::istreambuf_iterator(is) ), (std::istreambuf_iterator())); - is.close(); - Value iv; - Array a_trace; - - /*try to parse the current trace file*/ - try{ - read_string(istr, iv); - a_trace = iv.get_array(); - } - catch(...){} - - /*add this step to the array of steps*/ - a_trace.push_back(o_step); - - ofstream os( "./stackTrace.json"); - - Value v(a_trace); - os << write_string(v, true); + /*append the JSON object to the log file*/ + Value v(o_step); + ofstream os( "./stackTrace.json", ofstream::app); + os << write_string(v, true) << ","; os.close(); } }; From 8ecf06df863074e3c2009c0ed30540cb4fccc881 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Tue, 16 Dec 2014 20:17:23 +0100 Subject: [PATCH 09/57] FindCURL in out cmake folder, required to find debug curl library --- cmake/EthDependencies.cmake | 2 +- cmake/FindCURL.cmake | 49 +++++++++++++++++++++++++++++++++++++ cmake/FindGmp.cmake | 4 +-- 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 cmake/FindCURL.cmake diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index ee93a5311..136c86799 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -82,7 +82,7 @@ if (GMP_FOUND) endif() # curl is only requried for tests -# TODO specify min curl version, on windows we are currenly using 7.29 +# TODO specify min curl version, on windows we are currently using 7.29 find_package (CURL) message(" - curl header: ${CURL_INCLUDE_DIRS}") message(" - curl lib : ${CURL_LIBRARIES}") diff --git a/cmake/FindCURL.cmake b/cmake/FindCURL.cmake new file mode 100644 index 000000000..ba6603784 --- /dev/null +++ b/cmake/FindCURL.cmake @@ -0,0 +1,49 @@ +# Find CURL +# +# Find the curl includes and library +# +# if you nee to add a custom library search path, do it via via CMAKE_PREFIX_PATH +# +# This module defines +# CURL_INCLUDE_DIRS, where to find header, etc. +# CURL_LIBRARIES, the libraries needed to use curl. +# CURL_FOUND, If false, do not try to use curl. + +# only look in default directories +find_path( + CURL_INCLUDE_DIR + NAMES curl/curl.h + DOC "curl include dir" +) + +find_library( + CURL_LIBRARY + # names from cmake's FindCURL + NAMES curl curllib libcurl_imp curllib_static libcurl + DOC "curl library" +) + +set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) +set(CURL_LIBRARIES ${CURL_LIBRARY}) + +# debug library on windows +# same naming convention as in qt (appending debug library with d) +# boost is using the same "hack" as us with "optimized" and "debug" +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + find_library( + CURL_LIBRARY_DEBUG + NAMES curld libcurld + DOC "curl debug library" + ) + + set(CURL_LIBRARIES optimized ${CURL_LIBRARIES} debug ${CURL_LIBRARY_DEBUG}) + +endif() + +# handle the QUIETLY and REQUIRED arguments and set CURL_FOUND to TRUE +# if all listed variables are TRUE, hide their existence from configuration view +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CURL DEFAULT_MSG + CURL_INCLUDE_DIR CURL_LIBRARY) +mark_as_advanced (CURL_INCLUDE_DIR CURL_LIBRARY) + diff --git a/cmake/FindGmp.cmake b/cmake/FindGmp.cmake index 1cffa7efa..4570b35fa 100644 --- a/cmake/FindGmp.cmake +++ b/cmake/FindGmp.cmake @@ -14,13 +14,13 @@ find_path( GMP_INCLUDE_DIR NAMES gmp.h DOC "gmp include dir" - ) +) find_library( GMP_LIBRARY NAMES gmp DOC "gmp library" - ) +) set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR}) set(GMP_LIBRARIES ${GMP_LIBRARY}) From 99dce392b03edc5ec5e27039fac6e039953581ed Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Tue, 16 Dec 2014 20:53:48 +0100 Subject: [PATCH 10/57] find jsonrpccpp debug libraries --- cmake/FindJsonRpcCpp.cmake | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cmake/FindJsonRpcCpp.cmake b/cmake/FindJsonRpcCpp.cmake index 3072ff244..2ca176f68 100644 --- a/cmake/FindJsonRpcCpp.cmake +++ b/cmake/FindJsonRpcCpp.cmake @@ -43,6 +43,53 @@ set (JSON_RPC_CPP_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_SERVER set (JSON_RPC_CPP_SERVER_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_SERVER_LIBRARY}) set (JSON_RPC_CPP_CLIENT_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_CLIENT_LIBRARY}) +# debug library on windows +# same naming convention as in qt (appending debug library with d) +# boost is using the same "hack" as us with "optimized" and "debug" +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + find_library( + JSON_RPC_CPP_COMMON_LIBRARY_DEBUG + NAMES jsonrpccpp-commond + DOC "json-rpc-cpp common debug library" + ) + + find_library( + JSON_RPC_CPP_SERVER_LIBRARY_DEBUG + NAMES jsonrpccpp-serverd + DOC "json-rpc-cpp server debug library" + ) + + find_library( + JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG + NAMES jsonrpccpp-clientd + DOC "json-rpc-cpp client debug library" + ) + + set (JSON_RPC_CPP_LIBRARIES + optimized ${JSON_RPC_CPP_COMMON_LIBRARY} + optimized ${JSON_RPC_CPP_SERVER_LIBRARY} + optimized ${JSON_RPC_CPP_CLIENT_LIBRARY} + debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG} + debug ${JSON_RPC_CPP_SERVER_LIBRARY_DEBUG} + debug ${JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG} + ) + + set (JSON_RPC_CPP_SERVER_LIBRARIES + optimized ${JSON_RPC_CPP_COMMON_LIBRARY} + optimized ${JSON_RPC_CPP_SERVER_LIBRARY} + debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG} + debug ${JSON_RPC_CPP_SERVER_LIBRARY_DEBUG} + ) + + set (JSON_RPC_CPP_CLIENT_LIBRARIES + optimized ${JSON_RPC_CPP_COMMON_LIBRARY} + optimized ${JSON_RPC_CPP_CLIENT_LIBRARY} + debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG} + debug ${JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG} + ) + +endif() + # handle the QUIETLY and REQUIRED arguments and set JSON_RPC_CPP_FOUND to TRUE # if all listed variables are TRUE, hide their existence from configuration view include(FindPackageHandleStandardArgs) From 28409ed583eda067619a36832db8866af8593ac2 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 23:08:25 +0100 Subject: [PATCH 11/57] Fix for unhandled solc exception with opcodes argument - This should fix #631 --- solc/CommandLineInterface.cpp | 51 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 6ace332f7..f13423edb 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -43,6 +43,16 @@ using namespace std; namespace po = boost::program_options; +// LTODO: Maybe some argument class pairing names with +// extensions and other attributes would be a better choice here? +#define ARG_ABI_STR "abi" +#define ARG_ASM_STR "asm" +#define ARG_AST_STR "ast" +#define ARG_BINARY_STR "binary" +#define ARG_OPCODES_STR "opcodes" +#define ARG_NATSPECDEV_STR "natspec-dev" +#define ARG_NATSPECUSER_STR "natspec-user" + namespace dev { namespace solidity @@ -63,8 +73,9 @@ static inline bool argToStdout(po::variables_map const& _args, const char* _name static bool needStdout(po::variables_map const& _args) { - return argToStdout(_args, "abi") || argToStdout(_args, "natspec-user") || argToStdout(_args, "natspec-dev") || - argToStdout(_args, "asm") || argToStdout(_args, "opcodes") || argToStdout(_args, "binary"); + return argToStdout(_args, ARG_ABI_STR) || argToStdout(_args, ARG_NATSPECUSER_STR) || + argToStdout(_args, ARG_NATSPECDEV_STR) || argToStdout(_args, ARG_ASM_STR) || + argToStdout(_args, ARG_OPCODES_STR) || argToStdout(_args, ARG_BINARY_STR); } static inline bool outputToFile(OutputType type) @@ -94,7 +105,7 @@ static std::istream& operator>>(std::istream& _in, OutputType& io_output) void CommandLineInterface::handleBinary(string const& _contract) { - auto choice = m_args["binary"].as(); + auto choice = m_args[ARG_BINARY_STR].as(); if (outputToStdout(choice)) { cout << "Binary: " << endl; @@ -112,7 +123,7 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { // TODO: Figure out why the wrong operator << (from boost) is used here - auto choice = m_args["opcode"].as(); + auto choice = m_args[ARG_OPCODES_STR].as(); if (outputToStdout(choice)) { cout << "Opcodes: " << endl; @@ -130,9 +141,9 @@ void CommandLineInterface::handleOpcode(string const& _contract) void CommandLineInterface::handleBytecode(string const& _contract) { - if (m_args.count("opcodes")) + if (m_args.count(ARG_OPCODES_STR)) handleOpcode(_contract); - if (m_args.count("binary")) + if (m_args.count(ARG_BINARY_STR)) handleBinary(_contract); } @@ -145,17 +156,17 @@ void CommandLineInterface::handleJson(DocumentationType _type, switch(_type) { case DocumentationType::ABI_INTERFACE: - argName = "abi"; + argName = ARG_ABI_STR; suffix = ".abi"; title = "Contract ABI"; break; case DocumentationType::NATSPEC_USER: - argName = "natspec-user"; + argName = "ARG_NATSPECUSER_STR"; suffix = ".docuser"; title = "User Documentation"; break; case DocumentationType::NATSPEC_DEV: - argName = "natspec-dev"; + argName = ARG_NATSPECDEV_STR; suffix = ".docdev"; title = "Developer Documentation"; break; @@ -195,19 +206,19 @@ bool CommandLineInterface::parseArguments(int argc, char** argv) ("version", "Show version and exit") ("optimize", po::value()->default_value(false), "Optimize bytecode for size") ("input-file", po::value>(), "input file") - ("ast", po::value(), + (ARG_AST_STR, po::value(), "Request to output the AST of the contract. " OUTPUT_TYPE_STR) - ("asm", po::value(), + (ARG_ASM_STR, po::value(), "Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR) - ("opcodes", po::value(), + (ARG_OPCODES_STR, po::value(), "Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR) - ("binary", po::value(), + (ARG_BINARY_STR, po::value(), "Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR) - ("abi", po::value(), + (ARG_ABI_STR, po::value(), "Request to output the contract's ABI interface. " OUTPUT_TYPE_STR) - ("natspec-user", po::value(), + (ARG_NATSPECUSER_STR, po::value(), "Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR) - ("natspec-dev", po::value(), + (ARG_NATSPECDEV_STR, po::value(), "Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR); #undef OUTPUT_TYPE_STR @@ -321,9 +332,9 @@ bool CommandLineInterface::processInput() void CommandLineInterface::actOnInput() { // do we need AST output? - if (m_args.count("ast")) + if (m_args.count(ARG_AST_STR)) { - auto choice = m_args["ast"].as(); + auto choice = m_args[ARG_AST_STR].as(); if (outputToStdout(choice)) { cout << "Syntax trees:" << endl << endl; @@ -355,9 +366,9 @@ void CommandLineInterface::actOnInput() cout << endl << "======= " << contract << " =======" << endl; // do we need EVM assembly? - if (m_args.count("asm")) + if (m_args.count(ARG_ASM_STR)) { - auto choice = m_args["asm"].as(); + auto choice = m_args[ARG_ASM_STR].as(); if (outputToStdout(choice)) { cout << "EVM assembly:" << endl; From b95b6f3b6630bce6ecb73cdf20a58059276df488 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 23:31:22 +0100 Subject: [PATCH 12/57] Properly outputing opcodes in solc, using normal stream operator --- solc/CommandLineInterface.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index f13423edb..8aa182b5c 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -122,19 +122,18 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { - // TODO: Figure out why the wrong operator << (from boost) is used here auto choice = m_args[ARG_OPCODES_STR].as(); if (outputToStdout(choice)) { cout << "Opcodes: " << endl; - dev::operator<<(cout, m_compiler.getBytecode(_contract)); + cout << eth::disassemble(m_compiler.getBytecode(_contract)); cout << endl; } if (outputToFile(choice)) { ofstream outFile(_contract + ".opcode"); - dev::operator<<(outFile, m_compiler.getBytecode(_contract)); + outFile << eth::disassemble(m_compiler.getBytecode(_contract)); outFile.close(); } } From 41948b13097a8fed084dbdc8ff970cf1d49c40b1 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 16 Dec 2014 23:45:24 +0100 Subject: [PATCH 13/57] Fix: Resolve function types of all contracts before checking types inside functions. --- libsolidity/CompilerStack.cpp | 9 +++++++++ libsolidity/NameAndTypeResolver.cpp | 13 +++++++------ libsolidity/NameAndTypeResolver.h | 2 ++ test/solidityCompiler.cpp | 8 ++++++-- test/solidityExpressionCompiler.cpp | 7 ++++++- test/solidityNameAndTypeResolution.cpp | 18 ++++++++++++++++++ 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 23f5fd68f..1242c0aba 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -73,6 +73,15 @@ void CompilerStack::parse() resolver.resolveNamesAndTypes(*contract); m_contracts[contract->getName()].contract = contract; } + for (Source const* source: m_sourceOrder) + for (ASTPointer const& node: source->ast->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { + m_globalContext->setCurrentContract(*contract); + resolver.updateDeclaration(*m_globalContext->getCurrentThis()); + resolver.checkTypeRequirements(*contract); + m_contracts[contract->getName()].contract = contract; + } m_parseSuccessful = true; } diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 2ad276801..80732615c 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -49,8 +49,6 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) m_currentScope = &m_scopes[&_contract]; for (ASTPointer const& structDef: _contract.getDefinedStructs()) ReferencesResolver resolver(*structDef, *this, nullptr); - for (ASTPointer const& structDef: _contract.getDefinedStructs()) - structDef->checkValidityOfMembers(); for (ASTPointer const& variable: _contract.getStateVariables()) ReferencesResolver resolver(*variable, *this, nullptr); for (ASTPointer const& function: _contract.getDefinedFunctions()) @@ -59,13 +57,16 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) ReferencesResolver referencesResolver(*function, *this, function->getReturnParameterList().get()); } - // First, the parameter types of all functions need to be resolved before we can check - // the types, since it is possible to call functions that are only defined later - // in the source. - _contract.checkTypeRequirements(); m_currentScope = &m_scopes[nullptr]; } +void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) +{ + for (ASTPointer const& structDef: _contract.getDefinedStructs()) + structDef->checkValidityOfMembers(); + _contract.checkTypeRequirements(); +} + void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) { m_scopes[nullptr].registerDeclaration(_declaration, true); diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h index 1ff9febf0..23ac5fe77 100644 --- a/libsolidity/NameAndTypeResolver.h +++ b/libsolidity/NameAndTypeResolver.h @@ -46,6 +46,8 @@ public: void registerDeclarations(SourceUnit& _sourceUnit); /// Resolves all names and types referenced from the given contract. void resolveNamesAndTypes(ContractDefinition& _contract); + /// Check all type requirements in the given contract. + void checkTypeRequirements(ContractDefinition& _contract); /// Updates the given global declaration (used for "this"). Not to be used with declarations /// that create their own scope. void updateDeclaration(Declaration const& _declaration); diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 29f61454a..9ae8ff50b 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -52,9 +52,13 @@ bytes compileContract(const string& _sourceCode) resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) - { BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); - + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract)); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { Compiler compiler; compiler.compileContract(*contract, {}, {}); // debug diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index c05db25d4..2bdc38421 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -95,8 +95,13 @@ bytes compileFirstExpression(const string& _sourceCode, vector> _ resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) - { BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract)); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.getExpression() != nullptr); diff --git a/test/solidityNameAndTypeResolution.cpp b/test/solidityNameAndTypeResolution.cpp index 0ea6e51b9..0bda0a1fd 100644 --- a/test/solidityNameAndTypeResolution.cpp +++ b/test/solidityNameAndTypeResolution.cpp @@ -47,6 +47,9 @@ void parseTextAndResolveNames(std::string const& _source) for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) resolver.resolveNamesAndTypes(*contract); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + resolver.checkTypeRequirements(*contract); } } @@ -293,6 +296,21 @@ BOOST_AUTO_TEST_CASE(returns_in_constructor) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +BOOST_AUTO_TEST_CASE(forward_function_reference) +{ + char const* text = "contract First {\n" + " function fun() returns (bool ret) {\n" + " return Second(1).fun(1, true, 3) > 0;\n" + " }\n" + "}\n" + "contract Second {\n" + " function fun(uint a, bool b, uint c) returns (uint ret) {\n" + " if (First(2).fun() == true) return 1;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + BOOST_AUTO_TEST_SUITE_END() } From 4e83ba5ae148fdcf887e813e7d1aa45ece312666 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 15 Dec 2014 22:57:39 +0100 Subject: [PATCH 14/57] Add functions needed by constructor. --- libsolidity/CallGraph.cpp | 65 ++++++++++++++++++++++ libsolidity/CallGraph.h | 53 ++++++++++++++++++ libsolidity/Compiler.cpp | 101 +++++++++++++++++++--------------- libsolidity/Compiler.h | 12 +++- test/solidityEndToEndTest.cpp | 17 ++++++ 5 files changed, 202 insertions(+), 46 deletions(-) create mode 100644 libsolidity/CallGraph.cpp create mode 100644 libsolidity/CallGraph.h diff --git a/libsolidity/CallGraph.cpp b/libsolidity/CallGraph.cpp new file mode 100644 index 000000000..48666ca7f --- /dev/null +++ b/libsolidity/CallGraph.cpp @@ -0,0 +1,65 @@ + +/* + 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 + * Callgraph of functions inside a contract. + */ + +#include +#include + +using namespace std; + +namespace dev { +namespace solidity { + +void CallGraph::addFunction(FunctionDefinition const& _function) +{ + if (!m_functionsSeen.count(&_function)) + { + m_functionsSeen.insert(&_function); + m_workQueue.push(&_function); + } +} + +set const& CallGraph::getCalls() +{ + return m_functionsSeen; +} + +void CallGraph::computeCallGraph() +{ + while (!m_workQueue.empty()) + { + FunctionDefinition const* fun = m_workQueue.front(); + fun->accept(*this); + m_workQueue.pop(); + } +} + +bool CallGraph::visit(Identifier const& _identifier) +{ + FunctionDefinition const* fun = dynamic_cast(_identifier.getReferencedDeclaration()); + if (fun) + addFunction(*fun); + return true; +} + +} +} diff --git a/libsolidity/CallGraph.h b/libsolidity/CallGraph.h new file mode 100644 index 000000000..986e4dc4a --- /dev/null +++ b/libsolidity/CallGraph.h @@ -0,0 +1,53 @@ +/* + 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 + * Callgraph of functions inside a contract. + */ + +#include +#include +#include +#include + +namespace dev { +namespace solidity { + +/** + * Can be used to compute the graph of calls (or rather references) between functions of the same + * contract. Current functionality is limited to computing all functions that are directly + * or indirectly called by some functions. + */ +class CallGraph: private ASTConstVisitor +{ +public: + void addFunction(FunctionDefinition const& _function); + void computeCallGraph(); + + std::set const& getCalls(); + +private: + void addFunctionToQueue(FunctionDefinition const& _function); + virtual bool visit(Identifier const& _identifier) override; + + std::set m_functionsSeen; + std::queue m_workQueue; +}; + +} +} diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index f2877c1ce..0f85cda38 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace std; @@ -37,68 +38,82 @@ void Compiler::compileContract(ContractDefinition const& _contract, vector const& _contracts) { m_context = CompilerContext(); // clear it just in case - m_context.setCompiledContracts(_contracts); - - for (MagicVariableDeclaration const* variable: _magicGlobals) - m_context.addMagicGlobal(*variable); + initializeContext(_contract, _magicGlobals, _contracts); for (ASTPointer const& function: _contract.getDefinedFunctions()) if (function->getName() != _contract.getName()) // don't add the constructor here m_context.addFunction(*function); - registerStateVariables(_contract); appendFunctionSelector(_contract); for (ASTPointer const& function: _contract.getDefinedFunctions()) if (function->getName() != _contract.getName()) // don't add the constructor here function->accept(*this); - packIntoContractCreator(_contract, _contracts); -} - -void Compiler::packIntoContractCreator(ContractDefinition const& _contract, - map const& _contracts) -{ + // Swap the runtime context with the creation-time context CompilerContext runtimeContext; - runtimeContext.setCompiledContracts(_contracts); swap(m_context, runtimeContext); + initializeContext(_contract, _magicGlobals, _contracts); + packIntoContractCreator(_contract, runtimeContext); +} +void Compiler::initializeContext(ContractDefinition const& _contract, vector const& _magicGlobals, + map const& _contracts) +{ + m_context.setCompiledContracts(_contracts); + for (MagicVariableDeclaration const* variable: _magicGlobals) + m_context.addMagicGlobal(*variable); registerStateVariables(_contract); +} - FunctionDefinition* constructor = nullptr; - for (ASTPointer const& function: _contract.getDefinedFunctions()) - if (function->getName() == _contract.getName()) - { - constructor = function.get(); - break; - } - eth::AssemblyItem sub = m_context.addSubroutine(runtimeContext.getAssembly()); - // stack contains sub size +void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +{ + set neededFunctions; + FunctionDefinition const* constructor = _contract.getConstructor(); if (constructor) - { - eth::AssemblyItem returnTag = m_context.pushNewTag(); - m_context.addFunction(*constructor); // note that it cannot be called due to syntactic reasons - // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program - unsigned argumentSize = 0; - for (ASTPointer const& var: constructor->getParameters()) - argumentSize += var->getType()->getCalldataEncodedSize(); - if (argumentSize > 0) - { - m_context << u256(argumentSize); - m_context.appendProgramSize(); - m_context << u256(1); // copy it to byte one as expected for ABI calls - m_context << eth::Instruction::CODECOPY; - appendCalldataUnpacker(*constructor, true); - } - //@todo calling other functions inside the constructor should either trigger a parse error - //or we should copy them here (register them above and call "accept") - detecting which - // functions are referenced / called needs to be done in a recursive way. - m_context.appendJumpTo(m_context.getFunctionEntryLabel(*constructor)); - constructor->accept(*this); - m_context << returnTag; - } + neededFunctions = getFunctionsNeededByConstructor(*constructor); + + for (FunctionDefinition const* fun: neededFunctions) + m_context.addFunction(*fun); + if (constructor) + appendConstructorCall(*constructor); + + eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly()); + // stack contains sub size m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY; m_context << u256(0) << eth::Instruction::RETURN; + + // note that we have to explicitly include all used functions because of absolute jump + // labels + for (FunctionDefinition const* fun: neededFunctions) + fun->accept(*this); +} + +void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) +{ + eth::AssemblyItem returnTag = m_context.pushNewTag(); + // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program + unsigned argumentSize = 0; + for (ASTPointer const& var: _constructor.getParameters()) + argumentSize += var->getType()->getCalldataEncodedSize(); + if (argumentSize > 0) + { + m_context << u256(argumentSize); + m_context.appendProgramSize(); + m_context << u256(1); // copy it to byte one as expected for ABI calls + m_context << eth::Instruction::CODECOPY; + appendCalldataUnpacker(_constructor, true); + } + m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); + m_context << returnTag; +} + +set Compiler::getFunctionsNeededByConstructor(FunctionDefinition const& _constructor) +{ + CallGraph callgraph; + callgraph.addFunction(_constructor); + callgraph.computeCallGraph(); + return callgraph.getCalls(); } void Compiler::appendFunctionSelector(ContractDefinition const& _contract) diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 57e40cdae..b30e85ae4 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -38,10 +38,16 @@ public: void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } private: - /// Creates a new compiler context / assembly, packs the current code into the data part and + /// Registers the global objects and the non-function objects inside the contract with the context. + void initializeContext(ContractDefinition const& _contract, std::vector const& _magicGlobals, + std::map const& _contracts); + /// Adds the code that is run at creation time. Should be run after exchanging the run-time context + /// with a new and initialized context. /// adds the constructor code. - void packIntoContractCreator(ContractDefinition const& _contract, - std::map const& _contracts); + void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + void appendConstructorCall(FunctionDefinition const& _constructor); + /// Recursively searches the call graph and returns all functions needed by the constructor (including itself). + std::set getFunctionsNeededByConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); /// Creates code that unpacks the arguments for the given function, from memory if /// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes. diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index b6f63aa72..78926e94a 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -1038,6 +1038,23 @@ BOOST_AUTO_TEST_CASE(constructor_arguments) BOOST_REQUIRE(callContractFunction(1) == bytes({'a', 'b', 'c'})); } +BOOST_AUTO_TEST_CASE(functions_called_by_constructor) +{ + char const* sourceCode = R"( + contract Test { + string3 name; + bool flag; + function Test() { + setName("abc"); + } + function getName() returns (string3 ret) { return name; } + private: + function setName(string3 _name) { name = _name; } + })"; + compileAndRun(sourceCode); + BOOST_REQUIRE(callContractFunction(0) == bytes({'a', 'b', 'c'})); +} + BOOST_AUTO_TEST_SUITE_END() } From c99b38a4ed70e578065a59e0dd332474fac6e42d Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 23:55:38 +0100 Subject: [PATCH 15/57] Using strings instead of #defined literals in solc --- solc/CommandLineInterface.cpp | 76 +++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 8aa182b5c..fd660a0ca 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -43,21 +43,21 @@ using namespace std; namespace po = boost::program_options; -// LTODO: Maybe some argument class pairing names with -// extensions and other attributes would be a better choice here? -#define ARG_ABI_STR "abi" -#define ARG_ASM_STR "asm" -#define ARG_AST_STR "ast" -#define ARG_BINARY_STR "binary" -#define ARG_OPCODES_STR "opcodes" -#define ARG_NATSPECDEV_STR "natspec-dev" -#define ARG_NATSPECUSER_STR "natspec-user" - namespace dev { namespace solidity { +// LTODO: Maybe some argument class pairing names with +// extensions and other attributes would be a better choice here? +static string const g_argAbiStr = "abi"; +static string const g_argAsmStr = "asm"; +static string const g_argAstStr = "ast"; +static string const g_argBinaryStr = "binary"; +static string const g_argOpcodesStr = "opcodes"; +static string const g_argNatspecDevStr = "natspec-dev"; +static string const g_argNatspecUserStr = "natspec-user"; + static void version() { cout << "solc, the solidity complier commandline interface " << dev::Version << endl @@ -66,16 +66,16 @@ static void version() exit(0); } -static inline bool argToStdout(po::variables_map const& _args, const char* _name) +static inline bool argToStdout(po::variables_map const& _args, string const& _name) { return _args.count(_name) && _args[_name].as() != OutputType::FILE; } static bool needStdout(po::variables_map const& _args) { - return argToStdout(_args, ARG_ABI_STR) || argToStdout(_args, ARG_NATSPECUSER_STR) || - argToStdout(_args, ARG_NATSPECDEV_STR) || argToStdout(_args, ARG_ASM_STR) || - argToStdout(_args, ARG_OPCODES_STR) || argToStdout(_args, ARG_BINARY_STR); + return argToStdout(_args, g_argAbiStr) || argToStdout(_args, g_argNatspecUserStr) || + argToStdout(_args, g_argNatspecDevStr) || argToStdout(_args, g_argAsmStr) || + argToStdout(_args, g_argOpcodesStr) || argToStdout(_args, g_argBinaryStr); } static inline bool outputToFile(OutputType type) @@ -105,7 +105,7 @@ static std::istream& operator>>(std::istream& _in, OutputType& io_output) void CommandLineInterface::handleBinary(string const& _contract) { - auto choice = m_args[ARG_BINARY_STR].as(); + auto choice = m_args[g_argBinaryStr].as(); if (outputToStdout(choice)) { cout << "Binary: " << endl; @@ -122,7 +122,7 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { - auto choice = m_args[ARG_OPCODES_STR].as(); + auto choice = m_args[g_argOpcodesStr].as(); if (outputToStdout(choice)) { cout << "Opcodes: " << endl; @@ -140,9 +140,9 @@ void CommandLineInterface::handleOpcode(string const& _contract) void CommandLineInterface::handleBytecode(string const& _contract) { - if (m_args.count(ARG_OPCODES_STR)) + if (m_args.count(g_argOpcodesStr)) handleOpcode(_contract); - if (m_args.count(ARG_BINARY_STR)) + if (m_args.count(g_argBinaryStr)) handleBinary(_contract); } @@ -155,17 +155,17 @@ void CommandLineInterface::handleJson(DocumentationType _type, switch(_type) { case DocumentationType::ABI_INTERFACE: - argName = ARG_ABI_STR; + argName = g_argAbiStr; suffix = ".abi"; title = "Contract ABI"; break; case DocumentationType::NATSPEC_USER: - argName = "ARG_NATSPECUSER_STR"; + argName = "g_argNatspecUserStr"; suffix = ".docuser"; title = "User Documentation"; break; case DocumentationType::NATSPEC_DEV: - argName = ARG_NATSPECDEV_STR; + argName = g_argNatspecDevStr; suffix = ".docdev"; title = "Developer Documentation"; break; @@ -205,20 +205,20 @@ bool CommandLineInterface::parseArguments(int argc, char** argv) ("version", "Show version and exit") ("optimize", po::value()->default_value(false), "Optimize bytecode for size") ("input-file", po::value>(), "input file") - (ARG_AST_STR, po::value(), + (g_argAstStr.c_str(), po::value(), "Request to output the AST of the contract. " OUTPUT_TYPE_STR) - (ARG_ASM_STR, po::value(), - "Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR) - (ARG_OPCODES_STR, po::value(), - "Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR) - (ARG_BINARY_STR, po::value(), - "Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR) - (ARG_ABI_STR, po::value(), - "Request to output the contract's ABI interface. " OUTPUT_TYPE_STR) - (ARG_NATSPECUSER_STR, po::value(), - "Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR) - (ARG_NATSPECDEV_STR, po::value(), - "Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR); + (g_argAsmStr.c_str(), po::value(), + "Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR) + (g_argOpcodesStr.c_str(), po::value(), + "Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR) + (g_argBinaryStr.c_str(), po::value(), + "Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR) + (g_argAbiStr.c_str(), po::value(), + "Request to output the contract's ABI interface. " OUTPUT_TYPE_STR) + (g_argNatspecUserStr.c_str(), po::value(), + "Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR) + (g_argNatspecDevStr.c_str(), po::value(), + "Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR); #undef OUTPUT_TYPE_STR // All positional options should be interpreted as input files @@ -331,9 +331,9 @@ bool CommandLineInterface::processInput() void CommandLineInterface::actOnInput() { // do we need AST output? - if (m_args.count(ARG_AST_STR)) + if (m_args.count(g_argAstStr)) { - auto choice = m_args[ARG_AST_STR].as(); + auto choice = m_args[g_argAstStr].as(); if (outputToStdout(choice)) { cout << "Syntax trees:" << endl << endl; @@ -365,9 +365,9 @@ void CommandLineInterface::actOnInput() cout << endl << "======= " << contract << " =======" << endl; // do we need EVM assembly? - if (m_args.count(ARG_ASM_STR)) + if (m_args.count(g_argAsmStr)) { - auto choice = m_args[ARG_ASM_STR].as(); + auto choice = m_args[g_argAsmStr].as(); if (outputToStdout(choice)) { cout << "EVM assembly:" << endl; From 30c000d121dc0132a6c5abb9d6e3da96b4dfda4a Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Mon, 15 Dec 2014 17:45:18 +0100 Subject: [PATCH 16/57] Adding a ForStatement solidity AST Node. - Adding ForStatement node - Implemented Parsing for ForStatement - A simple parsing test for the ForStatement - Work in progress --- libsolidity/AST.cpp | 7 +++++ libsolidity/AST.h | 24 +++++++++++++++++ libsolidity/ASTForward.h | 1 + libsolidity/ASTPrinter.cpp | 12 +++++++++ libsolidity/ASTPrinter.h | 2 ++ libsolidity/ASTVisitor.h | 4 +++ libsolidity/AST_accept.h | 24 +++++++++++++++++ libsolidity/Compiler.cpp | 7 +++++ libsolidity/Compiler.h | 1 + libsolidity/Parser.cpp | 54 +++++++++++++++++++++++++++++++------- libsolidity/Parser.h | 6 +++++ libsolidity/grammar.txt | 1 + test/solidityParser.cpp | 28 ++++++++++++++++++++ 13 files changed, 162 insertions(+), 9 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 5e344eadb..c286c412b 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -130,6 +130,13 @@ void WhileStatement::checkTypeRequirements() m_body->checkTypeRequirements(); } +void ForStatement::checkTypeRequirements() +{ + // LTODO + m_condExpression->expectType(BoolType()); + m_body->checkTypeRequirements(); +} + void Return::checkTypeRequirements() { if (!m_expression) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 4bb623b49..297b7f4e9 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -509,6 +509,30 @@ private: ASTPointer m_body; }; +class ForStatement: public BreakableStatement +{ +public: + ForStatement(Location const& _location, + ASTPointer const& _initExpression, + ASTPointer const& _conditionExpression, + ASTPointer const& _loopExpression, + ASTPointer const& _body): + BreakableStatement(_location), + m_initExpression(_initExpression), + m_condExpression(_conditionExpression), + m_loopExpression(_loopExpression), + m_body(_body) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_initExpression; + ASTPointer m_condExpression; + ASTPointer m_loopExpression; + ASTPointer m_body; +}; + class Continue: public Statement { public: diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index d6c4c9f44..c960fc8f0 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -52,6 +52,7 @@ class Block; class IfStatement; class BreakableStatement; class WhileStatement; +class ForStatement; class Continue; class Break; class Return; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 970822a9a..916fca1ef 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -150,6 +150,13 @@ bool ASTPrinter::visit(WhileStatement const& _node) return goDeeper(); } +bool ASTPrinter::visit(ForStatement const& _node) +{ + writeLine("ForStatement"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Continue const& _node) { writeLine("Continue"); @@ -360,6 +367,11 @@ void ASTPrinter::endVisit(WhileStatement const&) m_indentation--; } +void ASTPrinter::endVisit(ForStatement const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Continue const&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index 15b65e3f0..fc5fb4acb 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -57,6 +57,7 @@ public: bool visit(IfStatement const& _node) override; bool visit(BreakableStatement const& _node) override; bool visit(WhileStatement const& _node) override; + bool visit(ForStatement const& _node) override; bool visit(Continue const& _node) override; bool visit(Break const& _node) override; bool visit(Return const& _node) override; @@ -90,6 +91,7 @@ public: void endVisit(IfStatement const&) override; void endVisit(BreakableStatement const&) override; void endVisit(WhileStatement const&) override; + void endVisit(ForStatement const&) override; void endVisit(Continue const&) override; void endVisit(Break const&) override; void endVisit(Return const&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index 5728cd3d0..33a8a3383 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -58,6 +58,7 @@ public: virtual bool visit(IfStatement&) { return true; } virtual bool visit(BreakableStatement&) { return true; } virtual bool visit(WhileStatement&) { return true; } + virtual bool visit(ForStatement&) { return true; } virtual bool visit(Continue&) { return true; } virtual bool visit(Break&) { return true; } virtual bool visit(Return&) { return true; } @@ -93,6 +94,7 @@ public: virtual void endVisit(IfStatement&) { } virtual void endVisit(BreakableStatement&) { } virtual void endVisit(WhileStatement&) { } + virtual void endVisit(ForStatement&) { } virtual void endVisit(Continue&) { } virtual void endVisit(Break&) { } virtual void endVisit(Return&) { } @@ -132,6 +134,7 @@ public: virtual bool visit(IfStatement const&) { return true; } virtual bool visit(BreakableStatement const&) { return true; } virtual bool visit(WhileStatement const&) { return true; } + virtual bool visit(ForStatement const&) { return true; } virtual bool visit(Continue const&) { return true; } virtual bool visit(Break const&) { return true; } virtual bool visit(Return const&) { return true; } @@ -167,6 +170,7 @@ public: virtual void endVisit(IfStatement const&) { } virtual void endVisit(BreakableStatement const&) { } virtual void endVisit(WhileStatement const&) { } + virtual void endVisit(ForStatement const&) { } virtual void endVisit(Continue const&) { } virtual void endVisit(Break const&) { } virtual void endVisit(Return const&) { } diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index e0454d33e..ffef6f8b2 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -267,6 +267,30 @@ void WhileStatement::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void ForStatement::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_initExpression->accept(_visitor); + m_condExpression->accept(_visitor); + m_loopExpression->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ForStatement::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_initExpression->accept(_visitor); + m_condExpression->accept(_visitor); + m_loopExpression->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Continue::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index f2877c1ce..a0cad5374 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -287,6 +287,13 @@ bool Compiler::visit(WhileStatement const& _whileStatement) return false; } +bool Compiler::visit(ForStatement const& _forStatement) +{ + // LTODO + (void) _forStatement; + return false; +} + bool Compiler::visit(Continue const&) { if (!m_continueTags.empty()) diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 57e40cdae..c4f46d10f 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -53,6 +53,7 @@ private: virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; + virtual bool visit(ForStatement const& _forStatement) override; virtual bool visit(Continue const& _continue) override; virtual bool visit(Break const& _break) override; virtual bool visit(Return const& _return) override; diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 21651eb14..9941fbf43 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -304,6 +304,8 @@ ASTPointer Parser::parseStatement() return parseIfStatement(); case Token::WHILE: return parseWhileStatement(); + case Token::FOR: + return parseForStatement(); case Token::LBRACE: return parseBlock(); // starting from here, all statements must be terminated by a semicolon @@ -328,15 +330,7 @@ ASTPointer Parser::parseStatement() } break; default: - // 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 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 || - ((Token::isElementaryTypeName(m_scanner->getCurrentToken()) || - m_scanner->getCurrentToken() == Token::IDENTIFIER) && - m_scanner->peekNextToken() == Token::IDENTIFIER)) + if (peekVariableDefinition()) statement = parseVariableDefinition(); else // "ordinary" expression statement statement = parseExpressionStatement(); @@ -377,6 +371,34 @@ ASTPointer Parser::parseWhileStatement() return nodeFactory.createNode(condition, body); } +ASTPointer Parser::parseForStatement() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::FOR); + expectToken(Token::LPAREN); + ASTPointer initExpression = parseVardefOrExprstatement(); + expectToken(Token::SEMICOLON); + ASTPointer conditionExpression = parseExpression(); + expectToken(Token::SEMICOLON); + ASTPointer loopExpression = parseExpressionStatement(); + expectToken(Token::SEMICOLON); + expectToken(Token::RPAREN); + ASTPointer body = parseStatement(); + nodeFactory.setEndPositionFromNode(body); + return nodeFactory.createNode(initExpression, + conditionExpression, + loopExpression, + body); +} + +ASTPointer Parser::parseVardefOrExprstatement() +{ + if (peekVariableDefinition()) + return parseVariableDefinition(); + else + return parseExpressionStatement(); +} + ASTPointer Parser::parseVariableDefinition() { ASTNodeFactory nodeFactory(*this); @@ -566,6 +588,20 @@ vector> Parser::parseFunctionCallArguments() return arguments; } + +// 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 get a keyword that specifies a type name, or +// in the case of a user-defined type, we have two identifiers following each other. +bool Parser::peekVariableDefinition() +{ + return (m_scanner->getCurrentToken() == Token::MAPPING || + m_scanner->getCurrentToken() == Token::VAR || + ((Token::isElementaryTypeName(m_scanner->getCurrentToken()) || + m_scanner->getCurrentToken() == Token::IDENTIFIER) && + m_scanner->peekNextToken() == Token::IDENTIFIER)); +} + void Parser::expectToken(Token::Value _value) { if (m_scanner->getCurrentToken() != _value) diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index 52a374e03..343a85c0e 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -59,6 +59,8 @@ private: ASTPointer parseStatement(); ASTPointer parseIfStatement(); ASTPointer parseWhileStatement(); + ASTPointer parseForStatement(); + ASTPointer parseVardefOrExprstatement(); ASTPointer parseVariableDefinition(); ASTPointer parseExpressionStatement(); ASTPointer parseExpression(); @@ -72,6 +74,10 @@ private: ///@{ ///@name Helper functions + /// Peeks ahead in the scanner to determine if an expression statement + /// could be a variable definition + bool peekVariableDefinition(); + /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); Token::Value expectAssignmentOperator(); diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index a26f717a1..99a590014 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -20,6 +20,7 @@ Statement = IfStatement | WhileStatement | Block | IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? WhileStatement = 'while' '(' Expression ')' Statement +ForStatement = 'for' '(' Expressionstatement Expression Expressionstatement ')' Statement Continue = 'continue' ';' Break = 'break' ';' Return = 'return' Expression? ';' diff --git a/test/solidityParser.cpp b/test/solidityParser.cpp index a9e2d531f..8268901be 100644 --- a/test/solidityParser.cpp +++ b/test/solidityParser.cpp @@ -49,6 +49,23 @@ ASTPointer parseText(std::string const& _source) BOOST_FAIL("No contract found in source."); return ASTPointer(); } + +ASTPointer parseTextExplainError(std::string const& _source) +{ + try + { + return parseText(_source); + } + catch (Exception const& exception) + { + // LTODO: Print the error in a kind of a better way? + // In absence of CompilerStack we can't use SourceReferenceFormatter + cout << "Exception while parsing: " << diagnostic_information(exception); + // rethrow to signal test failure + throw exception; + } +} + } @@ -357,6 +374,17 @@ BOOST_AUTO_TEST_CASE(while_loop) BOOST_CHECK_NO_THROW(parseText(text)); } +BOOST_AUTO_TEST_CASE(for_loop) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " for (uint256 i = 0; i < 10; i++;)\n" + " { uint256 x = i; break; continue; }\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextExplainError(text)); +} + BOOST_AUTO_TEST_CASE(if_statement) { char const* text = "contract test {\n" From e934aa0befb759ae3fafb545dd9398833a49bbe7 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Mon, 15 Dec 2014 17:54:29 +0100 Subject: [PATCH 17/57] new ForStatement parsing test and small grammar fix --- libsolidity/Parser.cpp | 1 - libsolidity/grammar.txt | 2 +- test/solidityParser.cpp | 16 ++++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 9941fbf43..d936cfc72 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -381,7 +381,6 @@ ASTPointer Parser::parseForStatement() ASTPointer conditionExpression = parseExpression(); expectToken(Token::SEMICOLON); ASTPointer loopExpression = parseExpressionStatement(); - expectToken(Token::SEMICOLON); expectToken(Token::RPAREN); ASTPointer body = parseStatement(); nodeFactory.setEndPositionFromNode(body); diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 99a590014..76e7bb6b6 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -20,7 +20,7 @@ Statement = IfStatement | WhileStatement | Block | IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? WhileStatement = 'while' '(' Expression ')' Statement -ForStatement = 'for' '(' Expressionstatement Expression Expressionstatement ')' Statement +ForStatement = 'for' '(' VardefOrExpressionstatement ';' Expression ';' Expressionstatement ')' Statement Continue = 'continue' ';' Break = 'break' ';' Return = 'return' Expression? ';' diff --git a/test/solidityParser.cpp b/test/solidityParser.cpp index 8268901be..53bd3f163 100644 --- a/test/solidityParser.cpp +++ b/test/solidityParser.cpp @@ -374,11 +374,23 @@ BOOST_AUTO_TEST_CASE(while_loop) BOOST_CHECK_NO_THROW(parseText(text)); } -BOOST_AUTO_TEST_CASE(for_loop) +BOOST_AUTO_TEST_CASE(for_loop_vardef_initexpr) { char const* text = "contract test {\n" " function fun(uint256 a) {\n" - " for (uint256 i = 0; i < 10; i++;)\n" + " for (uint256 i = 0; i < 10; i++)\n" + " { uint256 x = i; break; continue; }\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextExplainError(text)); +} + +BOOST_AUTO_TEST_CASE(for_loop_simple_initexpr) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 i =0;\n" + " for (i = 0; i < 10; i++)\n" " { uint256 x = i; break; continue; }\n" " }\n" "}\n"; From b2992bd659768b0ee8f33ea24bf4e308efea3891 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 09:51:51 +0100 Subject: [PATCH 18/57] Solidity ForStatements expressions are now optional --- libsolidity/AST_accept.h | 18 ++++++++++++------ libsolidity/Parser.cpp | 17 ++++++++++++++--- libsolidity/grammar.txt | 2 +- test/solidityParser.cpp | 12 ++++++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index ffef6f8b2..0e5a71b62 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -271,9 +271,12 @@ void ForStatement::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { - m_initExpression->accept(_visitor); - m_condExpression->accept(_visitor); - m_loopExpression->accept(_visitor); + if (m_initExpression) + m_initExpression->accept(_visitor); + if (m_condExpression) + m_condExpression->accept(_visitor); + if (m_loopExpression) + m_loopExpression->accept(_visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -283,9 +286,12 @@ void ForStatement::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { - m_initExpression->accept(_visitor); - m_condExpression->accept(_visitor); - m_loopExpression->accept(_visitor); + if (m_initExpression) + m_initExpression->accept(_visitor); + if (m_condExpression) + m_condExpression->accept(_visitor); + if (m_loopExpression) + m_loopExpression->accept(_visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index d936cfc72..598f5ad47 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -374,14 +374,25 @@ ASTPointer Parser::parseWhileStatement() ASTPointer Parser::parseForStatement() { ASTNodeFactory nodeFactory(*this); + ASTPointer initExpression; + ASTPointer conditionExpression; + ASTPointer loopExpression; expectToken(Token::FOR); expectToken(Token::LPAREN); - ASTPointer initExpression = parseVardefOrExprstatement(); + + // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RPAREN? + if (m_scanner->getCurrentToken() != Token::SEMICOLON) + initExpression = parseVardefOrExprstatement(); expectToken(Token::SEMICOLON); - ASTPointer conditionExpression = parseExpression(); + + if (m_scanner->getCurrentToken() != Token::SEMICOLON) + conditionExpression = parseExpression(); expectToken(Token::SEMICOLON); - ASTPointer loopExpression = parseExpressionStatement(); + + if (m_scanner->getCurrentToken() != Token::RPAREN) + loopExpression = parseExpressionStatement(); expectToken(Token::RPAREN); + ASTPointer body = parseStatement(); nodeFactory.setEndPositionFromNode(body); return nodeFactory.createNode(initExpression, diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 76e7bb6b6..7c0ac3a50 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -20,7 +20,7 @@ Statement = IfStatement | WhileStatement | Block | IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? WhileStatement = 'while' '(' Expression ')' Statement -ForStatement = 'for' '(' VardefOrExpressionstatement ';' Expression ';' Expressionstatement ')' Statement +ForStatement = 'for' '(' (VardefOrExpressionstatement)? ';' (Expression)? ';' (Expressionstatement)? ')' Statement Continue = 'continue' ';' Break = 'break' ';' Return = 'return' Expression? ';' diff --git a/test/solidityParser.cpp b/test/solidityParser.cpp index 53bd3f163..d57754b61 100644 --- a/test/solidityParser.cpp +++ b/test/solidityParser.cpp @@ -397,6 +397,18 @@ BOOST_AUTO_TEST_CASE(for_loop_simple_initexpr) BOOST_CHECK_NO_THROW(parseTextExplainError(text)); } +BOOST_AUTO_TEST_CASE(for_loop_simple_noexpr) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 i =0;\n" + " for (;;)\n" + " { uint256 x = i; break; continue; }\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextExplainError(text)); +} + BOOST_AUTO_TEST_CASE(if_statement) { char const* text = "contract test {\n" From 5eec2c5ac6d5a7f543a9eb33bdb3365653146fa1 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 13:20:50 +0100 Subject: [PATCH 19/57] ForStatement typecheck and initExpression is a Statement --- libsolidity/AST.cpp | 8 ++++++-- libsolidity/AST.h | 4 ++-- libsolidity/Parser.cpp | 4 ++-- libsolidity/Parser.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index c286c412b..4c042f2cf 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -132,8 +132,12 @@ void WhileStatement::checkTypeRequirements() void ForStatement::checkTypeRequirements() { - // LTODO - m_condExpression->expectType(BoolType()); + if (m_initExpression) + m_initExpression->checkTypeRequirements(); + if (m_condExpression) + m_condExpression->expectType(BoolType()); + if (m_loopExpression) + m_loopExpression->checkTypeRequirements(); m_body->checkTypeRequirements(); } diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 297b7f4e9..2fb6a2e32 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -513,7 +513,7 @@ class ForStatement: public BreakableStatement { public: ForStatement(Location const& _location, - ASTPointer const& _initExpression, + ASTPointer const& _initExpression, ASTPointer const& _conditionExpression, ASTPointer const& _loopExpression, ASTPointer const& _body): @@ -527,7 +527,7 @@ public: virtual void checkTypeRequirements() override; private: - ASTPointer m_initExpression; + ASTPointer m_initExpression; ASTPointer m_condExpression; ASTPointer m_loopExpression; ASTPointer m_body; diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 598f5ad47..2669ce209 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -374,7 +374,7 @@ ASTPointer Parser::parseWhileStatement() ASTPointer Parser::parseForStatement() { ASTNodeFactory nodeFactory(*this); - ASTPointer initExpression; + ASTPointer initExpression; ASTPointer conditionExpression; ASTPointer loopExpression; expectToken(Token::FOR); @@ -401,7 +401,7 @@ ASTPointer Parser::parseForStatement() body); } -ASTPointer Parser::parseVardefOrExprstatement() +ASTPointer Parser::parseVardefOrExprstatement() { if (peekVariableDefinition()) return parseVariableDefinition(); diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index 343a85c0e..cd3d5bab6 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -60,7 +60,7 @@ private: ASTPointer parseIfStatement(); ASTPointer parseWhileStatement(); ASTPointer parseForStatement(); - ASTPointer parseVardefOrExprstatement(); + ASTPointer parseVardefOrExprstatement(); ASTPointer parseVariableDefinition(); ASTPointer parseExpressionStatement(); ASTPointer parseExpression(); From 1f0346396ae307aa87e184f3ac8f0d7f9a97c832 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 14:46:17 +0100 Subject: [PATCH 20/57] Minor issues, grammar update, new ForStatement test --- libsolidity/AST.h | 7 +++++++ libsolidity/Parser.cpp | 9 +++------ libsolidity/Parser.h | 5 ++--- libsolidity/grammar.txt | 6 ++++-- test/solidityParser.cpp | 12 ++++++++++++ 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 2fb6a2e32..dcbca1ee8 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -509,6 +509,9 @@ private: ASTPointer m_body; }; +/** + * For loop statement + */ class ForStatement: public BreakableStatement { public: @@ -527,9 +530,13 @@ public: virtual void checkTypeRequirements() override; private: + /// For statement's initialization expresion. for(XXX; ; ). Can be empty ASTPointer m_initExpression; + /// For statement's condition expresion. for(; XXX ; ). Can be empty ASTPointer m_condExpression; + /// For statement's loop expresion. for(;;XXX). Can be empty ASTPointer m_loopExpression; + /// The body of the loop ASTPointer m_body; }; diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 2669ce209..732356751 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -330,10 +330,7 @@ ASTPointer Parser::parseStatement() } break; default: - if (peekVariableDefinition()) - statement = parseVariableDefinition(); - else // "ordinary" expression statement - statement = parseExpressionStatement(); + statement = parseVarDefOrExprStmt(); } expectToken(Token::SEMICOLON); return statement; @@ -382,7 +379,7 @@ ASTPointer Parser::parseForStatement() // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RPAREN? if (m_scanner->getCurrentToken() != Token::SEMICOLON) - initExpression = parseVardefOrExprstatement(); + initExpression = parseVarDefOrExprStmt(); expectToken(Token::SEMICOLON); if (m_scanner->getCurrentToken() != Token::SEMICOLON) @@ -401,7 +398,7 @@ ASTPointer Parser::parseForStatement() body); } -ASTPointer Parser::parseVardefOrExprstatement() +ASTPointer Parser::parseVarDefOrExprStmt() { if (peekVariableDefinition()) return parseVariableDefinition(); diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index cd3d5bab6..bf3a6beac 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -60,7 +60,7 @@ private: ASTPointer parseIfStatement(); ASTPointer parseWhileStatement(); ASTPointer parseForStatement(); - ASTPointer parseVardefOrExprstatement(); + ASTPointer parseVarDefOrExprStmt(); ASTPointer parseVariableDefinition(); ASTPointer parseExpressionStatement(); ASTPointer parseExpression(); @@ -74,8 +74,7 @@ private: ///@{ ///@name Helper functions - /// Peeks ahead in the scanner to determine if an expression statement - /// could be a variable definition + /// Peeks ahead in the scanner to determine if a variable definition is going to follow bool peekVariableDefinition(); /// If current token value is not _value, throw exception otherwise advance token. diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 7c0ac3a50..8c34997b9 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -16,11 +16,13 @@ Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' Block = '{' Statement* '}' Statement = IfStatement | WhileStatement | Block | - ( Continue | Break | Return | VariableDefinition | Expression ) ';' + ( Continue | Break | Return | VariableDefinition | ExpressionStatement ) ';' +ExpressionStatement = Expression IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? WhileStatement = 'while' '(' Expression ')' Statement -ForStatement = 'for' '(' (VardefOrExpressionstatement)? ';' (Expression)? ';' (Expressionstatement)? ')' Statement +VardefOrExprStmt = Variabledefinition | ExpressionStatement +ForStatement = 'for' '(' (VardefOrExprStmt)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement Continue = 'continue' ';' Break = 'break' ';' Return = 'return' Expression? ';' diff --git a/test/solidityParser.cpp b/test/solidityParser.cpp index d57754b61..f978cdd9b 100644 --- a/test/solidityParser.cpp +++ b/test/solidityParser.cpp @@ -409,6 +409,18 @@ BOOST_AUTO_TEST_CASE(for_loop_simple_noexpr) BOOST_CHECK_NO_THROW(parseTextExplainError(text)); } +BOOST_AUTO_TEST_CASE(for_loop_single_stmt_body) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 i =0;\n" + " for (i = 0; i < 10; i++)\n" + " continue;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextExplainError(text)); +} + BOOST_AUTO_TEST_CASE(if_statement) { char const* text = "contract test {\n" From 3c6e966160d828882d243ee37b52b23992891404 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 16:52:47 +0100 Subject: [PATCH 21/57] Solidity ForStatement Compiler part - Work in progress --- libsolidity/AST.h | 5 +++++ libsolidity/Compiler.cpp | 31 +++++++++++++++++++++++++++++-- test/solidityEndToEndTest.cpp | 22 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index dcbca1ee8..c83690443 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -529,6 +529,11 @@ public: virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; + Statement const* getInitializationExpression() const { return m_initExpression.get(); } + Expression const* getCondition() const { return m_condExpression.get(); } + ExpressionStatement const* getLoopExpression() const { return m_loopExpression.get(); } + Statement const& getBody() const { return *m_body; } + private: /// For statement's initialization expresion. for(XXX; ; ). Can be empty ASTPointer m_initExpression; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index a0cad5374..8c70b2718 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -289,8 +289,35 @@ bool Compiler::visit(WhileStatement const& _whileStatement) bool Compiler::visit(ForStatement const& _forStatement) { - // LTODO - (void) _forStatement; + eth::AssemblyItem loopStart = m_context.newTag(); + eth::AssemblyItem loopEnd = m_context.newTag(); + m_continueTags.push_back(loopStart); + m_breakTags.push_back(loopEnd); + + if (_forStatement.getInitializationExpression()) + _forStatement.getInitializationExpression()->accept(*this); + + m_context << loopStart; + + // if there is no terminating condition in for, default is to always be true + if (_forStatement.getCondition()) + { + compileExpression(*_forStatement.getCondition()); + m_context << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } + + _forStatement.getBody().accept(*this); + + // for's loop expression if existing + if (_forStatement.getLoopExpression()) + _forStatement.getLoopExpression()->accept(*this); + + m_context.appendJumpTo(loopStart); + m_context << loopEnd; + + m_continueTags.pop_back(); + m_breakTags.pop_back(); return false; } diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index b6f63aa72..32de9af46 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -176,6 +176,28 @@ BOOST_AUTO_TEST_CASE(nested_loops) testSolidityAgainstCppOnRange(0, nested_loops_cpp, 0, 12); } +BOOST_AUTO_TEST_CASE(for_loop) +{ + char const* sourceCode = "contract test {\n" + " function f(uint n) returns(uint nfac) {\n" + " nfac = 1;\n" + " for (var i = 2; i <= n; i++)\n" + " nfac *= 1;\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + + auto for_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + for (auto i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; + + testSolidityAgainstCppOnRange(0, for_loop_cpp, 0, 5); +} + BOOST_AUTO_TEST_CASE(calling_other_functions) { // note that the index of a function is its index in the sorted sequence of functions From 82117dc7dab431b2de6cfb73a6b17343ff71b57b Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 16 Dec 2014 17:35:00 +0100 Subject: [PATCH 22/57] Solidity: More tests for the ForStatement --- test/solidityEndToEndTest.cpp | 54 ++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 32de9af46..ecd3b63a3 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -182,7 +182,7 @@ BOOST_AUTO_TEST_CASE(for_loop) " function f(uint n) returns(uint nfac) {\n" " nfac = 1;\n" " for (var i = 2; i <= n; i++)\n" - " nfac *= 1;\n" + " nfac *= i;\n" " }\n" "}\n"; compileAndRun(sourceCode); @@ -198,6 +198,58 @@ BOOST_AUTO_TEST_CASE(for_loop) testSolidityAgainstCppOnRange(0, for_loop_cpp, 0, 5); } +BOOST_AUTO_TEST_CASE(for_loop_empty) +{ + char const* sourceCode = "contract test {\n" + " function f() returns(uint ret) {\n" + " ret = 1;\n" + " for (;;)\n" + " {\n" + " ret += 1;\n" + " if (ret >= 10) break;\n" + " }\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + + auto for_loop_empty_cpp = []() -> u256 + { + u256 ret = 1; + for (;;) + { + ret += 1; + if (ret >= 10) break; + } + return ret; + }; + + testSolidityAgainstCpp(0, for_loop_empty_cpp); +} + +BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr) +{ + char const* sourceCode = "contract test {\n" + " function f(uint n) returns(uint nfac) {\n" + " nfac = 1;\n" + " uint256 i;\n" + " for (i = 2; i <= n; i++)\n" + " nfac *= i;\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + + auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i; + for (i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; + + testSolidityAgainstCppOnRange(0, for_loop_simple_init_expr_cpp, 0, 5); +} + BOOST_AUTO_TEST_CASE(calling_other_functions) { // note that the index of a function is its index in the sorted sequence of functions From c9a032aad4f762d15f92135cd0135303de2ad43c Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Wed, 17 Dec 2014 00:17:38 +0100 Subject: [PATCH 23/57] better alignment of global string constants --- solc/CommandLineInterface.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index fd660a0ca..81dd25ec7 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -50,13 +50,13 @@ namespace solidity // LTODO: Maybe some argument class pairing names with // extensions and other attributes would be a better choice here? -static string const g_argAbiStr = "abi"; -static string const g_argAsmStr = "asm"; -static string const g_argAstStr = "ast"; -static string const g_argBinaryStr = "binary"; -static string const g_argOpcodesStr = "opcodes"; -static string const g_argNatspecDevStr = "natspec-dev"; -static string const g_argNatspecUserStr = "natspec-user"; +static string const g_argAbiStr = "abi"; +static string const g_argAsmStr = "asm"; +static string const g_argAstStr = "ast"; +static string const g_argBinaryStr = "binary"; +static string const g_argOpcodesStr = "opcodes"; +static string const g_argNatspecDevStr = "natspec-dev"; +static string const g_argNatspecUserStr = "natspec-user"; static void version() { From bcc263070b5851dfa432272d18b06ec32a6fb59c Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Wed, 17 Dec 2014 00:28:26 +0100 Subject: [PATCH 24/57] Moving comment to function body --- libsolidity/Parser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 732356751..e287319d2 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -596,12 +596,12 @@ vector> Parser::parseFunctionCallArguments() } -// 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 get a keyword that specifies a type name, or -// in the case of a user-defined type, we have two identifiers following each other. bool Parser::peekVariableDefinition() { + // 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 get a keyword that specifies a type name, or + // in the case of a user-defined type, we have two identifiers following each other. return (m_scanner->getCurrentToken() == Token::MAPPING || m_scanner->getCurrentToken() == Token::VAR || ((Token::isElementaryTypeName(m_scanner->getCurrentToken()) || From f9627b07dcb170a98f51c52da261ace8dfbebc67 Mon Sep 17 00:00:00 2001 From: ethdev Date: Wed, 17 Dec 2014 11:18:59 +0100 Subject: [PATCH 25/57] vs files added to gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ca18feb5b..c18d5a01d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,11 +18,14 @@ ipch *.sdf *.opensdf *.suo +*.vcxproj +*.vcxproj.filters +*.sln # VIM stuff *.swp -#Xcode stuff +# Xcode stuff build_xc *.user From 373dbcaa5ffec342a870070c35ee01cb2993455e Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Wed, 17 Dec 2014 11:48:43 +0100 Subject: [PATCH 26/57] clang requires explicit initialization of map --- test/solidityCompiler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index 29f61454a..1bdafc599 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -56,7 +56,11 @@ bytes compileContract(const string& _sourceCode) BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); Compiler compiler; - compiler.compileContract(*contract, {}, {}); + + // clang requires explicit initialization of map + // http://stackoverflow.com/questions/17264067/chosen-constructor-is-explicit-in-copy-initialization-error-with-clang-4-2 + compiler.compileContract(*contract, {}, map{}); + // debug //compiler.streamAssembly(cout); return compiler.getAssembledBytecode(); From edb6072cc9c8bbf9d54ee0596e24c03b1f778681 Mon Sep 17 00:00:00 2001 From: ethdev Date: Wed, 17 Dec 2014 11:57:32 +0100 Subject: [PATCH 27/57] windows fix for initializer-list cannot convert to dev::bytes --- test/solidityEndToEndTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index ecd3b63a3..1ac480da9 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -1108,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(constructor_arguments) function getName() returns (string3 ret) { return h.getName(); } })"; compileAndRun(sourceCode, 0, "Main"); - BOOST_REQUIRE(callContractFunction(0) == bytes({0x01})); + BOOST_REQUIRE(callContractFunction(0) == bytes{0x01}); BOOST_REQUIRE(callContractFunction(1) == bytes({'a', 'b', 'c'})); } From 2daf84f1bd122861bbc9a0b6e8f1401a6abc4ceb Mon Sep 17 00:00:00 2001 From: ethdev Date: Wed, 17 Dec 2014 12:19:10 +0100 Subject: [PATCH 28/57] initializer-list fix --- test/solidityEndToEndTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 1ac480da9..ad277a389 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -1108,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(constructor_arguments) function getName() returns (string3 ret) { return h.getName(); } })"; compileAndRun(sourceCode, 0, "Main"); - BOOST_REQUIRE(callContractFunction(0) == bytes{0x01}); + BOOST_REQUIRE(callContractFunction(0) == bytes({byte(0x01)})); BOOST_REQUIRE(callContractFunction(1) == bytes({'a', 'b', 'c'})); } From d09f6e8c9756d2bf21499b80056fcb9bfcc76b1f Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 17 Dec 2014 15:15:04 +0100 Subject: [PATCH 29/57] Whitespace change. --- libsolidity/CallGraph.cpp | 6 ++++-- libsolidity/CallGraph.h | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libsolidity/CallGraph.cpp b/libsolidity/CallGraph.cpp index 48666ca7f..b30afb612 100644 --- a/libsolidity/CallGraph.cpp +++ b/libsolidity/CallGraph.cpp @@ -26,8 +26,10 @@ using namespace std; -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ void CallGraph::addFunction(FunctionDefinition const& _function) { diff --git a/libsolidity/CallGraph.h b/libsolidity/CallGraph.h index 986e4dc4a..f7af64bff 100644 --- a/libsolidity/CallGraph.h +++ b/libsolidity/CallGraph.h @@ -25,8 +25,10 @@ #include #include -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ /** * Can be used to compute the graph of calls (or rather references) between functions of the same From 46664c44b368f8471d16f607d8ad6467ec5dbf89 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 17 Dec 2014 16:23:18 +0100 Subject: [PATCH 30/57] Assertions that throw InternalCompilerErrors. --- libsolidity/AST.cpp | 8 ++--- libsolidity/AST.h | 16 +++++----- libsolidity/CompilerContext.cpp | 20 +++++------- libsolidity/CompilerUtils.cpp | 6 ++-- libsolidity/ExpressionCompiler.cpp | 33 ++++++++----------- libsolidity/InterfaceHandler.cpp | 3 +- libsolidity/NameAndTypeResolver.cpp | 15 +++------ libsolidity/Scanner.cpp | 13 +++----- libsolidity/Token.h | 13 +++----- libsolidity/Types.cpp | 13 ++++---- libsolidity/Utils.h | 49 +++++++++++++++++++++++++++++ solc/CommandLineInterface.cpp | 3 +- 12 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 libsolidity/Utils.h diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 4c042f2cf..1fa6d8f6f 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -21,7 +21,7 @@ */ #include - +#include #include #include #include @@ -145,8 +145,7 @@ void Return::checkTypeRequirements() { if (!m_expression) return; - if (asserts(m_returnParameters)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not assigned.")); + solAssert(m_returnParameters, "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.")); @@ -336,8 +335,7 @@ void IndexAccess::checkTypeRequirements() void Identifier::checkTypeRequirements() { - if (asserts(m_referencedDeclaration)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier not resolved.")); + solAssert(m_referencedDeclaration, "Identifier not resolved."); VariableDeclaration const* variable = dynamic_cast(m_referencedDeclaration); if (variable) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index c83690443..b7c3dc6c0 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -364,7 +365,7 @@ public: explicit ElementaryTypeName(Location const& _location, Token::Value _type): TypeName(_location), m_type(_type) { - if (asserts(Token::isElementaryTypeName(_type))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isElementaryTypeName(_type), ""); } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -575,8 +576,7 @@ public: void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } ParameterList const& getFunctionReturnParameters() const { - if (asserts(m_returnParameters)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(m_returnParameters, ""); return *m_returnParameters; } Expression const* getExpression() const { return m_expression.get(); } @@ -682,7 +682,7 @@ public: Expression(_location), m_leftHandSide(_leftHandSide), m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) { - if (asserts(Token::isAssignmentOp(_assignmentOperator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isAssignmentOp(_assignmentOperator), ""); } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -710,7 +710,7 @@ public: Expression(_location), m_operator(_operator), m_subExpression(_subExpression), m_isPrefix(_isPrefix) { - if (asserts(Token::isUnaryOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isUnaryOp(_operator), ""); } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -737,7 +737,7 @@ public: Token::Value _operator, ASTPointer const& _right): Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) { - if (asserts(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator), ""); } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -799,7 +799,7 @@ public: std::vector> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } /// Returns the referenced contract. Can only be called after type checking. - ContractDefinition const* getContract() const { if (asserts(m_contract)) BOOST_THROW_EXCEPTION(InternalCompilerError()); else return m_contract; } + ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; } private: ASTPointer m_contractName; @@ -894,7 +894,7 @@ public: ElementaryTypeNameExpression(Location const& _location, Token::Value _typeToken): PrimaryExpression(_location), m_typeToken(_typeToken) { - if (asserts(Token::isElementaryTypeName(_typeToken))) BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isElementaryTypeName(_typeToken), ""); } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index 47401436a..18357bf0c 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -27,8 +27,10 @@ using namespace std; -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration) { @@ -65,8 +67,7 @@ void CompilerContext::addFunction(FunctionDefinition const& _function) bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const { auto ret = m_compiledContracts.find(&_contract); - if (asserts(ret != m_compiledContracts.end())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Compiled contract not found.")); + solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); return *ret->second; } @@ -78,16 +79,14 @@ bool CompilerContext::isLocalVariable(Declaration const* _declaration) const eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const { auto res = m_functionEntryLabels.find(&_function); - if (asserts(res != m_functionEntryLabels.end())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function entry label not found.")); + solAssert(res != m_functionEntryLabels.end(), "Function entry label not found."); return res->second.tag(); } unsigned CompilerContext::getBaseStackOffsetOfVariable(Declaration const& _declaration) const { auto res = m_localVariables.find(&_declaration); - if (asserts(res != m_localVariables.end())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found on stack.")); + solAssert(res != m_localVariables.end(), "Variable not found on stack."); return m_localVariablesSize - res->second - 1; } @@ -99,12 +98,9 @@ unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const { auto it = m_stateVariables.find(&_declaration); - if (it == m_stateVariables.end()) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found in storage.")); + solAssert(it != m_stateVariables.end(), "Variable not found in storage."); return it->second; } - - } } diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 9f474896e..680e9190b 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -39,8 +39,7 @@ void CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _left return; } eth::Instruction load = _fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD; - if (asserts(_bytes <= 32)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory load of more than 32 bytes requested.")); + solAssert(_bytes <= 32, "Memory load of more than 32 bytes requested."); if (_bytes == 32) m_context << u256(_offset) << load; else @@ -63,8 +62,7 @@ void CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftA m_context << eth::Instruction::POP; return; } - if (asserts(_bytes <= 32)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory store of more than 32 bytes requested.")); + solAssert(_bytes <= 32, "Memory store of more than 32 bytes requested."); if (_bytes != 32 && !_leftAligned) // shift the value accordingly before storing m_context << (u256(1) << ((32 - _bytes) * 8)) << eth::Instruction::MUL; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 743503bbf..f58c157d9 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -30,8 +30,10 @@ using namespace std; -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression const& _expression, bool _optimize) { @@ -51,8 +53,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) _assignment.getRightHandSide().accept(*this); appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); _assignment.getLeftHandSide().accept(*this); - if (asserts(m_currentLValue.isValid())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved.")); + solAssert(m_currentLValue.isValid(), "LValue not retrieved."); Token::Value op = _assignment.getAssignmentOperator(); if (op != Token::ASSIGN) // compound assignment @@ -84,8 +85,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) break; case Token::DELETE: // delete // @todo semantics change for complex types - if (asserts(m_currentLValue.isValid())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved.")); + solAssert(m_currentLValue.isValid(), "LValue not retrieved."); m_context << u256(0); if (m_currentLValue.storesReferenceOnStack()) @@ -95,8 +95,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) break; case Token::INC: // ++ (pre- or postfix) case Token::DEC: // -- (pre- or postfix) - if (asserts(m_currentLValue.isValid())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved.")); + solAssert(m_currentLValue.isValid(), "LValue not retrieved."); m_currentLValue.retrieveValue(_unaryOperation); if (!_unaryOperation.isPrefixOperation()) { @@ -179,8 +178,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (_functionCall.isTypeConversion()) { //@todo struct construction - if (asserts(_functionCall.getArguments().size() == 1)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(_functionCall.getArguments().size() == 1, ""); Expression const& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); if (firstArgument.getType()->getCategory() == Type::Category::CONTRACT && @@ -195,8 +193,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { FunctionType const& function = dynamic_cast(*_functionCall.getExpression().getType()); vector> arguments = _functionCall.getArguments(); - if (asserts(arguments.size() == function.getParameterTypes().size())) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(arguments.size() == function.getParameterTypes().size(), ""); switch (function.getLocation()) { @@ -282,12 +279,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) bool ExpressionCompiler::visit(NewExpression const& _newExpression) { ContractType const* type = dynamic_cast(_newExpression.getType().get()); - if (asserts(type)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(type, ""); TypePointers const& types = type->getConstructorType()->getParameterTypes(); vector> arguments = _newExpression.getArguments(); - if (asserts(arguments.size() == types.size())) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(arguments.size() == types.size(), ""); // copy the contracts code into memory bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract()); @@ -439,8 +434,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal) void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation) { Token::Value const op = _binaryOperation.getOperator(); - if (asserts(op == Token::OR || op == Token::AND)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(op == Token::OR || op == Token::AND, ""); _binaryOperation.getLeftExpression().accept(*this); m_context << eth::Instruction::DUP1; @@ -592,8 +586,7 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio vector> const& _arguments, FunctionCallOptions const& _options) { - if (asserts(_arguments.size() == _functionType.getParameterTypes().size())) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(_arguments.size() == _functionType.getParameterTypes().size(), ""); unsigned dataOffset = _options.bare ? 0 : 1; // reserve one byte for the function index for (unsigned i = 0; i < _arguments.size(); ++i) diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index 1310f504a..c734fa38c 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -198,8 +198,7 @@ std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::con std::string::const_iterator _end) { // Should never be called with an empty vector - if (asserts(!m_params.empty())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Tried to append to empty parameter")); + solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); auto pair = m_params.back(); pair.second += " "; diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 80732615c..3774537d1 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -70,8 +70,7 @@ void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) { m_scopes[nullptr].registerDeclaration(_declaration, true); - if (asserts(_declaration.getScope() == nullptr)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Updated declaration outside global scope.")); + solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope."); } Declaration const* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const @@ -133,8 +132,7 @@ 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. - if (asserts(m_currentFunction)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable definition without function.")); + solAssert(m_currentFunction, "Variable definition without function."); m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration()); } @@ -149,15 +147,13 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara map::iterator iter; bool newlyAdded; tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, DeclarationContainer(m_currentScope, &m_scopes[m_currentScope])); - if (asserts(newlyAdded)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to add new scope.")); + solAssert(newlyAdded, "Unable to add new scope."); m_currentScope = &_declaration; } void DeclarationRegistrationHelper::closeCurrentScope() { - if (asserts(m_currentScope)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Closed non-existing scope.")); + solAssert(m_currentScope, "Closed non-existing scope."); m_currentScope = m_scopes[m_currentScope].getEnclosingDeclaration(); } @@ -196,8 +192,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool ReferencesResolver::visit(Return& _return) { - if (asserts(m_returnParameters)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not set.")); + solAssert(m_returnParameters, "Return parameters not set."); _return.setFunctionReturnParameters(*m_returnParameters); return true; } diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 08bf744d4..1a21149a1 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -52,6 +52,7 @@ #include #include +#include #include using namespace std; @@ -249,8 +250,7 @@ Token::Value Scanner::scanDocumentationComment() Token::Value Scanner::skipMultiLineComment() { - if (asserts(m_char == '*')) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(m_char == '*', ""); advance(); while (!isSourcePastEndOfInput()) { @@ -597,8 +597,7 @@ Token::Value Scanner::scanNumber(char _charSeen) // scan exponent, if any if (m_char == 'e' || m_char == 'E') { - if (asserts(kind != HEX)) // 'e'/'E' must be scanned as part of the hex number - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); if (kind != DECIMAL) return Token::ILLEGAL; // scan exponent @@ -639,8 +638,7 @@ static Token::Value keywordOrIdentifierToken(string const& _input) Token::Value Scanner::scanIdentifierOrKeyword() { - if (asserts(isIdentifierStart(m_char))) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(isIdentifierStart(m_char), ""); LiteralScope literal(this, LITERAL_TYPE_STRING); addLiteralCharAndAdvance(); // Scan the rest of the identifier characters. @@ -662,8 +660,7 @@ char CharStream::advanceAndGet(size_t _chars) char CharStream::rollback(size_t _amount) { - if (asserts(m_pos >= _amount)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(m_pos >= _amount, ""); m_pos -= _amount; return get(); } diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 22378ae07..32656096a 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -44,6 +44,7 @@ #include #include +#include #include namespace dev @@ -344,8 +345,7 @@ public: // (e.g. "LT" for the token LT). static char const* getName(Value tok) { - if (asserts(tok < NUM_TOKENS)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(tok < NUM_TOKENS, ""); return m_name[tok]; } @@ -360,8 +360,7 @@ public: static Value AssignmentToBinaryOp(Value op) { - if (asserts(isAssignmentOp(op) && op != ASSIGN)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(isAssignmentOp(op) && op != ASSIGN, ""); return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR)); } @@ -375,8 +374,7 @@ public: // have a (unique) string (e.g. an IDENTIFIER). static char const* toString(Value tok) { - if (asserts(tok < NUM_TOKENS)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(tok < NUM_TOKENS, ""); return m_string[tok]; } @@ -384,8 +382,7 @@ public: // operators; returns 0 otherwise. static int precedence(Value tok) { - if (asserts(tok < NUM_TOKENS)) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(tok < NUM_TOKENS, ""); return m_precedence[tok]; } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index eba11cb0d..71319c3ac 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -34,8 +35,7 @@ namespace solidity shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) { - if (asserts(Token::isElementaryTypeName(_typeToken))) - BOOST_THROW_EXCEPTION(InternalCompilerError()); + solAssert(Token::isElementaryTypeName(_typeToken), ""); if (Token::INT <= _typeToken && _typeToken <= Token::HASH256) { @@ -120,8 +120,8 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): { if (isAddress()) m_bits = 160; - if (asserts(m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid bit number for integer type: " + dev::toString(_bits))); + solAssert(m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0, + "Invalid bit number for integer type: " + dev::toString(_bits)); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -215,9 +215,8 @@ shared_ptr StaticStringType::smallestTypeForLiteral(string con StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes) { - if (asserts(m_bytes >= 0 && m_bytes <= 32)) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid byte number for static string type: " + - dev::toString(m_bytes))); + solAssert(m_bytes >= 0 && m_bytes <= 32, + "Invalid byte number for static string type: " + dev::toString(m_bytes)); } bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const diff --git a/libsolidity/Utils.h b/libsolidity/Utils.h new file mode 100644 index 000000000..8d6a3ab08 --- /dev/null +++ b/libsolidity/Utils.h @@ -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 + * Solidity Utilities. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ + +/// Assertion that throws an InternalCompilerError containing the given description if it is not met. +#define solAssert(CONDITION, DESCRIPTION) \ + ::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC) + +inline void solAssertAux(bool _condition, std::string const& _errorDescription, unsigned _line, + char const* _file, char const* _function) +{ + if (!_condition) + ::boost::throw_exception( InternalCompilerError() + << errinfo_comment(_errorDescription) + << ::boost::throw_function(_function) + << ::boost::throw_file(_file) + << ::boost::throw_line(_line)); +} + +} +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 81dd25ec7..399f52436 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -311,7 +311,8 @@ bool CommandLineInterface::processInput() } catch (InternalCompilerError const& exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Internal compiler error", m_compiler); + cerr << "Internal compiler error during compilation:" << endl + << boost::diagnostic_information(exception); return false; } catch (Exception const& exception) From 0f75aaf6e63fd1c22ddc0eb828af8874bc2cd018 Mon Sep 17 00:00:00 2001 From: ethdev Date: Wed, 17 Dec 2014 16:25:24 +0100 Subject: [PATCH 31/57] cpack nsis windows installer --- CMakeLists.txt | 26 +++++++++++++++++++++++++- cmake/EthExecutableHelper.cmake | 15 ++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02e3a1a05..1293b4427 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,4 +156,28 @@ add_test(NAME alltests WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testet #unset(TARGET_PLATFORM CACHE) - +if (WIN32) + # packaging stuff + include(InstallRequiredSystemLibraries) + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "ethereum") + set(CPACK_PACKAGE_VENDOR "ethereum.org") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_VERSION "0.7") + set(CPACK_GENERATOR "NSIS") + + # our stuff + set(CPACK_COMPONENT_ALETHZERO_GROUP "Applications") + set(CPACK_COMPONENT_THIRD_GROUP "Applications") + set(CPACK_COMPONENT_MIX_GROUP "Applications") + set(CPACK_COMPONENTS_ALL alethzero third mix) + + # nsis specific stuff + set(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY} ethereum") + set(CPACK_NSIS_HELP_LINK "https://github.com/ethereum/cpp-ethereum") + set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/ethereum/cpp-ethereum") + set(CPACK_NSIS_CONTACT "ethereum.org") + set(CPACK_NSIS_MODIFY_PATH ON) + + include(CPack) +endif (WIN32) diff --git a/cmake/EthExecutableHelper.cmake b/cmake/EthExecutableHelper.cmake index 9d273522e..0ec1e10d4 100644 --- a/cmake/EthExecutableHelper.cmake +++ b/cmake/EthExecutableHelper.cmake @@ -108,7 +108,20 @@ macro(eth_install_executable EXECUTABLE) $/platforms ) - install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin) + install( FILES ${DLLS} + DESTINATION bin + COMPONENT ${EXECUTABLE} + ) + + install( DIRECTORY ${ETH_DEPENDENCY_INSTALL_DIR}/plugins/platforms + DESTINATION bin + COMPONENT ${EXECUTABLE} + ) + + install( TARGETS ${EXECUTABLE} RUNTIME + DESTINATION bin + COMPONENT ${EXECUTABLE} + ) else() install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin) From 8468d896a73b5ea44c94ca92afe78189044d8024 Mon Sep 17 00:00:00 2001 From: ethdev Date: Wed, 17 Dec 2014 16:47:22 +0100 Subject: [PATCH 32/57] fixed msvc not expanding macros correctly --- test/CMakeLists.txt | 1 + test/solidityCompiler.cpp | 7 ++++--- test/solidityExpressionCompiler.cpp | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f862de24c..7cedc117b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_policy(SET CMP0015 NEW) aux_source_directory(. SRC_LIST) list(REMOVE_ITEM SRC_LIST "./createRandomTest.cpp") +include_directories(${Boost_INCLUDE_DIRS}) include_directories(${CRYPTOPP_INCLUDE_DIRS}) include_directories(${JSONCPP_INCLUDE_DIRS}) include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index be2c83838..385a3e577 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -52,17 +52,18 @@ bytes compileContract(const string& _sourceCode) resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) + { BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + } for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) + { BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract)); + } for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { Compiler compiler; - - // clang requires explicit initialization of map - // http://stackoverflow.com/questions/17264067/chosen-constructor-is-explicit-in-copy-initialization-error-with-clang-4-2 compiler.compileContract(*contract, {}, map{}); // debug diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index 2bdc38421..9c375418e 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -95,10 +95,14 @@ bytes compileFirstExpression(const string& _sourceCode, vector> _ resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) + { BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + } for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) + { BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract)); + } for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { From 83b218908b8220aa47a3bb1ea8b2e3cdc60474b7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 17 Dec 2014 17:08:57 +0100 Subject: [PATCH 33/57] Format catch arguments as function parameters. --- solc/CommandLineInterface.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 399f52436..2a79521ef 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -230,9 +230,9 @@ bool CommandLineInterface::parseArguments(int argc, char** argv) { po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args); } - catch (po::error const& exception) + catch (po::error const& _exception) { - cout << exception.what() << endl; + cout << _exception.what() << endl; return false; } po::notify(m_args); @@ -289,35 +289,35 @@ bool CommandLineInterface::processInput() // TODO: Perhaps we should not compile unless requested m_compiler.compile(m_args["optimize"].as()); } - catch (ParserError const& exception) + catch (ParserError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Parser error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", m_compiler); return false; } - catch (DeclarationError const& exception) + catch (DeclarationError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Declaration error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", m_compiler); return false; } - catch (TypeError const& exception) + catch (TypeError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Type error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", m_compiler); return false; } - catch (CompilerError const& exception) + catch (CompilerError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Compiler error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", m_compiler); return false; } - catch (InternalCompilerError const& exception) + catch (InternalCompilerError const& _exception) { cerr << "Internal compiler error during compilation:" << endl - << boost::diagnostic_information(exception); + << boost::diagnostic_information(_exception); return false; } - catch (Exception const& exception) + catch (Exception const& _exception) { - cerr << "Exception during compilation: " << boost::diagnostic_information(exception) << endl; + cerr << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl; return false; } catch (...) From 68b9c67e9577b80cba4b8900da36e4898198bb76 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 17 Dec 2014 18:33:55 +0100 Subject: [PATCH 34/57] Return the last contract by default. --- libsolidity/CompilerStack.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 1242c0aba..9fdc88baa 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -234,9 +234,13 @@ CompilerStack::Contract const& CompilerStack::getContract(string const& _contrac { if (m_contracts.empty()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + string contractName = _contractName; if (_contractName.empty()) - return m_contracts.begin()->second; - auto it = m_contracts.find(_contractName); + // try to find the "last contract" + for (ASTPointer const& node: m_sourceOrder.back()->ast->getNodes()) + if (auto contract = dynamic_cast(node.get())) + contractName = contract->getName(); + auto it = m_contracts.find(contractName); if (it == m_contracts.end()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); return it->second; From 3f865244df4d145d8b12af9a0c95859c2608cd5a Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 17 Dec 2014 18:52:53 +0100 Subject: [PATCH 35/57] Fix warning about sequence points. --- libdevcrypto/Common.h | 2 +- libethereum/Transaction.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index 02d52f495..021f97de8 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -47,7 +47,7 @@ struct SignatureStruct { SignatureStruct() {} SignatureStruct(Signature const& _s) { *(h520*)this = _s; } - SignatureStruct(h256 _r, h256 _s, byte _v): r(_r), s(_s), v(_v) {} + SignatureStruct(h256 const& _r, h256 const& _s, byte _v): r(_r), s(_s), v(_v) {} operator Signature() const { return *(h520 const*)this; } /// @returns true if r,s,v values are valid, otherwise false diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 1edfe3927..0c1173134 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -43,7 +43,10 @@ Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) m_receiveAddress = rlp[field = 3].toHash
(); m_value = rlp[field = 4].toInt(); m_data = rlp[field = 5].toBytes(); - m_vrs = SignatureStruct{ rlp[field = 7].toInt(), rlp[field = 8].toInt(), byte(rlp[field = 6].toInt() - 27) }; + h256 r = rlp[field = 7].toInt(); + h256 s = rlp[field = 8].toInt(); + byte v = rlp[field = 6].toInt() - 27; + m_vrs = SignatureStruct{ r, s, v }; if (_checkSender) m_sender = sender(); } From 7377e9388294b918a917691218f0b659a0d74edf Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 17 Dec 2014 19:20:36 +0100 Subject: [PATCH 36/57] Bugfix: Data reference tags can be longer than jump tags. --- libevmcore/Assembly.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index 74bd015a6..5efbb9487 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -478,7 +478,6 @@ bytes Assembly::assemble() const bytes ret; unsigned totalBytes = bytesRequired(); - ret.reserve(totalBytes); vector tagPos(m_usedTags); map tagRef; multimap dataRef; @@ -489,6 +488,12 @@ bytes Assembly::assemble() const for (auto const& i: m_subs) m_data[i.first] = i.second.assemble(); + unsigned bytesRequiredIncludingData = bytesRequired(); + unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); + byte dataRefPush = (byte)Instruction::PUSH1 - 1 + bytesPerDataRef; + ret.reserve(bytesRequiredIncludingData); + // m_data must not change from here on + for (AssemblyItem const& i: m_items) switch (i.m_type) { @@ -526,9 +531,9 @@ bytes Assembly::assemble() const } case PushData: case PushSub: { - ret.push_back(tagPush); + ret.push_back(dataRefPush); dataRef.insert(make_pair((h256)i.m_data, ret.size())); - ret.resize(ret.size() + bytesPerTag); + ret.resize(ret.size() + bytesPerDataRef); break; } case PushSubSize: @@ -542,10 +547,12 @@ bytes Assembly::assemble() const break; } case PushProgramSize: - ret.push_back(tagPush); + { + ret.push_back(dataRefPush); sizeRef.push_back(ret.size()); - ret.resize(ret.size() + bytesPerTag); + ret.resize(ret.size() + bytesPerDataRef); break; + } case Tag: tagPos[(unsigned)i.m_data] = ret.size(); ret.push_back((byte)Instruction::JUMPDEST); @@ -573,7 +580,7 @@ bytes Assembly::assemble() const { for (auto it = its.first; it != its.second; ++it) { - bytesRef r(ret.data() + it->second, bytesPerTag); + bytesRef r(ret.data() + it->second, bytesPerDataRef); toBigEndian(ret.size(), r); } for (auto b: i.second) @@ -583,7 +590,7 @@ bytes Assembly::assemble() const } for (unsigned pos: sizeRef) { - bytesRef r(ret.data() + pos, bytesPerTag); + bytesRef r(ret.data() + pos, bytesPerDataRef); toBigEndian(ret.size(), r); } return ret; From 857dca6c8d4b9c35c7cb5229906c0ecf9004a069 Mon Sep 17 00:00:00 2001 From: CJentzsch Date: Thu, 18 Dec 2014 09:06:06 +0100 Subject: [PATCH 37/57] refund tests --- test/stRefundTestFiller.json | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/stRefundTestFiller.json diff --git a/test/stRefundTestFiller.json b/test/stRefundTestFiller.json new file mode 100644 index 000000000..3745c8d81 --- /dev/null +++ b/test/stRefundTestFiller.json @@ -0,0 +1,37 @@ +{ + "refund0" : { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 1 ]] 0 }", + "storage" : { + "0x01" : "0x01" + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "1000", + "nonce" : 0, + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "850", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + } +} From 87721356e1e5c44375db9331034312152c8f4f36 Mon Sep 17 00:00:00 2001 From: CJentzsch Date: Thu, 18 Dec 2014 09:06:51 +0100 Subject: [PATCH 38/57] refund test inclusion --- test/state.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/state.cpp b/test/state.cpp index f183c4813..5bdfb419b 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -140,11 +140,22 @@ BOOST_AUTO_TEST_CASE(stInitCodeTest) dev::test::executeTests("stInitCodeTest", "/StateTests", dev::test::doStateTests); } +BOOST_AUTO_TEST_CASE(stTransactionTest) +{ + dev::test::executeTests("stTransactionTest", "/StateTests", dev::test::doStateTests); +} + BOOST_AUTO_TEST_CASE(stSpecialTest) { dev::test::executeTests("stSpecialTest", "/StateTests", dev::test::doStateTests); } +BOOST_AUTO_TEST_CASE(stRefundTest) +{ + dev::test::executeTests("stRefundTest", "/StateTests", dev::test::doStateTests); +} + + BOOST_AUTO_TEST_CASE(stCreateTest) { for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i) From 0bf7eb0670101fc2ba74e6ae276299e7bc197579 Mon Sep 17 00:00:00 2001 From: CJentzsch Date: Thu, 18 Dec 2014 09:32:24 +0100 Subject: [PATCH 39/57] more refund tests --- test/stRefundTestFiller.json | 112 ++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/test/stRefundTestFiller.json b/test/stRefundTestFiller.json index 3745c8d81..4060bbb72 100644 --- a/test/stRefundTestFiller.json +++ b/test/stRefundTestFiller.json @@ -1,5 +1,5 @@ { - "refund0" : { + "refund_getEtherBack" : { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -33,5 +33,113 @@ "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", "data" : "" } - } + }, + + "refund_changeNonZeroStorage" : { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 1 ]] 23 }", + "storage" : { + "0x01" : "0x01" + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "1000", + "nonce" : 0, + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "850", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "refund_OOG" : { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 1 ]] 0 }", + "storage" : { + "0x01" : "0x01" + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "500", + "nonce" : 0, + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "500", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "refund_NoOOG_1" : { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 1 ]] 0 }", + "storage" : { + "0x01" : "0x01" + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "502", + "nonce" : 0, + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "502", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + } } From 23c46dd468f087881c89ee85ca1567f60d57ea5f Mon Sep 17 00:00:00 2001 From: CJentzsch Date: Thu, 18 Dec 2014 09:50:45 +0100 Subject: [PATCH 40/57] style --- test/state.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/state.cpp b/test/state.cpp index 5bdfb419b..d8a3dd283 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -155,7 +155,6 @@ BOOST_AUTO_TEST_CASE(stRefundTest) dev::test::executeTests("stRefundTest", "/StateTests", dev::test::doStateTests); } - BOOST_AUTO_TEST_CASE(stCreateTest) { for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i) From 80368b870b55eac441e43a12b558361f04a5c1e6 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Wed, 17 Dec 2014 17:47:34 +0100 Subject: [PATCH 41/57] windows icon --- CMakeLists.txt | 4 ++++ alethzero/alethzero.ico | Bin 0 -> 370070 bytes 2 files changed, 4 insertions(+) create mode 100644 alethzero/alethzero.ico diff --git a/CMakeLists.txt b/CMakeLists.txt index 1293b4427..5763c9b85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,8 @@ if (WIN32) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION "0.7") set(CPACK_GENERATOR "NSIS") + # seems to be not working + # set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.bmp") # our stuff set(CPACK_COMPONENT_ALETHZERO_GROUP "Applications") @@ -178,6 +180,8 @@ if (WIN32) set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/ethereum/cpp-ethereum") set(CPACK_NSIS_CONTACT "ethereum.org") set(CPACK_NSIS_MODIFY_PATH ON) + set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.ico") + set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.ico") include(CPack) endif (WIN32) diff --git a/alethzero/alethzero.ico b/alethzero/alethzero.ico new file mode 100644 index 0000000000000000000000000000000000000000..acee27751646c7054bd617e0be34ed1c4e55a1f3 GIT binary patch literal 370070 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 Date: Thu, 18 Dec 2014 14:39:16 +0100 Subject: [PATCH 42/57] Possibility to replace source and obtain ContractDefinition by name. --- libsolidity/CompilerStack.cpp | 12 ++++++++---- libsolidity/CompilerStack.h | 9 +++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 9fdc88baa..79716fdec 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -36,13 +36,12 @@ namespace dev namespace solidity { -void CompilerStack::addSource(string const& _name, string const& _content) +bool CompilerStack::addSource(string const& _name, string const& _content) { - if (m_sources.count(_name)) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Source by given name already exists.")); - + bool existed = m_sources.count(_name); reset(true); m_sources[_name].scanner = make_shared(CharStream(_content), _name); + return existed; } void CompilerStack::setSource(string const& _sourceCode) @@ -181,6 +180,11 @@ SourceUnit const& CompilerStack::getAST(string const& _sourceName) const return *getSource(_sourceName).ast; } +ContractDefinition const& CompilerStack::getContractDefinition(string const& _contractName) const +{ + return *getContract(_contractName).contract; +} + bytes CompilerStack::staticCompile(std::string const& _sourceCode, bool _optimize) { CompilerStack stack; diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 5ad6f0a60..358c8fb77 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -57,7 +57,8 @@ public: CompilerStack(): m_parseSuccessful(false) {} /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. - void addSource(std::string const& _name, std::string const& _content); + /// @returns true if a source object by the name already existed and was replaced. + bool addSource(std::string const& _name, std::string const& _content); void setSource(std::string const& _sourceCode); /// Parses all source units that were added void parse(); @@ -86,9 +87,13 @@ public: /// Can be one of 3 types defined at @c DocumentationType std::string const& getJsonDocumentation(std::string const& _contractName, DocumentationType _type) const; - /// Returns the previously used scanner, useful for counting lines during error reporting. + /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& getScanner(std::string const& _sourceName = "") const; + /// @returns the parsed source unit with the supplied name. SourceUnit const& getAST(std::string const& _sourceName = "") const; + /// @returns the parsed contract with the supplied name. Throws an exception if the contract + /// does not exist. + ContractDefinition const& getContractDefinition(std::string const& _contractName) const; /// 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. From 6d48f3e98764fff270397fc00d37f9c6328294fd Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Wed, 17 Dec 2014 18:53:18 +0100 Subject: [PATCH 43/57] Work in progress for /** ... */ natspec comments - Work in progress on the scanner for recognizing the second type of doxygen comments for Natspec. --- libsolidity/Scanner.cpp | 58 +++++++++++++++++++++++++++++++++++++--- libsolidity/Scanner.h | 3 ++- test/solidityScanner.cpp | 7 +++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 1a21149a1..2e9b7b454 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -219,7 +219,7 @@ Token::Value Scanner::skipSingleLineComment() return Token::WHITESPACE; } -Token::Value Scanner::scanDocumentationComment() +Token::Value Scanner::scanSingleLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); advance(); //consume the last '/' @@ -250,7 +250,6 @@ Token::Value Scanner::scanDocumentationComment() Token::Value Scanner::skipMultiLineComment() { - solAssert(m_char == '*', ""); advance(); while (!isSourcePastEndOfInput()) { @@ -270,6 +269,43 @@ Token::Value Scanner::skipMultiLineComment() return Token::ILLEGAL; } +Token::Value Scanner::scanMultiLineDocComment() +{ + LiteralScope literal(this, LITERAL_TYPE_COMMENT); + bool endFound = false; + + advance(); //consume the last '*' + while (!isSourcePastEndOfInput()) + { + // skip starting '*' in multiine comments + if (isLineTerminator(m_char)) + { + skipWhitespace(); + if (!m_source.isPastEndOfInput(2) && m_source.get(1) == '*' && m_source.get(2) != '/') + { + addCommentLiteralChar('\n'); + m_char = m_source.advanceAndGet(3); + } + else + addCommentLiteralChar('\n'); + } + + if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') + { + m_source.advanceAndGet(2); + endFound = true; + break; + } + addCommentLiteralChar(m_char); + advance(); + } + literal.complete(); + if (!endFound) + return Token::ILLEGAL; + else + return Token::COMMENT_LITERAL; +} + void Scanner::scanToken() { m_nextToken.literal.clear(); @@ -381,7 +417,7 @@ void Scanner::scanToken() { Token::Value comment; m_nextSkippedComment.location.start = getSourcePos(); - comment = scanDocumentationComment(); + comment = scanSingleLineDocComment(); m_nextSkippedComment.location.end = getSourcePos(); m_nextSkippedComment.token = comment; token = Token::WHITESPACE; @@ -390,7 +426,21 @@ void Scanner::scanToken() token = skipSingleLineComment(); } else if (m_char == '*') - token = skipMultiLineComment(); + { + if (!advance()) /* slash star comment before EOS */ + token = Token::WHITESPACE; + else if (m_char == '*') + { + Token::Value comment; + m_nextSkippedComment.location.start = getSourcePos(); + comment = scanMultiLineDocComment(); + m_nextSkippedComment.location.end = getSourcePos(); + m_nextSkippedComment.token = comment; + token = Token::WHITESPACE; + } + else + token = skipMultiLineComment(); + } else if (m_char == '=') token = selectToken(Token::ASSIGN_DIV); else diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h index 18b1f5d3a..7f1b18352 100644 --- a/libsolidity/Scanner.h +++ b/libsolidity/Scanner.h @@ -190,7 +190,8 @@ private: Token::Value scanIdentifierOrKeyword(); Token::Value scanString(); - Token::Value scanDocumentationComment(); + Token::Value scanSingleLineDocComment(); + Token::Value scanMultiLineDocComment(); /// Scans an escape-sequence which is part of a string and adds the /// decoded character to the current literal. Returns true if a pattern diff --git a/test/solidityScanner.cpp b/test/solidityScanner.cpp index 573affe6d..2b8c7c458 100644 --- a/test/solidityScanner.cpp +++ b/test/solidityScanner.cpp @@ -160,6 +160,13 @@ BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin) BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user"); } +BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed_begin) +{ + Scanner scanner(CharStream("/** Send $(value / 1000) chocolates to the user*/")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user"); +} + BOOST_AUTO_TEST_CASE(documentation_comments_parsed) { Scanner scanner(CharStream("some other tokens /// Send $(value / 1000) chocolates to the user")); From 61a1f4436c5561a6c521b9c6009c57c76d275adb Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 13:27:25 +0100 Subject: [PATCH 44/57] Scanner properly scans multiline natspec comments - Single and multiline natspect comments get the initial whitespace skipped now - Some rules introduced for the multiline comments. If first line is empty then no newline is added to the literal. Same thing with the last line. Finally in all lines initial '*' are skipped --- libsolidity/Scanner.cpp | 36 ++++++++++++++++++++++++++++-------- libsolidity/Scanner.h | 2 ++ test/solidityScanner.cpp | 25 ++++++++++++++++++++++--- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 2e9b7b454..890d69494 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -209,6 +209,15 @@ bool Scanner::skipWhitespace() return getSourcePos() != startPosition; } +bool Scanner::skipWhitespaceExceptLF() +{ + int const startPosition = getSourcePos(); + while (m_char == ' ' || m_char == '\t') + advance(); + // Return whether or not we skipped any characters. + return getSourcePos() != startPosition; +} + Token::Value Scanner::skipSingleLineComment() { // The line terminator at the end of the line is not considered @@ -222,7 +231,8 @@ Token::Value Scanner::skipSingleLineComment() Token::Value Scanner::scanSingleLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); - advance(); //consume the last '/' + advance(); //consume the last '/' at /// + skipWhitespaceExceptLF(); while (!isSourcePastEndOfInput()) { if (isLineTerminator(m_char)) @@ -273,18 +283,27 @@ Token::Value Scanner::scanMultiLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); bool endFound = false; + bool charsAdded = false; - advance(); //consume the last '*' + advance(); //consume the last '*' at /** + skipWhitespaceExceptLF(); while (!isSourcePastEndOfInput()) { - // skip starting '*' in multiine comments + //handle newlines in multline comments if (isLineTerminator(m_char)) { skipWhitespace(); - if (!m_source.isPastEndOfInput(2) && m_source.get(1) == '*' && m_source.get(2) != '/') - { - addCommentLiteralChar('\n'); - m_char = m_source.advanceAndGet(3); + if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/') + { // skip first '*' in subsequent lines + if (charsAdded) + addCommentLiteralChar('\n'); + m_char = m_source.advanceAndGet(2); + } + else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') + { // if after newline the comment ends, don't insert the newline + m_char = m_source.advanceAndGet(2); + endFound = true; + break; } else addCommentLiteralChar('\n'); @@ -292,11 +311,12 @@ Token::Value Scanner::scanMultiLineDocComment() if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') { - m_source.advanceAndGet(2); + m_char = m_source.advanceAndGet(2); endFound = true; break; } addCommentLiteralChar(m_char); + charsAdded = true; advance(); } literal.complete(); diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h index 7f1b18352..5e70db51d 100644 --- a/libsolidity/Scanner.h +++ b/libsolidity/Scanner.h @@ -182,6 +182,8 @@ private: /// Skips all whitespace and @returns true if something was skipped. bool skipWhitespace(); + /// Skips all whitespace except Line feeds and returns true if something was skipped + bool skipWhitespaceExceptLF(); Token::Value skipSingleLineComment(); Token::Value skipMultiLineComment(); diff --git a/test/solidityScanner.cpp b/test/solidityScanner.cpp index 2b8c7c458..159e53055 100644 --- a/test/solidityScanner.cpp +++ b/test/solidityScanner.cpp @@ -157,14 +157,14 @@ BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin) { Scanner scanner(CharStream("/// Send $(value / 1000) chocolates to the user")); BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); - BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user"); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); } BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed_begin) { Scanner scanner(CharStream("/** Send $(value / 1000) chocolates to the user*/")); BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); - BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user"); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); } BOOST_AUTO_TEST_CASE(documentation_comments_parsed) @@ -174,7 +174,19 @@ BOOST_AUTO_TEST_CASE(documentation_comments_parsed) BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); - BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user"); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); +} + +BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed) +{ + Scanner scanner(CharStream("some other tokens /**\n" + "* Send $(value / 1000) chocolates to the user\n" + "*/")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); } BOOST_AUTO_TEST_CASE(comment_before_eos) @@ -191,6 +203,13 @@ BOOST_AUTO_TEST_CASE(documentation_comment_before_eos) BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); } +BOOST_AUTO_TEST_CASE(empty_multiline_documentation_comment_before_eos) +{ + Scanner scanner(CharStream("/***/")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), ""); +} + BOOST_AUTO_TEST_CASE(comments_mixed_in_sequence) { Scanner scanner(CharStream("hello_world ///documentation comment \n" From 30beaca35955476ef07ee1f9c82735b0d63241bf Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 14:43:35 +0100 Subject: [PATCH 45/57] Changes in InterfaceHandler to deal with multiline natspec - Also now Solidity scanner considers Carriage Return as whitespace - Tests for Natspec generation with the new multiline comments --- libsolidity/InterfaceHandler.cpp | 46 +++++++++++++------------------- libsolidity/InterfaceHandler.h | 3 ++- libsolidity/Scanner.cpp | 2 +- test/solidityNatspecJSON.cpp | 29 ++++++++++++++++++++ 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index c734fa38c..9ae284b40 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -166,9 +166,12 @@ static inline std::string::const_iterator skipLineOrEOS(std::string::const_itera std::string::const_iterator InterfaceHandler::parseDocTagLine(std::string::const_iterator _pos, std::string::const_iterator _end, std::string& _tagString, - DocTagType _tagType) + DocTagType _tagType, + bool _appending) { auto nlPos = std::find(_pos, _end, '\n'); + if (_appending && *_pos != ' ') + _tagString += " "; std::copy(_pos, nlPos, back_inserter(_tagString)); m_lastTag = _tagType; return skipLineOrEOS(nlPos, _end); @@ -201,7 +204,8 @@ std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::con solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); auto pair = m_params.back(); - pair.second += " "; + if (*_pos != ' ') + pair.second += " "; auto nlPos = std::find(_pos, _end, '\n'); std::copy(_pos, nlPos, back_inserter(pair.second)); @@ -221,17 +225,17 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite if (m_lastTag == DocTagType::NONE || _tag != "") { if (_tag == "dev") - return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV); + return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, false); else if (_tag == "notice") - return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); + return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, false); else if (_tag == "return") - return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); + return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, false); else if (_tag == "author") { if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR); + return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, false); else if (_owner == CommentOwner::FUNCTION) - return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR); + return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, false); else // LTODO: for now this else makes no sense but later comments will go to more language constructs BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag is legal only for contracts")); @@ -239,7 +243,7 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite else if (_tag == "title") { if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE); + return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, false); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag is legal only for contracts")); @@ -261,34 +265,22 @@ std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_it switch (m_lastTag) { case DocTagType::DEV: - m_dev += " "; - return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV); + return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, true); case DocTagType::NOTICE: - m_notice += " "; - return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE); + return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, true); case DocTagType::RETURN: - m_return += " "; - return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN); + return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, true); case DocTagType::AUTHOR: if (_owner == CommentOwner::CONTRACT) - { - m_contractAuthor += " "; - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR); - } + return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, true); else if (_owner == CommentOwner::FUNCTION) - { - m_author += " "; - return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR); - } + return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, true); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag in illegal comment")); case DocTagType::TITLE: if (_owner == CommentOwner::CONTRACT) - { - m_title += " "; - return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE); - } + return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, true); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag in illegal comment")); @@ -329,7 +321,7 @@ void InterfaceHandler::parseDocString(std::string const& _string, CommentOwner _ currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos), _owner); } else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag - currPos = appendDocTag(currPos + 1, end, _owner); + currPos = appendDocTag(currPos, end, _owner); else if (currPos != end) // skip the line if a newline was found currPos = nlPos + 1; } diff --git a/libsolidity/InterfaceHandler.h b/libsolidity/InterfaceHandler.h index d271a6697..c8399d71f 100644 --- a/libsolidity/InterfaceHandler.h +++ b/libsolidity/InterfaceHandler.h @@ -92,7 +92,8 @@ private: std::string::const_iterator parseDocTagLine(std::string::const_iterator _pos, std::string::const_iterator _end, std::string& _tagString, - DocTagType _tagType); + DocTagType _tagType, + bool _appending); std::string::const_iterator parseDocTagParam(std::string::const_iterator _pos, std::string::const_iterator _end); std::string::const_iterator appendDocTagParam(std::string::const_iterator _pos, diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 890d69494..69b61ce4b 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -80,7 +80,7 @@ bool isLineTerminator(char c) } bool isWhiteSpace(char c) { - return c == ' ' || c == '\n' || c == '\t'; + return c == ' ' || c == '\n' || c == '\t' || c == '\r'; } bool isIdentifierStart(char c) { diff --git a/test/solidityNatspecJSON.cpp b/test/solidityNatspecJSON.cpp index 2c3ded08d..5b48a67ce 100644 --- a/test/solidityNatspecJSON.cpp +++ b/test/solidityNatspecJSON.cpp @@ -394,6 +394,35 @@ BOOST_AUTO_TEST_CASE(dev_multiline_return) checkNatspec(sourceCode, natspec, false); } +BOOST_AUTO_TEST_CASE(dev_multiline_comment) +{ + char const* sourceCode = "contract test {\n" + " /**\n" + " * @dev Multiplies a number by 7 and adds second parameter\n" + " * @param a Documentation for the first parameter starts here.\n" + " * Since it's a really complicated parameter we need 2 lines\n" + " * @param second Documentation for the second parameter\n" + " * @return The result of the multiplication\n" + " * and cookies with nutella\n" + " */" + " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" + "}\n"; + + char const* natspec = "{" + "\"methods\":{" + " \"mul\":{ \n" + " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" + " \"params\": {\n" + " \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" + " \"second\": \"Documentation for the second parameter\"\n" + " },\n" + " \"return\": \"The result of the multiplication and cookies with nutella\"\n" + " }\n" + "}}"; + + checkNatspec(sourceCode, natspec, false); +} + BOOST_AUTO_TEST_CASE(dev_contract_no_doc) { char const* sourceCode = "contract test {\n" From 8edb5b8b54f1c023f68688f6ac52d9878975b8a7 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 15:21:03 +0100 Subject: [PATCH 46/57] Solidity Tests names are now more consistent - File names and Boost Test Suite have the same name now for every solidity Test, so that there is no need to guess or check when you want to run a specific suite from the command line --- test/{solidityJSONInterfaceTest.cpp => SolidityABIJSON.cpp} | 0 test/{solidityCompiler.cpp => SolidityCompiler.cpp} | 0 test/{solidityEndToEndTest.cpp => SolidityEndToEndTest.cpp} | 2 +- ...ityExpressionCompiler.cpp => SolidityExpressionCompiler.cpp} | 0 ...eAndTypeResolution.cpp => SolidityNameAndTypeResolution.cpp} | 0 test/{solidityNatspecJSON.cpp => SolidityNatspecJSON.cpp} | 0 test/{solidityOptimizerTest.cpp => SolidityOptimizer.cpp} | 2 +- test/{solidityParser.cpp => SolidityParser.cpp} | 0 test/{solidityScanner.cpp => SolidityScanner.cpp} | 0 9 files changed, 2 insertions(+), 2 deletions(-) rename test/{solidityJSONInterfaceTest.cpp => SolidityABIJSON.cpp} (100%) rename test/{solidityCompiler.cpp => SolidityCompiler.cpp} (100%) rename test/{solidityEndToEndTest.cpp => SolidityEndToEndTest.cpp} (99%) rename test/{solidityExpressionCompiler.cpp => SolidityExpressionCompiler.cpp} (100%) rename test/{solidityNameAndTypeResolution.cpp => SolidityNameAndTypeResolution.cpp} (100%) rename test/{solidityNatspecJSON.cpp => SolidityNatspecJSON.cpp} (100%) rename test/{solidityOptimizerTest.cpp => SolidityOptimizer.cpp} (98%) rename test/{solidityParser.cpp => SolidityParser.cpp} (100%) rename test/{solidityScanner.cpp => SolidityScanner.cpp} (100%) diff --git a/test/solidityJSONInterfaceTest.cpp b/test/SolidityABIJSON.cpp similarity index 100% rename from test/solidityJSONInterfaceTest.cpp rename to test/SolidityABIJSON.cpp diff --git a/test/solidityCompiler.cpp b/test/SolidityCompiler.cpp similarity index 100% rename from test/solidityCompiler.cpp rename to test/SolidityCompiler.cpp diff --git a/test/solidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp similarity index 99% rename from test/solidityEndToEndTest.cpp rename to test/SolidityEndToEndTest.cpp index 26f5528ab..aa74f8186 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -36,7 +36,7 @@ namespace solidity namespace test { -BOOST_FIXTURE_TEST_SUITE(SolidityCompilerEndToEndTest, ExecutionFramework) +BOOST_FIXTURE_TEST_SUITE(SolidityEndToEndTest, ExecutionFramework) BOOST_AUTO_TEST_CASE(smoke_test) { diff --git a/test/solidityExpressionCompiler.cpp b/test/SolidityExpressionCompiler.cpp similarity index 100% rename from test/solidityExpressionCompiler.cpp rename to test/SolidityExpressionCompiler.cpp diff --git a/test/solidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp similarity index 100% rename from test/solidityNameAndTypeResolution.cpp rename to test/SolidityNameAndTypeResolution.cpp diff --git a/test/solidityNatspecJSON.cpp b/test/SolidityNatspecJSON.cpp similarity index 100% rename from test/solidityNatspecJSON.cpp rename to test/SolidityNatspecJSON.cpp diff --git a/test/solidityOptimizerTest.cpp b/test/SolidityOptimizer.cpp similarity index 98% rename from test/solidityOptimizerTest.cpp rename to test/SolidityOptimizer.cpp index 388e0579c..ef5c6f9b5 100644 --- a/test/solidityOptimizerTest.cpp +++ b/test/SolidityOptimizer.cpp @@ -71,7 +71,7 @@ protected: Address m_nonOptimizedContract; }; -BOOST_FIXTURE_TEST_SUITE(SolidityOptimizerTest, OptimizerTestFramework) +BOOST_FIXTURE_TEST_SUITE(SolidityOptimizer, OptimizerTestFramework) BOOST_AUTO_TEST_CASE(smoke_test) { diff --git a/test/solidityParser.cpp b/test/SolidityParser.cpp similarity index 100% rename from test/solidityParser.cpp rename to test/SolidityParser.cpp diff --git a/test/solidityScanner.cpp b/test/SolidityScanner.cpp similarity index 100% rename from test/solidityScanner.cpp rename to test/SolidityScanner.cpp From 2e3d45004681407393f0742bec6d9ed463211dcf Mon Sep 17 00:00:00 2001 From: ethdev Date: Thu, 18 Dec 2014 15:39:15 +0100 Subject: [PATCH 47/57] copy qml dependencies of mix to install directory --- cmake/EthExecutableHelper.cmake | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cmake/EthExecutableHelper.cmake b/cmake/EthExecutableHelper.cmake index 0ec1e10d4..5f4ae3c09 100644 --- a/cmake/EthExecutableHelper.cmake +++ b/cmake/EthExecutableHelper.cmake @@ -60,7 +60,11 @@ macro(eth_install_executable EXECUTABLE) cmake_parse_arguments (ETH_INSTALL_EXECUTABLE "${options}" "${one_value_args}" "${multi_value_args}" "${extra_macro_args}") if (ETH_INSTALL_EXECUTABLE_QMLDIR) - set(eth_qml_dir "-qmldir=${ETH_INSTALL_EXECUTABLE_QMLDIR}") + if (APPLE) + set(eth_qml_dir "-qmldir=${ETH_INSTALL_EXECUTABLE_QMLDIR}") + elseif (WIN32) + set(eth_qml_dir --qmldir ${ETH_INSTALL_EXECUTABLE_QMLDIR}) + endif() message(STATUS "${EXECUTABLE} qmldir: ${eth_qml_dir}") endif() @@ -68,7 +72,8 @@ macro(eth_install_executable EXECUTABLE) # First have qt5 install plugins and frameworks add_custom_command(TARGET ${EXECUTABLE} POST_BUILD COMMAND ${MACDEPLOYQT_APP} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTABLE}.app ${eth_qml_dir} - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + ) # This tool and next will inspect linked libraries in order to determine which dependencies are required if (${CMAKE_CFG_INTDIR} STREQUAL ".") @@ -108,6 +113,13 @@ macro(eth_install_executable EXECUTABLE) $/platforms ) + # ugly way, improve that + add_custom_command(TARGET ${EXECUTABLE} POST_BUILD + COMMAND cmake -E copy_directory + "${ETH_DEPENDENCY_INSTALL_DIR}/qml" + $ + ) + install( FILES ${DLLS} DESTINATION bin COMPONENT ${EXECUTABLE} @@ -118,6 +130,14 @@ macro(eth_install_executable EXECUTABLE) COMPONENT ${EXECUTABLE} ) + file (GLOB QMLS ${ETH_DEPENDENCY_INSTALL_DIR}/qml/*) + foreach(QML ${QMLS}) + install( DIRECTORY ${QML} + DESTINATION bin + COMPONENT ${EXECUTABLE} + ) + endforeach() + install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin COMPONENT ${EXECUTABLE} From 23d4f98b744887bdc1f1952bf303651ac6573e3e Mon Sep 17 00:00:00 2001 From: ethdev Date: Thu, 18 Dec 2014 15:50:23 +0100 Subject: [PATCH 48/57] alethzero.exe icon --- alethzero/CMakeLists.txt | 1 + alethzero/alethzero.rc | 1 + cmake/EthExecutableHelper.cmake | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 alethzero/alethzero.rc diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index 39c02d6be..8c8a37a42 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -27,6 +27,7 @@ endif () eth_add_executable(${EXECUTABLE} ICON alethzero UI_RESOURCES alethzero.icns Main.ui + WIN_RESOURCES alethzero.rc ) add_dependencies(${EXECUTABLE} BuildInfo.h) diff --git a/alethzero/alethzero.rc b/alethzero/alethzero.rc new file mode 100644 index 000000000..29c778bd4 --- /dev/null +++ b/alethzero/alethzero.rc @@ -0,0 +1 @@ +APP_ICON ICON DISCARDABLE "alethzero.ico" \ No newline at end of file diff --git a/cmake/EthExecutableHelper.cmake b/cmake/EthExecutableHelper.cmake index 5f4ae3c09..88e2a3e16 100644 --- a/cmake/EthExecutableHelper.cmake +++ b/cmake/EthExecutableHelper.cmake @@ -18,7 +18,7 @@ macro(eth_add_executable EXECUTABLE) set (extra_macro_args ${ARGN}) set (options) set (one_value_args ICON) - set (multi_value_args UI_RESOURCES) + set (multi_value_args UI_RESOURCES WIN_RESOURCES) cmake_parse_arguments (ETH_ADD_EXECUTABLE "${options}" "${one_value_args}" "${multi_value_args}" "${extra_macro_args}") if (APPLE) @@ -38,7 +38,7 @@ macro(eth_add_executable EXECUTABLE) set_source_files_properties(${MACOSX_BUNDLE_ICON_FILE}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) else () - add_executable(${EXECUTABLE} ${ETH_ADD_EXECUTABLE_UI_RESOURCES} ${SRC_LIST} ${HEADERS}) + add_executable(${EXECUTABLE} ${ETH_ADD_EXECUTABLE_UI_RESOURCES} ${ETH_ADD_EXECUTABLE_WIN_RESOURCES} ${SRC_LIST} ${HEADERS}) endif() endmacro() From b657b009ef7a272d105f92fbc0d3d6e449664fd3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 18 Dec 2014 16:03:59 +0100 Subject: [PATCH 49/57] Cleanups and YP formalism. --- libethereum/Transaction.cpp | 4 ++-- libevm/VM.h | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 0c1173134..24c56930e 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -43,9 +43,9 @@ Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) m_receiveAddress = rlp[field = 3].toHash
(); m_value = rlp[field = 4].toInt(); m_data = rlp[field = 5].toBytes(); + byte v = rlp[field = 6].toInt() - 27; h256 r = rlp[field = 7].toInt(); h256 s = rlp[field = 8].toInt(); - byte v = rlp[field = 6].toInt() - 27; m_vrs = SignatureStruct{ r, s, v }; if (_checkSender) m_sender = sender(); @@ -74,7 +74,7 @@ Address Transaction::sender() const { if (!m_sender) { - auto p = recover(*(Signature const*)&m_vrs, sha3(WithoutSignature)); + auto p = recover(m_vrs, sha3(WithoutSignature)); if (!p) BOOST_THROW_EXCEPTION(InvalidSignature()); m_sender = right160(dev::sha3(bytesConstRef(p.data(), sizeof(p)))); diff --git a/libevm/VM.h b/libevm/VM.h index 3eb330fcd..1f3fc17d5 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -217,6 +217,8 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st require(7); runGas = (bigint)c_callGas + m_stack[m_stack.size() - 1]; newTempSize = std::max(memNeed(m_stack[m_stack.size() - 6], m_stack[m_stack.size() - 7]), memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5])); + if (_ext.depth == 1024) + BOOST_THROW_EXCEPTION(OutOfGas()); break; case Instruction::CREATE: @@ -226,6 +228,8 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st u256 inSize = m_stack[m_stack.size() - 3]; newTempSize = (bigint)inOff + inSize; runGas = c_createGas; + if (_ext.depth == 1024) + BOOST_THROW_EXCEPTION(OutOfGas()); break; } case Instruction::EXP: @@ -566,6 +570,7 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st break; default: // this is unreachable, but if someone introduces a bug in the future, he may get here. + assert(false); BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("CALLDATACOPY, CODECOPY or EXTCODECOPY instruction requested.")); break; } @@ -795,8 +800,6 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st if (_ext.balance(_ext.myAddress) >= endowment) { - if (_ext.depth == 1024) - BOOST_THROW_EXCEPTION(OutOfGas()); _ext.subBalance(endowment); m_stack.push_back((u160)_ext.create(endowment, m_gas, bytesConstRef(m_temp.data() + initOff, initSize), _onOp)); } @@ -825,8 +828,6 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st if (_ext.balance(_ext.myAddress) >= value) { - if (_ext.depth == 1024) - BOOST_THROW_EXCEPTION(OutOfGas()); _ext.subBalance(value); m_stack.push_back(_ext.call(inst == Instruction::CALL ? receiveAddress : _ext.myAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), gas, bytesRef(m_temp.data() + outOff, outSize), _onOp, {}, receiveAddress)); } From 71b267d01aaa32786dfbb3dccf520d86f2f77708 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 18 Dec 2014 16:25:44 +0100 Subject: [PATCH 50/57] Update serpent. --- libserpent/bignum.h | 3 + libserpent/compiler.cpp | 279 +++------ libserpent/opcodes.h | 123 +--- libserpent/parser.cpp | 86 +-- libserpent/rewriter.cpp | 1206 +++++++++++++++------------------------ libserpent/tokenize.cpp | 4 +- libserpent/util.cpp | 57 +- libserpent/util.h | 36 +- sc/cmdline.cpp | 13 + 9 files changed, 709 insertions(+), 1098 deletions(-) diff --git a/libserpent/bignum.h b/libserpent/bignum.h index 599365b6c..99571acd2 100644 --- a/libserpent/bignum.h +++ b/libserpent/bignum.h @@ -35,4 +35,7 @@ bool decimalGt(std::string a, std::string b, bool eqAllowed=false); unsigned decimalToUnsigned(std::string a); +#define utd unsignedToDecimal +#define dtu decimalToUnsigned + #endif diff --git a/libserpent/compiler.cpp b/libserpent/compiler.cpp index 30628fbc9..a3e5b1c60 100644 --- a/libserpent/compiler.cpp +++ b/libserpent/compiler.cpp @@ -6,6 +6,7 @@ #include "bignum.h" #include "opcodes.h" +// Auxiliary data that is gathered while compiling struct programAux { std::map vars; int nextVarMem; @@ -13,15 +14,19 @@ struct programAux { bool calldataUsed; int step; int labelLength; - int functionCount; }; +// Auxiliary data that gets passed down vertically +// but not back up struct programVerticalAux { int height; + std::string innerScopeName; std::map dupvars; std::map funvars; + std::vector scopes; }; +// Compilation result struct programData { programAux aux; Node code; @@ -34,7 +39,6 @@ programAux Aux() { o.calldataUsed = false; o.step = 0; o.nextVarMem = 32; - o.functionCount = 0; return o; } @@ -43,6 +47,7 @@ programVerticalAux verticalAux() { o.height = 0; o.dupvars = std::map(); o.funvars = std::map(); + o.scopes = std::vector(); return o; } @@ -72,29 +77,58 @@ Node popwrap(Node node) { return multiToken(nodelist, 2, node.metadata); } +// Grabs variables +mss getVariables(Node node, mss cur=mss()) { + Metadata m = node.metadata; + // Tokens don't contain any variables + if (node.type == TOKEN) + return cur; + // Don't descend into call fragments + else if (node.val == "lll") + return getVariables(node.args[1], cur); + // At global scope get/set/ref also declare + else if (node.val == "get" || node.val == "set" || node.val == "ref") { + if (node.args[0].type != TOKEN) + err("Variable name must be simple token," + " not complex expression! " + printSimple(node.args[0]), m); + if (!cur.count(node.args[0].val)) { + cur[node.args[0].val] = utd(cur.size() * 32 + 32); + //std::cerr << node.args[0].val << " " << cur[node.args[0].val] << "\n"; + } + } + // Recursively process children + for (unsigned i = 0; i < node.args.size(); i++) { + cur = getVariables(node.args[i], cur); + } + return cur; +} + // Turns LLL tree into tree of code fragments programData opcodeify(Node node, programAux aux=Aux(), programVerticalAux vaux=verticalAux()) { std::string symb = "_"+mkUniqueToken(); Metadata m = node.metadata; + // Get variables + if (!aux.vars.size()) { + aux.vars = getVariables(node); + aux.nextVarMem = aux.vars.size() * 32 + 32; + } // Numbers if (node.type == TOKEN) { return pd(aux, nodeToNumeric(node), 1); } - else if (node.val == "ref" || node.val == "get" || - node.val == "set" || node.val == "declare") { + else if (node.val == "ref" || node.val == "get" || node.val == "set") { std::string varname = node.args[0].val; - if (!aux.vars.count(varname)) { - aux.vars[varname] = unsignedToDecimal(aux.nextVarMem); - aux.nextVarMem += 32; - } - if (varname == "'msg.data") aux.calldataUsed = true; + // Determine reference to variable + Node varNode = tkn(aux.vars[varname], m); + //std::cerr << varname << " " << printSimple(varNode) << "\n"; // Set variable if (node.val == "set") { programData sub = opcodeify(node.args[1], aux, vaux); if (!sub.outs) err("Value to set variable must have nonzero arity!", m); + // What if we are setting a stack variable? if (vaux.dupvars.count(node.args[0].val)) { int h = vaux.height - vaux.dupvars[node.args[0].val]; if (h > 16) err("Too deep for stack variable (max 16)", m); @@ -105,149 +139,65 @@ programData opcodeify(Node node, }; return pd(sub.aux, multiToken(nodelist, 3, m), 0); } - Node nodelist[] = { - sub.code, - token(sub.aux.vars[varname], m), - token("MSTORE", m), - }; - return pd(sub.aux, multiToken(nodelist, 3, m), 0); + // Setting a memory variable + else { + Node nodelist[] = { + sub.code, + varNode, + token("MSTORE", m), + }; + return pd(sub.aux, multiToken(nodelist, 3, m), 0); + } } // Get variable else if (node.val == "get") { - if (vaux.dupvars.count(node.args[0].val)) { + // Getting a stack variable + if (vaux.dupvars.count(node.args[0].val)) { int h = vaux.height - vaux.dupvars[node.args[0].val]; if (h > 16) err("Too deep for stack variable (max 16)", m); return pd(aux, token("DUP"+unsignedToDecimal(h)), 1); } - Node nodelist[] = - { token(aux.vars[varname], m), token("MLOAD", m) }; - return pd(aux, multiToken(nodelist, 2, m), 1); + // Getting a memory variable + else { + Node nodelist[] = + { varNode, token("MLOAD", m) }; + return pd(aux, multiToken(nodelist, 2, m), 1); + } } // Refer variable else if (node.val == "ref") { if (vaux.dupvars.count(node.args[0].val)) err("Cannot ref stack variable!", m); - return pd(aux, token(aux.vars[varname], m), 1); - } - // Declare variable - else { - return pd(aux, multiToken(nullptr, 0, m), 0); + return pd(aux, varNode, 1); } } - // Define functions (TODO: eventually move to rewriter.cpp, keep - // compiler pure LLL) - if (node.val == "def") { - std::vector varNames; - std::vector varSizes; - bool useLt32 = false; - int totalSz = 0; - if (node.args.size() != 2) - err("Malformed def!", m); - // Collect the list of variable names and variable byte counts - for (unsigned i = 0; i < node.args[0].args.size(); i++) { - if (node.args[0].args[i].val == "kv") { - if (node.args[0].args[i].args.size() != 2) - err("Malformed def!", m); - varNames.push_back(node.args[0].args[i].args[0].val); - varSizes.push_back( - decimalToUnsigned(node.args[0].args[i].args[1].val)); - if (varSizes.back() > 32) - err("Max argument width: 32 bytes", m); - useLt32 = true; + // Comments do nothing + else if (node.val == "comment") { + Node nodelist[] = { }; + return pd(aux, multiToken(nodelist, 0, m), 0); + } + // Custom operation sequence + // eg. (ops bytez id msize swap1 msize add 0 swap1 mstore) == alloc + if (node.val == "ops") { + std::vector subs2; + int depth = 0; + for (unsigned i = 0; i < node.args.size(); i++) { + std::string op = upperCase(node.args[i].val); + if (node.args[i].type == ASTNODE || opinputs(op) == -1) { + programVerticalAux vaux2 = vaux; + vaux2.height = vaux.height - i - 1 + node.args.size(); + programData sub = opcodeify(node.args[i], aux, vaux2); + aux = sub.aux; + depth += sub.outs; + subs2.push_back(sub.code); } else { - varNames.push_back(node.args[0].args[i].val); - varSizes.push_back(32); + subs2.push_back(token(op, m)); + depth += opoutputs(op) - opinputs(op); } - aux.vars[varNames.back()] = unsignedToDecimal(aux.nextVarMem + 32 * i); - totalSz += varSizes.back(); } - int functionCount = aux.functionCount; - int nextVarMem = aux.nextVarMem; - aux.nextVarMem += 32 * varNames.size(); - aux.functionCount += 1; - programData inner; - // If we're only using 32-byte variables, then great, just copy - // over the calldata! - if (!useLt32) { - programData sub = opcodeify(node.args[1], aux, vaux); - Node nodelist[] = { - token(unsignedToDecimal(totalSz), m), - token("1", m), - token(unsignedToDecimal(nextVarMem), m), - token("CALLDATACOPY", m), - sub.code - }; - inner = pd(sub.aux, multiToken(nodelist, 5, m), 0); - } - else { - std::vector innerList; - int cum = 1; - for (unsigned i = 0; i < varNames.size();) { - // If we get a series of 32-byte values, we calldatacopy them - if (varSizes[i] == 32) { - unsigned until = i+1; - while (until < varNames.size() && varSizes[until] == 32) - until += 1; - innerList.push_back(token(unsignedToDecimal((until - i) * 32), m)); - innerList.push_back(token(unsignedToDecimal(cum), m)); - innerList.push_back(token(unsignedToDecimal(nextVarMem + i * 32), m)); - innerList.push_back(token("CALLDATACOPY", m)); - cum += (until - i) * 32; - i = until; - } - // Otherwise, we do a clever trick to extract the value - else { - innerList.push_back(token(unsignedToDecimal(32 - varSizes[i]), m)); - innerList.push_back(token("256", m)); - innerList.push_back(token("EXP", m)); - innerList.push_back(token(unsignedToDecimal(cum), m)); - innerList.push_back(token("CALLDATALOAD", m)); - innerList.push_back(token("DIV", m)); - innerList.push_back(token(unsignedToDecimal(nextVarMem + i * 32), m)); - innerList.push_back(token("MSTORE", m)); - cum += varSizes[i]; - i += 1; - } - } - // If caller == origin, then it's from a tx, so unpack, otherwise - // plain copy - programData sub = opcodeify(node.args[1], aux, vaux); - Node ilnode = astnode("", innerList, m); - Node nodelist[] = { - token(unsignedToDecimal(32 * varNames.size()), m), - token("1", m), - token(unsignedToDecimal(nextVarMem), m), - token("CALLDATACOPY", m), - token("CALLER", m), - token("ORIGIN", m), - token("EQ", m), - token("ISZERO", m), - token("$maincode"+symb, m), - token("JUMPI", m), - ilnode, - token("~maincode"+symb, m), - token("JUMPDEST", m), - sub.code - }; - inner = pd(sub.aux, multiToken(nodelist, 14, m), 0); - } - // Check if the function call byte is the same - Node nodelist2[] = { - token("0", m), - token("CALLDATALOAD", m), - token("0", m), - token("BYTE", m), - token(unsignedToDecimal(functionCount), m), - token("EQ", m), - token("ISZERO", m), - token("$endcode"+symb, m), - token("JUMPI", m), - inner.code, - token("~endcode"+symb, m), - token("JUMPDEST", m), - }; - return pd(inner.aux, multiToken(nodelist2, 12, m), 0); + if (depth < 0 || depth > 1) err("Stack depth mismatch", m); + return pd(aux, astnode("_", subs2, m), 0); } // Code blocks if (node.val == "lll" && node.args.size() == 2) { @@ -372,49 +322,14 @@ programData opcodeify(Node node, }; return pd(aux, multiToken(nodelist, 8, m), 1); } - // Array literals - else if (node.val == "array_lit") { - aux.allocUsed = true; - std::vector nodes; - if (!node.args.size()) { - nodes.push_back(token("MSIZE", m)); - return pd(aux, astnode("_", nodes, m)); - } - nodes.push_back(token("MSIZE", m)); - nodes.push_back(token("0", m)); - nodes.push_back(token("MSIZE", m)); - nodes.push_back(token(unsignedToDecimal(node.args.size() * 32 - 1), m)); - nodes.push_back(token("ADD", m)); - nodes.push_back(token("MSTORE8", m)); - for (unsigned i = 0; i < node.args.size(); i++) { - Metadata m2 = node.args[i].metadata; - nodes.push_back(token("DUP1", m2)); - programVerticalAux vaux2 = vaux; - vaux2.height += 2; - programData sub = opcodeify(node.args[i], aux, vaux2); - if (!sub.outs) - err("Array_lit item " + unsignedToDecimal(i) + " has zero arity", m2); - aux = sub.aux; - nodes.push_back(sub.code); - nodes.push_back(token("SWAP1", m2)); - if (i > 0) { - nodes.push_back(token(unsignedToDecimal(i * 32), m2)); - nodes.push_back(token("ADD", m2)); - } - nodes.push_back(token("MSTORE", m2)); - } - return pd(aux, astnode("_", nodes, m), 1); - } // All other functions/operators else { std::vector subs2; int depth = opinputs(upperCase(node.val)); - if (node.val != "debug") { - if (depth == -1) - err("Not a function or opcode: "+node.val, m); - if ((int)node.args.size() != depth) - err("Invalid arity for "+node.val, m); - } + if (depth == -1) + err("Not a function or opcode: "+node.val, m); + if ((int)node.args.size() != depth) + err("Invalid arity for "+node.val, m); for (int i = node.args.size() - 1; i >= 0; i--) { programVerticalAux vaux2 = vaux; vaux2.height = vaux.height - i - 1 + node.args.size(); @@ -424,13 +339,8 @@ programData opcodeify(Node node, err("Input "+unsignedToDecimal(i)+" has arity 0", sub.code.metadata); subs2.push_back(sub.code); } - if (node.val == "debug") { - subs2.push_back(token("DUP"+unsignedToDecimal(node.args.size()), m)); - for (int i = 0; i <= (int)node.args.size(); i++) - subs2.push_back(token("POP", m)); - } - else subs2.push_back(token(upperCase(node.val), m)); - int outdepth = node.val == "debug" ? 0 : opoutputs(upperCase(node.val)); + subs2.push_back(token(upperCase(node.val), m)); + int outdepth = opoutputs(upperCase(node.val)); return pd(aux, astnode("_", subs2, m), outdepth); } } @@ -449,15 +359,6 @@ Node finalize(programData c) { }; bottom.push_back(multiToken(nodelist, 3, m)); } - // If msg.data is being used as an array, then we need to copy it - if (c.aux.calldataUsed) { - Node nodelist[] = { - token("MSIZE", m), token("CALLDATASIZE", m), token("0", m), - token("MSIZE", m), token("CALLDATACOPY", m), - token(c.aux.vars["'msg.data"], m), token("MSTORE", m) - }; - bottom.push_back(multiToken(nodelist, 7, m)); - } // The actual code bottom.push_back(c.code); return astnode("_", bottom, m); diff --git a/libserpent/opcodes.h b/libserpent/opcodes.h index a7bcc1af9..41423c169 100644 --- a/libserpent/opcodes.h +++ b/libserpent/opcodes.h @@ -5,6 +5,7 @@ #include #include #include +#include "util.h" class Mapping { public: @@ -20,119 +21,25 @@ class Mapping { int out; }; -Mapping mapping[] = { - Mapping("STOP", 0x00, 0, 0), - Mapping("ADD", 0x01, 2, 1), - Mapping("MUL", 0x02, 2, 1), - Mapping("SUB", 0x03, 2, 1), - Mapping("DIV", 0x04, 2, 1), - Mapping("SDIV", 0x05, 2, 1), - Mapping("MOD", 0x06, 2, 1), - Mapping("SMOD", 0x07, 2, 1), - Mapping("ADDMOD", 0x08, 3, 1), - Mapping("MULMOD", 0x09, 3, 1), - Mapping("EXP", 0x0a, 2, 1), - Mapping("SIGNEXTEND", 0x0b, 2, 1), - Mapping("LT", 0x10, 2, 1), - Mapping("GT", 0x11, 2, 1), - Mapping("SLT", 0x12, 2, 1), - Mapping("SGT", 0x13, 2, 1), - Mapping("EQ", 0x14, 2, 1), - Mapping("ISZERO", 0x15, 1, 1), - Mapping("AND", 0x16, 2, 1), - Mapping("OR", 0x17, 2, 1), - Mapping("XOR", 0x18, 2, 1), - Mapping("NOT", 0x19, 1, 1), - Mapping("BYTE", 0x1a, 2, 1), - Mapping("ADDMOD", 0x14, 3, 1), - Mapping("MULMOD", 0x15, 3, 1), - Mapping("SIGNEXTEND", 0x16, 2, 1), - Mapping("SHA3", 0x20, 2, 1), - Mapping("ADDRESS", 0x30, 0, 1), - Mapping("BALANCE", 0x31, 1, 1), - Mapping("ORIGIN", 0x32, 0, 1), - Mapping("CALLER", 0x33, 0, 1), - Mapping("CALLVALUE", 0x34, 0, 1), - Mapping("CALLDATALOAD", 0x35, 1, 1), - Mapping("CALLDATASIZE", 0x36, 0, 1), - Mapping("CALLDATACOPY", 0x37, 3, 1), - Mapping("CODESIZE", 0x38, 0, 1), - Mapping("CODECOPY", 0x39, 3, 1), - Mapping("GASPRICE", 0x3a, 0, 1), - Mapping("PREVHASH", 0x40, 0, 1), - Mapping("COINBASE", 0x41, 0, 1), - Mapping("TIMESTAMP", 0x42, 0, 1), - Mapping("NUMBER", 0x43, 0, 1), - Mapping("DIFFICULTY", 0x44, 0, 1), - Mapping("GASLIMIT", 0x45, 0, 1), - Mapping("POP", 0x50, 1, 0), - Mapping("MLOAD", 0x51, 1, 1), - Mapping("MSTORE", 0x52, 2, 0), - Mapping("MSTORE8", 0x53, 2, 0), - Mapping("SLOAD", 0x54, 1, 1), - Mapping("SSTORE", 0x55, 2, 0), - Mapping("JUMP", 0x56, 1, 0), - Mapping("JUMPI", 0x57, 2, 0), - Mapping("PC", 0x58, 0, 1), - Mapping("MSIZE", 0x59, 0, 1), - Mapping("GAS", 0x5a, 0, 1), - Mapping("JUMPDEST", 0x5b, 0, 0), - Mapping("LOG0", 0xa0, 2, 0), - Mapping("LOG1", 0xa1, 3, 0), - Mapping("LOG2", 0xa2, 4, 0), - Mapping("LOG3", 0xa3, 5, 0), - Mapping("LOG4", 0xa4, 6, 0), - Mapping("CREATE", 0xf0, 3, 1), - Mapping("CALL", 0xf1, 7, 1), - Mapping("RETURN", 0xf2, 2, 0), - Mapping("CALL_CODE", 0xf3, 7, 1), - Mapping("SUICIDE", 0xff, 1, 0), - Mapping("---END---", 0x00, 0, 0), -}; +extern Mapping mapping[]; -std::map > opcodes; -std::map reverseOpcodes; +extern std::map > opcodes; +extern std::map reverseOpcodes; -// Fetches everything EXCEPT PUSH1..32 -std::pair > _opdata(std::string ops, int opi) { - if (!opcodes.size()) { - int i = 0; - while (mapping[i].op != "---END---") { - Mapping mi = mapping[i]; - opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out); - i++; - } - for (i = 1; i <= 16; i++) { - opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1); - opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1); - } - for (std::map >::iterator it=opcodes.begin(); - it != opcodes.end(); - it++) { - reverseOpcodes[(*it).second[0]] = (*it).first; - } - } - std::string op; - std::vector opdata; - op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : ""; - opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1); - return std::pair >(op, opdata); -} +std::pair > _opdata(std::string ops, int opi); + +int opcode(std::string op); + +int opinputs(std::string op); + +int opoutputs(std::string op); -int opcode(std::string op) { - return _opdata(op, -1).second[0]; -} +std::string op(int opcode); -int opinputs(std::string op) { - return _opdata(op, -1).second[1]; -} +extern std::string lllSpecials[][3]; -int opoutputs(std::string op) { - return _opdata(op, -1).second[2]; -} +extern std::map > lllMap; -std::string op(int opcode) { - return _opdata("", opcode).first; -} +bool isValidLLLFunc(std::string f, int argc); #endif diff --git a/libserpent/parser.cpp b/libserpent/parser.cpp index 4ceb1d12d..2b9d73702 100644 --- a/libserpent/parser.cpp +++ b/libserpent/parser.cpp @@ -12,17 +12,15 @@ int precedence(Node tok) { if (v == ".") return -1; else if (v == "!" || v == "not") return 1; else if (v=="^" || v == "**") return 2; - else if (v=="*" || v=="/" || v=="@/" || v=="%" || v=="@%") return 3; + else if (v=="*" || v=="/" || v=="%") return 3; else if (v=="+" || v=="-") return 4; else if (v=="<" || v==">" || v=="<=" || v==">=") return 5; - else if (v=="@<" || v=="@>" || v=="@<=" || v=="@>=") return 5; else if (v=="&" || v=="|" || v=="xor" || v=="==" || v == "!=") return 6; else if (v=="&&" || v=="and") return 7; else if (v=="||" || v=="or") return 8; - else if (v==":") return 9; else if (v=="=") return 10; else if (v=="+=" || v=="-=" || v=="*=" || v=="/=" || v=="%=") return 10; - else if (v=="@/=" || v=="@%=") return 10; + else if (v==":" || v == "::") return 11; else return 0; } @@ -223,8 +221,15 @@ Node treefy(std::vector stream) { filename = filename.substr(1, filename.length() - 2); if (!exists(root + filename)) err("File does not exist: "+root + filename, tok.metadata); - oq.back().args.pop_back(); - oq.back().args.push_back(parseSerpent(root + filename)); + if (v == "inset") { + oq.pop_back(); + oq.push_back(parseSerpent(root + filename)); + } + else { + oq.back().args.pop_back(); + oq.back().args.push_back( + asn("outer", parseSerpent(root + filename), tok.metadata)); + } } //Useful for debugging //for (int i = 0; i < oq.size(); i++) { @@ -237,7 +242,7 @@ Node treefy(std::vector stream) { err("Output blank", Metadata()); } else if (oq.size() > 1) { - err("Multiple expressions or unclosed bracket", oq[1].metadata); + return asn("multi", oq, oq[0].metadata); } return oq[0]; @@ -262,15 +267,9 @@ int spaceCount(std::string s) { bool bodied(std::string tok) { return tok == "if" || tok == "elif" || tok == "while" || tok == "with" || tok == "def" || tok == "extern" - || tok == "data"; -} - -// Is this a command that takes an argument as a child block? -bool childBlocked(std::string tok) { - return tok == "if" || tok == "elif" || tok == "else" - || tok == "code" || tok == "shared" || tok == "init" - || tok == "while" || tok == "repeat" || tok == "for" - || tok == "with" || tok == "def"; + || tok == "data" || tok == "assert" || tok == "return" + || tok == "fun" || tok == "scope" || tok == "macro" + || tok == "type"; } // Are the two commands meant to continue each other? @@ -278,10 +277,7 @@ bool bodiedContinued(std::string prev, std::string tok) { return (prev == "if" && tok == "elif") || (prev == "elif" && tok == "else") || (prev == "elif" && tok == "elif") - || (prev == "if" && tok == "else") - || (prev == "init" && tok == "code") - || (prev == "shared" && tok == "code") - || (prev == "shared" && tok == "init"); + || (prev == "if" && tok == "else"); } // Is a line of code empty? @@ -310,16 +306,17 @@ Node parseLines(std::vector lines, Metadata metadata, int sp) { } // Tokenize current line std::vector tokens = tokenize(main.substr(sp), metadata); - // Remove extraneous tokens, including if / elif + // Remove comments std::vector tokens2; for (unsigned j = 0; j < tokens.size(); j++) { if (tokens[j].val == "#" || tokens[j].val == "//") break; - if (j >= 1 || !bodied(tokens[j].val)) { - tokens2.push_back(tokens[j]); - } + tokens2.push_back(tokens[j]); } - if (tokens2.size() > 0 && tokens2.back().val == ":") + bool expectingChildBlock = false; + if (tokens2.size() > 0 && tokens2.back().val == ":") { tokens2.pop_back(); + expectingChildBlock = true; + } // Parse current line Node out = parseSerpentTokenStream(tokens2); // Parse child block @@ -343,14 +340,8 @@ Node parseLines(std::vector lines, Metadata metadata, int sp) { for (unsigned i = 0; i < childBlock.size(); i++) { if (childBlock[i].length() > 0) { cbe = false; break; } } - // Bring back if / elif into AST - if (bodied(tokens[0].val)) { - std::vector args; - args.push_back(out); - out = astnode(tokens[0].val, args, out.metadata); - } // Add child block to AST - if (childBlocked(tokens[0].val)) { + if (expectingChildBlock) { if (cbe) err("Expected indented child block!", out.metadata); out.type = ASTNODE; @@ -360,6 +351,37 @@ Node parseLines(std::vector lines, Metadata metadata, int sp) { } else if (!cbe) err("Did not expect indented child block!", out.metadata); + else if (out.args.size() && out.args[out.args.size() - 1].val == ":") { + Node n = out.args[out.args.size() - 1]; + out.args.pop_back(); + out.args.push_back(n.args[0]); + out.args.push_back(n.args[1]); + } + // Bring back if / elif into AST + if (bodied(tokens[0].val)) { + if (out.val != "multi") { + // token not being used in bodied form + } + else if (out.args[0].val == "id") + out = astnode(tokens[0].val, out.args[1].args, out.metadata); + else if (out.args[0].type == TOKEN) { + std::vector out2; + for (unsigned i = 1; i < out.args.size(); i++) + out2.push_back(out.args[i]); + out = astnode(tokens[0].val, out2, out.metadata); + } + else + out = astnode("fun", out.args, out.metadata); + } + // Multi not supported + if (out.val == "multi") + err("Multiple expressions or unclosed bracket", out.metadata); + // Convert top-level colon expressions into non-colon expressions; + // makes if statements and the like equivalent indented or not + //if (out.val == ":" && out.args[0].type == TOKEN) + // out = asn(out.args[0].val, out.args[1], out.metadata); + //if (bodied(tokens[0].val) && out.args[0].val == ":") + // out = asn(tokens[0].val, out.args[0].args); if (o.size() == 0 || o.back().type == TOKEN) { o.push_back(out); continue; diff --git a/libserpent/rewriter.cpp b/libserpent/rewriter.cpp index 443457acf..294c9a0b3 100644 --- a/libserpent/rewriter.cpp +++ b/libserpent/rewriter.cpp @@ -2,37 +2,25 @@ #include #include #include -#include #include "util.h" #include "lllparser.h" #include "bignum.h" +#include "optimize.h" +#include "rewriteutils.h" +#include "preprocess.h" +#include "functions.h" +#include "opcodes.h" -std::string valid[][3] = { - { "if", "2", "3" }, - { "unless", "2", "2" }, - { "while", "2", "2" }, - { "until", "2", "2" }, - { "alloc", "1", "1" }, - { "array", "1", "1" }, - { "call", "2", tt256 }, - { "call_code", "2", tt256 }, - { "create", "1", "4" }, - { "getch", "2", "2" }, - { "setch", "3", "3" }, - { "sha3", "1", "2" }, - { "return", "1", "2" }, - { "inset", "1", "1" }, - { "min", "2", "2" }, - { "max", "2", "2" }, - { "array_lit", "0", tt256 }, - { "seq", "0", tt256 }, - { "log", "1", "6" }, - { "outer", "1", "1" }, - { "set", "2", "2" }, - { "---END---", "", "" } //Keep this line at the end of the list -}; - +// Rewrite rules std::string macros[][2] = { + { + "(seq $x)", + "$x" + }, + { + "(seq (seq) $x)", + "$x" + }, { "(+= $a $b)", "(set $a (+ $a $b))" @@ -58,24 +46,28 @@ std::string macros[][2] = { "(set $a (^ $a $b))" }, { - "(@/= $a $b)", - "(set $a (@/ $a $b))" + "(!= $a $b)", + "(iszero (eq $a $b))" }, { - "(@%= $a $b)", - "(set $a (@% $a $b))" + "(assert $x)", + "(unless $x (stop))" }, { - "(!= $a $b)", - "(iszero (eq $a $b))" + "(min $a $b)", + "(with $1 $a (with $2 $b (if (lt $1 $2) $1 $2)))" + }, + { + "(max $a $b)", + "(with $1 $a (with $2 $b (if (lt $1 $2) $2 $1)))" }, { - "(min a b)", - "(with $1 a (with $2 b (if (lt $1 $2) $1 $2)))" + "(smin $a $b)", + "(with $1 $a (with $2 $b (if (slt $1 $2) $1 $2)))" }, { - "(max a b)", - "(with $1 a (with $2 b (if (lt $1 $2) $2 $1)))" + "(smax $a $b)", + "(with $1 $a (with $2 $b (if (slt $1 $2) $2 $1)))" }, { "(if $cond $do (else $else))", @@ -85,10 +77,6 @@ std::string macros[][2] = { "(code $code)", "$code" }, - { - "(access (. msg data) $ind)", - "(calldataload (mul 32 $ind))" - }, { "(slice $arr $pos)", "(add $arr (mul 32 $pos))", @@ -125,13 +113,17 @@ std::string macros[][2] = { "(set (access (. self storage) $ind) $val)", "(sstore $ind $val)" }, + { + "(set (sload $ind) $val)", + "(sstore $ind $val)" + }, { "(set (access $var $ind) $val)", "(mstore (add $var (mul 32 $ind)) $val)" }, { "(getch $var $ind)", - "(mod (mload (add $var $ind)) 256)" + "(mod (mload (sub (add $var $ind) 31)) 256)" }, { "(setch $var $ind $val)", @@ -149,6 +141,10 @@ std::string macros[][2] = { "(sha3 $x)", "(seq (set $1 $x) (~sha3 (ref $1) 32))" }, + { + "(sha3 $mstart (= chars $msize))", + "(~sha3 $mstart $msize)" + }, { "(sha3 $mstart $msize)", "(~sha3 $mstart (mul 32 $msize))" @@ -161,6 +157,10 @@ std::string macros[][2] = { "(return $x)", "(seq (set $1 $x) (~return (ref $1) 32))" }, + { + "(return $mstart (= chars $msize))", + "(~return $mstart $msize)" + }, { "(return $start $len)", "(~return $start (mul 32 $len))" @@ -171,7 +171,7 @@ std::string macros[][2] = { }, { "(|| $x $y)", - "(with $1 $x (if (get $1) (get $1) $y))" + "(with $1 $x (if $1 $1 $y))" }, { "(>= $x $y)", @@ -181,45 +181,41 @@ std::string macros[][2] = { "(<= $x $y)", "(iszero (sgt $x $y))" }, - { - "(@>= $x $y)", - "(iszero (lt $x $y))" - }, - { - "(@<= $x $y)", - "(iszero (gt $x $y))" - }, { "(create $code)", "(create 0 $code)" }, { "(create $endowment $code)", - "(with $1 (msize) (create $endowment (get $1) (lll (outer $code) (msize))))" + "(with $1 (msize) (create $endowment (get $1) (lll $code (msize))))" }, { "(sha256 $x)", - "(seq (set $1 $x) (pop (~call 101 2 0 (ref $1) 32 (ref $2) 32)) (get $2))" + "(with $1 (alloc 64) (seq (mstore (add (get $1) 32) $x) (pop (~call 101 2 0 (add (get $1) 32) 32 (get $1) 32)) (mload (get $1))))" + }, + { + "(sha256 $arr (= chars $sz))", + "(with $1 (alloc 32) (seq (pop (~call 101 2 0 $arr $sz (get $1) 32)) (mload (get $1))))" }, { "(sha256 $arr $sz)", - "(seq (pop (~call 101 2 0 $arr (mul 32 $sz) (ref $2) 32)) (get $2))" + "(with $1 (alloc 32) (seq (pop (~call 101 2 0 $arr (mul 32 $sz) (get $1) 32)) (mload (get $1))))" }, { "(ripemd160 $x)", - "(seq (set $1 $x) (pop (~call 101 3 0 (ref $1) 32 (ref $2) 32)) (get $2))" + "(with $1 (alloc 64) (seq (mstore (add (get $1) 32) $x) (pop (~call 101 3 0 (add (get $1) 32) 32 (get $1) 32)) (mload (get $1))))" }, { - "(ripemd160 $arr $sz)", - "(seq (pop (~call 101 3 0 $arr (mul 32 $sz) (ref $2) 32)) (get $2))" + "(ripemd160 $arr (= chars $sz))", + "(with $1 (alloc 32) (seq (pop (~call 101 3 0 $arr $sz (mload $1) 32)) (mload (get $1))))" }, { - "(ecrecover $h $v $r $s)", - "(seq (declare $1) (declare $2) (declare $3) (declare $4) (set $1 $h) (set $2 $v) (set $3 $r) (set $4 $s) (pop (~call 101 1 0 (ref $1) 128 (ref $5) 32)) (get $5))" + "(ripemd160 $arr $sz)", + "(with $1 (alloc 32) (seq (pop (~call 101 3 0 $arr (mul 32 $sz) (get $1) 32)) (mload (get $1))))" }, { - "(seq (seq) $x)", - "$x" + "(ecrecover $h $v $r $s)", + "(with $1 (alloc 160) (seq (mstore (get $1) $h) (mstore (add (get $1) 32) $v) (mstore (add (get $1) 64) $r) (mstore (add (get $1) 96) $s) (pop (~call 101 1 0 (get $1) 128 (add (get $1 128)) 32)) (mload (add (get $1) 128))))" }, { "(inset $x)", @@ -235,21 +231,64 @@ std::string macros[][2] = { }, { "(log $t1)", - "(~log1 $t1 0 0)" + "(~log1 0 0 $t1)" }, { "(log $t1 $t2)", - "(~log2 $t1 $t2 0 0)" + "(~log2 0 0 $t1 $t2)" }, { "(log $t1 $t2 $t3)", - "(~log3 $t1 $t2 $t3 0 0)" + "(~log3 0 0 $t1 $t2 $t3)" }, { "(log $t1 $t2 $t3 $t4)", - "(~log4 $t1 $t2 $t3 $t4 0 0)" + "(~log4 0 0 $t1 $t2 $t3 $t4)" + }, + { + "(logarr $a $sz)", + "(~log0 $a (mul 32 $sz))" + }, + { + "(logarr $a $sz $t1)", + "(~log1 $a (mul 32 $sz) $t1)" + }, + { + "(logarr $a $sz $t1 $t2)", + "(~log2 $a (mul 32 $sz) $t1 $t2)" + }, + { + "(logarr $a $sz $t1 $t2 $t3)", + "(~log3 $a (mul 32 $sz) $t1 $t2 $t3)" + }, + { + "(logarr $a $sz $t1 $t2 $t3 $t4)", + "(~log4 $a (mul 32 $sz) $t1 $t2 $t3 $t4)" + }, + { + "(save $loc $array (= chars $count))", + "(with $location (ref $loc) (with $c $count (with $end (div $c 32) (with $i 0 (seq (while (slt $i $end) (seq (sstore (add $i $location) (access $array $i)) (set $i (add $i 1)))) (sstore (add $i $location) (~and (access $array $i) (sub 0 (exp 256 (sub 32 (mod $c 32)))))))))))" + }, + { + "(save $loc $array $count)", + "(with $location (ref $loc) (with $end $count (with $i 0 (while (slt $i $end) (seq (sstore (add $i $location) (access $array $i)) (set $i (add $i 1)))))))" + }, + { + "(load $loc (= chars $count))", + "(with $location (ref $loc) (with $c $count (with $a (alloc $c) (with $i 0 (seq (while (slt $i (div $c 32)) (seq (set (access $a $i) (sload (add $location $i))) (set $i (add $i 1)))) (set (access $a $i) (~and (sload (add $location $i)) (sub 0 (exp 256 (sub 32 (mod $c 32)))))) $a)))))" + }, + { + "(load $loc $count)", + "(with $location (ref $loc) (with $c $count (with $a (alloc $c) (with $i 0 (seq (while (slt $i $c) (seq (set (access $a $i) (sload (add $location $i))) (set $i (add $i 1)))) $a)))))" + }, + { + "(unsafe_mcopy $to $from $sz)", + "(with _sz $sz (with _from $from (with _to $to (seq (comment STARTING UNSAFE MCOPY) (with _i 0 (while (lt _i _sz) (seq (mstore (add $to _i) (mload (add _from _i))) (set _i (add _i 32)))))))))" + }, + { + "(mcopy $to $from $_sz)", + "(with _to $to (with _from $from (with _sz $sz (seq (comment STARTING MCOPY (with _i 0 (seq (while (lt (add _i 31) _sz) (seq (mstore (add _to _i) (mload (add _from _i))) (set _i (add _i 32)))) (with _mask (exp 256 (sub 32 (mod _sz 32))) (mstore (add $to _i) (add (mod (mload (add $to _i)) _mask) (and (mload (add $from _i)) (sub 0 _mask))))))))))))" }, - { "(. msg datasize)", "(div (calldatasize) 32)" }, { "(. msg sender)", "(caller)" }, { "(. msg value)", "(callvalue)" }, { "(. tx gasprice)", "(gasprice)" }, @@ -267,8 +306,7 @@ std::string macros[][2] = { { "---END---", "" } //Keep this line at the end of the list }; -std::vector > nodeMacros; - +// Token synonyms std::string synonyms[][2] = { { "or", "||" }, { "and", "&&" }, @@ -286,10 +324,6 @@ std::string synonyms[][2] = { { "^", "exp" }, { "**", "exp" }, { "%", "smod" }, - { "@/", "div" }, - { "@%", "mod" }, - { "@<", "lt" }, - { "@>", "gt" }, { "<", "slt" }, { ">", "sgt" }, { "=", "set" }, @@ -298,6 +332,10 @@ std::string synonyms[][2] = { { "---END---", "" } //Keep this line at the end of the list }; +std::map synonymMap; + +// Custom setters (need to be registered separately +// for use with managed storage) std::string setters[][2] = { { "+=", "+" }, { "-=", "-" }, @@ -305,550 +343,136 @@ std::string setters[][2] = { { "/=", "/" }, { "%=", "%" }, { "^=", "^" }, - { "!=", "!" }, { "---END---", "" } //Keep this line at the end of the list }; -// Match result storing object -struct matchResult { - bool success; - std::map map; -}; - -// Storage variable index storing object -struct svObj { - std::map offsets; - std::map indices; - std::map > coefficients; - std::map nonfinal; - std::string globalOffset; -}; - -// Preprocessing result storing object -class preprocessAux { - public: - preprocessAux() { - globalExterns = std::map(); - localExterns = std::map >(); - localExterns["self"] = std::map(); - } - std::map globalExterns; - std::map > localExterns; - svObj storageVars; -}; - -#define preprocessResult std::pair - -// Main pattern matching routine, for those patterns that can be expressed -// using our standard mini-language above -// -// Returns two values. First, a boolean to determine whether the node matches -// the pattern, second, if the node does match then a map mapping variables -// in the pattern to nodes -matchResult match(Node p, Node n) { - matchResult o; - o.success = false; - if (p.type == TOKEN) { - if (p.val == n.val && n.type == TOKEN) o.success = true; - else if (p.val[0] == '$') { - o.success = true; - o.map[p.val.substr(1)] = n; - } - } - else if (n.type==TOKEN || p.val!=n.val || p.args.size()!=n.args.size()) { - // do nothing - } - else { - for (unsigned i = 0; i < p.args.size(); i++) { - matchResult oPrime = match(p.args[i], n.args[i]); - if (!oPrime.success) { - o.success = false; - return o; - } - for (std::map::iterator it = oPrime.map.begin(); - it != oPrime.map.end(); - it++) { - o.map[(*it).first] = (*it).second; - } - } - o.success = true; - } - return o; -} - -// Fills in the pattern with a dictionary mapping variable names to -// nodes (these dicts are generated by match). Match and subst together -// create a full pattern-matching engine. -Node subst(Node pattern, - std::map dict, - std::string varflag, - Metadata metadata) { - if (pattern.type == TOKEN && pattern.val[0] == '$') { - if (dict.count(pattern.val.substr(1))) { - return dict[pattern.val.substr(1)]; - } - else { - return token(varflag + pattern.val.substr(1), metadata); - } - } - else if (pattern.type == TOKEN) { - return pattern; - } - else { - std::vector args; - for (unsigned i = 0; i < pattern.args.size(); i++) { - args.push_back(subst(pattern.args[i], dict, varflag, metadata)); - } - return astnode(pattern.val, args, metadata); - } -} +std::map setterMap; // Processes mutable array literals - Node array_lit_transform(Node node) { + std::string prefix = "_temp"+mkUniqueToken() + "_"; Metadata m = node.metadata; - std::vector o1; - o1.push_back(token(unsignedToDecimal(node.args.size() * 32), m)); - std::vector o2; - std::string symb = "_temp"+mkUniqueToken()+"_0"; - o2.push_back(token(symb, m)); - o2.push_back(astnode("alloc", o1, m)); - std::vector o3; - o3.push_back(astnode("set", o2, m)); + std::map d; + std::string o = "(seq (set $arr (alloc "+utd(node.args.size()*32)+"))"; for (unsigned i = 0; i < node.args.size(); i++) { - std::vector o5; - o5.push_back(token(symb, m)); - std::vector o6; - o6.push_back(astnode("get", o5, m)); - o6.push_back(token(unsignedToDecimal(i * 32), m)); - std::vector o7; - o7.push_back(astnode("add", o6)); - o7.push_back(node.args[i]); - o3.push_back(astnode("mstore", o7, m)); - } - std::vector o8; - o8.push_back(token(symb, m)); - o3.push_back(astnode("get", o8)); - return astnode("seq", o3, m); -} - -// Is the given node something of the form -// self.cow -// self.horse[0] -// self.a[6][7][self.storage[3]].chicken[9] -bool isNodeStorageVariable(Node node) { - std::vector nodez; - nodez.push_back(node); - while (1) { - if (nodez.back().type == TOKEN) return false; - if (nodez.back().args.size() == 0) return false; - if (nodez.back().val != "." && nodez.back().val != "access") - return false; - if (nodez.back().args[0].val == "self") return true; - nodez.push_back(nodez.back().args[0]); + o += " (mstore (add (get $arr) "+utd(i * 32)+") $"+utd(i)+")"; + d[utd(i)] = node.args[i]; } + o += " (get $arr))"; + return subst(parseLLL(o), d, prefix, m); } -Node optimize(Node inp); - -Node apply_rules(preprocessResult pr); - -// Convert: -// self.cow -> ["cow"] -// self.horse[0] -> ["horse", "0"] -// self.a[6][7][self.storage[3]].chicken[9] -> -// ["6", "7", (sload 3), "chicken", "9"] -std::vector listfyStorageAccess(Node node) { - std::vector out; - std::vector nodez; - nodez.push_back(node); - while (1) { - if (nodez.back().type == TOKEN) { - out.push_back(token("--" + nodez.back().val, node.metadata)); - std::vector outrev; - for (int i = (signed)out.size() - 1; i >= 0; i--) { - outrev.push_back(out[i]); +// Processes long text literals +Node string_transform(Node node) { + Metadata m = node.metadata; + if (!node.args.size()) + err("Empty text!", m); + if (node.args[0].val.size() < 2 + || node.args[0].val[0] != '"' + || node.args[0].val[node.args[0].val.size() - 1] != '"') + err("Text contents don't look like a string!", m); + std::string bin = node.args[0].val.substr(1, node.args[0].val.size() - 2); + unsigned sz = bin.size(); + std::vector o; + for (unsigned i = 0; i < sz; i += 32) { + std::string t = binToNumeric(bin.substr(i, 32)); + if ((sz - i) < 32 && (sz - i) > 0) { + while ((sz - i) < 32) { + t = decimalMul(t, "256"); + i--; } - return outrev; + i = sz; } - if (nodez.back().val == ".") - nodez.back().args[1].val = "--" + nodez.back().args[1].val; - if (nodez.back().args.size() == 0) - err("Error parsing storage variable statement", node.metadata); - if (nodez.back().args.size() == 1) - out.push_back(token(tt256m1, node.metadata)); - else - out.push_back(nodez.back().args[1]); - nodez.push_back(nodez.back().args[0]); + o.push_back(token(t, node.metadata)); } + node = astnode("array_lit", o, node.metadata); + return array_lit_transform(node); } -// Cool function for debug purposes (named cerrStringList to make -// all prints searchable via 'cerr') -void cerrStringList(std::vector s, std::string suffix="") { - for (unsigned i = 0; i < s.size(); i++) std::cerr << s[i] << " "; - std::cerr << suffix << "\n"; -} -// Populate an svObj with the arguments needed to determine -// the storage position of a node -svObj getStorageVars(svObj pre, Node node, std::string prefix="", int index=0) { - Metadata m = node.metadata; - if (!pre.globalOffset.size()) pre.globalOffset = "0"; - std::vector h; - std::vector coefficients; - // Array accesses or atoms - if (node.val == "access" || node.type == TOKEN) { - std::string tot = "1"; - h = listfyStorageAccess(node); - coefficients.push_back("1"); - for (unsigned i = h.size() - 1; i >= 1; i--) { - // Array sizes must be constant or at least arithmetically - // evaluable at compile time - h[i] = optimize(apply_rules(preprocessResult( - h[i], preprocessAux()))); - if (!isNumberLike(h[i])) - err("Array size must be fixed value", m); - // Create a list of the coefficient associated with each - // array index - coefficients.push_back(decimalMul(coefficients.back(), h[i].val)); - } - } - // Tuples - else { - int startc; - // Handle the (fun args...) case - if (node.val == "fun") { - startc = 1; - h = listfyStorageAccess(node.args[0]); - } - // Handle the ( args...) case, which - // the serpent parser produces when the function - // is a simple name and not a complex astnode - else { - startc = 0; - h = listfyStorageAccess(token(node.val, m)); - } - svObj sub = pre; - sub.globalOffset = "0"; - // Evaluate tuple elements recursively - for (unsigned i = startc; i < node.args.size(); i++) { - sub = getStorageVars(sub, - node.args[i], - prefix+h[0].val.substr(2)+".", - i-1); - } - coefficients.push_back(sub.globalOffset); - for (unsigned i = h.size() - 1; i >= 1; i--) { - // Array sizes must be constant or at least arithmetically - // evaluable at compile time - h[i] = optimize(apply_rules(preprocessResult( - h[i], preprocessAux()))); - if (!isNumberLike(h[i])) - err("Array size must be fixed value", m); - // Create a list of the coefficient associated with each - // array index - coefficients.push_back(decimalMul(coefficients.back(), h[i].val)); - } - pre.offsets = sub.offsets; - pre.coefficients = sub.coefficients; - pre.nonfinal = sub.nonfinal; - pre.nonfinal[prefix+h[0].val.substr(2)] = true; - } - pre.coefficients[prefix+h[0].val.substr(2)] = coefficients; - pre.offsets[prefix+h[0].val.substr(2)] = pre.globalOffset; - pre.indices[prefix+h[0].val.substr(2)] = index; - if (decimalGt(tt176, coefficients.back())) - pre.globalOffset = decimalAdd(pre.globalOffset, coefficients.back()); - return pre; -} +Node apply_rules(preprocessResult pr); -// Transform a node of the form (call to funid vars...) into +// Transform ".(args...)" into // a call - -#define psn std::pair - -Node call_transform(Node node, std::string op) { +Node dotTransform(Node node, preprocessAux aux) { Metadata m = node.metadata; // We're gonna make lots of temporary variables, // so set up a unique flag for them std::string prefix = "_temp"+mkUniqueToken()+"_"; + // Check that the function name is a token + if (node.args[0].args[1].type == ASTNODE) + err("Function name must be static", m); + + Node dotOwner = node.args[0].args[0]; + std::string dotMember = node.args[0].args[1].val; // kwargs = map of special arguments std::map kwargs; kwargs["value"] = token("0", m); - kwargs["gas"] = parseLLL("(- (gas) 25)"); - std::vector args; - for (unsigned i = 0; i < node.args.size(); i++) { - if (node.args[i].val == "=" || node.args[i].val == "set") { - if (node.args[i].args.size() != 2) - err("Malformed set", m); - kwargs[node.args[i].args[0].val] = node.args[i].args[1]; - } - else args.push_back(node.args[i]); - } - if (args.size() < 2) err("Too few arguments for call!", m); - kwargs["to"] = args[0]; - kwargs["funid"] = args[1]; - std::vector inputs; - for (unsigned i = 2; i < args.size(); i++) { - inputs.push_back(args[i]); - } - std::vector with; - std::vector precompute; - std::vector post; - if (kwargs.count("data")) { - if (!kwargs.count("datasz")) err("Required param datasz", m); - // The strategy here is, we store the function ID byte at the index - // before the start of the byte, but then we store the value that was - // there before and reinstate it once the process is over - // store data: data array start - with.push_back(psn(prefix+"data", kwargs["data"])); - // store data: prior: data array - 32 - Node prior = astnode("sub", token(prefix+"data", m), token("32", m), m); - with.push_back(psn(prefix+"prior", prior)); - // store data: priormem: data array - 32 prior memory value - Node priormem = astnode("mload", token(prefix+"prior", m), m); - with.push_back(psn(prefix+"priormem", priormem)); - // post: reinstate prior mem at data array - 32 - post.push_back(astnode("mstore", - token(prefix+"prior", m), - token(prefix+"priormem", m), - m)); - // store data: datastart: data array - 1 - Node datastart = astnode("sub", - token(prefix+"data", m), - token("1", m), - m); - with.push_back(psn(prefix+"datastart", datastart)); - // push funid byte to datastart - precompute.push_back(astnode("mstore8", - token(prefix+"datastart", m), - kwargs["funid"], - m)); - // set data array start loc - kwargs["datain"] = token(prefix+"datastart", m); - kwargs["datainsz"] = astnode("add", - token("1", m), - astnode("mul", - token("32", m), - kwargs["datasz"], - m), - m); - } - else { - // Here, there is no data array, instead there are function arguments. - // This actually lets us be much more efficient with how we set things - // up. - // Pre-declare variables; relies on declared variables being sequential - precompute.push_back(astnode("declare", - token(prefix+"prebyte", m), - m)); - for (unsigned i = 0; i < inputs.size(); i++) { - precompute.push_back(astnode("declare", - token(prefix+unsignedToDecimal(i), m), - m)); - } - // Set up variables to store the function arguments, and store the - // function ID at the byte before the start - Node datastart = astnode("add", - token("31", m), - astnode("ref", - token(prefix+"prebyte", m), - m), - m); - precompute.push_back(astnode("mstore8", - datastart, - kwargs["funid"], - m)); - for (unsigned i = 0; i < inputs.size(); i++) { - precompute.push_back(astnode("set", - token(prefix+unsignedToDecimal(i), m), - inputs[i], - m)); - - } - kwargs["datain"] = datastart; - kwargs["datainsz"] = token(unsignedToDecimal(inputs.size()*32+1), m); - } - if (!kwargs.count("outsz")) { - kwargs["dataout"] = astnode("ref", token(prefix+"dataout", m), m); - kwargs["dataoutsz"] = token("32", node.metadata); - post.push_back(astnode("get", token(prefix+"dataout", m), m)); - } - else { - kwargs["dataout"] = kwargs["out"]; - kwargs["dataoutsz"] = kwargs["outsz"]; - post.push_back(astnode("ref", token(prefix+"dataout", m), m)); - } - // Set up main call - std::vector main; - for (unsigned i = 0; i < precompute.size(); i++) { - main.push_back(precompute[i]); - } - std::vector call; - call.push_back(kwargs["gas"]); - call.push_back(kwargs["to"]); - call.push_back(kwargs["value"]); - call.push_back(kwargs["datain"]); - call.push_back(kwargs["datainsz"]); - call.push_back(kwargs["dataout"]); - call.push_back(kwargs["dataoutsz"]); - main.push_back(astnode("pop", astnode("~"+op, call, m), m)); - for (unsigned i = 0; i < post.size(); i++) { - main.push_back(post[i]); - } - Node mainNode = astnode("seq", main, node.metadata); - // Add with variables - for (int i = with.size() - 1; i >= 0; i--) { - mainNode = astnode("with", - token(with[i].first, m), - with[i].second, - mainNode, - m); - } - return mainNode; -} - -// Preprocess input containing functions -// -// localExterns is a map of the form, eg, -// -// { x: { foo: 0, bar: 1, baz: 2 }, y: { qux: 0, foo: 1 } ... } -// -// Signifying that x.foo = 0, x.baz = 2, y.foo = 1, etc -// -// globalExterns is a one-level map, eg from above -// -// { foo: 1, bar: 1, baz: 2, qux: 0 } -// -// Note that globalExterns may be ambiguous -preprocessResult preprocess(Node inp) { - inp = inp.args[0]; - Metadata m = inp.metadata; - if (inp.val != "seq") { - std::vector args; - args.push_back(inp); - inp = astnode("seq", args, m); - } - std::vector empty; - Node init = astnode("seq", empty, m); - Node shared = astnode("seq", empty, m); - std::vector any; - std::vector functions; - preprocessAux out = preprocessAux(); - out.localExterns["self"] = std::map(); - int functionCount = 0; - int storageDataCount = 0; - for (unsigned i = 0; i < inp.args.size(); i++) { - Node obj = inp.args[i]; - // Functions - if (obj.val == "def") { - if (obj.args.size() == 0) - err("Empty def", m); - std::string funName = obj.args[0].val; - // Init, shared and any are special functions - if (funName == "init" || funName == "shared" || funName == "any") { - if (obj.args[0].args.size()) - err(funName+" cannot have arguments", m); - } - if (funName == "init") init = obj.args[1]; - else if (funName == "shared") shared = obj.args[1]; - else if (funName == "any") any.push_back(obj.args[1]); - else { - // Other functions - functions.push_back(obj); - out.localExterns["self"][obj.args[0].val] = functionCount; - functionCount++; - } - } - // Extern declarations - else if (obj.val == "extern") { - std::string externName = obj.args[0].args[0].val; - Node al = obj.args[0].args[1]; - if (!out.localExterns.count(externName)) - out.localExterns[externName] = std::map(); - for (unsigned i = 0; i < al.args.size(); i++) { - out.globalExterns[al.args[i].val] = i; - out.localExterns[externName][al.args[i].val] = i; - } - } - // Storage variables/structures - else if (obj.val == "data") { - out.storageVars = getStorageVars(out.storageVars, - obj.args[0], - "", - storageDataCount); - storageDataCount += 1; - } - else any.push_back(obj); - } - std::vector main; - if (shared.args.size()) main.push_back(shared); - if (init.args.size()) main.push_back(init); - - std::vector code; - if (shared.args.size()) code.push_back(shared); - for (unsigned i = 0; i < any.size(); i++) - code.push_back(any[i]); - for (unsigned i = 0; i < functions.size(); i++) - code.push_back(functions[i]); - main.push_back(astnode("~return", - token("0", m), - astnode("lll", - astnode("seq", code, m), - token("0", m), - m), - m)); - - - - return preprocessResult(astnode("seq", main, inp.metadata), out); -} - -// Transform ".(args...)" into -// (call args...) -Node dotTransform(Node node, preprocessAux aux) { - Metadata m = node.metadata; - Node pre = node.args[0].args[0]; - std::string post = node.args[0].args[1].val; - if (node.args[0].args[1].type == ASTNODE) - err("Function name must be static", m); - // Search for as=? and call=code keywords + kwargs["gas"] = subst(parseLLL("(- (gas) 25)"), msn(), prefix, m); + // Search for as=? and call=code keywords, and isolate the actual + // function arguments + std::vector fnargs; std::string as = ""; - bool call_code = false; + std::string op = "call"; for (unsigned i = 1; i < node.args.size(); i++) { - Node arg = node.args[i]; + fnargs.push_back(node.args[i]); + Node arg = fnargs.back(); if (arg.val == "=" || arg.val == "set") { if (arg.args[0].val == "as") as = arg.args[1].val; if (arg.args[0].val == "call" && arg.args[1].val == "code") - call_code = true; + op = "callcode"; + if (arg.args[0].val == "gas") + kwargs["gas"] = arg.args[1]; + if (arg.args[0].val == "value") + kwargs["value"] = arg.args[1]; + if (arg.args[0].val == "outsz") + kwargs["outsz"] = arg.args[1]; } } - if (pre.val == "self") { + if (dotOwner.val == "self") { if (as.size()) err("Cannot use \"as\" when calling self!", m); - as = pre.val; + as = dotOwner.val; } - std::vector args; - args.push_back(pre); - // Determine the funId assuming the "as" keyword was used + // Determine the funId and sig assuming the "as" keyword was used + int funId = 0; + std::string sig; if (as.size() > 0 && aux.localExterns.count(as)) { - if (!aux.localExterns[as].count(post)) - err("Invalid call: "+printSimple(pre)+"."+post, m); - std::string funid = unsignedToDecimal(aux.localExterns[as][post]); - args.push_back(token(funid, m)); + if (!aux.localExterns[as].count(dotMember)) + err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m); + funId = aux.localExterns[as][dotMember]; + sig = aux.localExternSigs[as][dotMember]; } - // Determine the funId otherwise + // Determine the funId and sig otherwise else if (!as.size()) { - if (!aux.globalExterns.count(post)) - err("Invalid call: "+printSimple(pre)+"."+post, m); - std::string key = unsignedToDecimal(aux.globalExterns[post]); - args.push_back(token(key, m)); + if (!aux.globalExterns.count(dotMember)) + err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m); + std::string key = unsignedToDecimal(aux.globalExterns[dotMember]); + funId = aux.globalExterns[dotMember]; + sig = aux.globalExternSigs[dotMember]; + } + else err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m); + // Pack arguments + kwargs["data"] = packArguments(fnargs, sig, funId, m); + kwargs["to"] = dotOwner; + Node main; + // Pack output + if (!kwargs.count("outsz")) { + main = parseLLL( + "(with _data $data (seq " + "(pop (~"+op+" $gas $to $value (access _data 0) (access _data 1) (ref $dataout) 32))" + "(get $dataout)))"); + } + else { + main = parseLLL( + "(with _data $data (with _outsz (mul 32 $outsz) (with _out (alloc _outsz) (seq " + "(pop (~"+op+" $gas $to $value (access _data 0) (access _data 1) _out _outsz))" + "(get _out)))))"); } - else err("Invalid call: "+printSimple(pre)+"."+post, m); - for (unsigned i = 1; i < node.args.size(); i++) - args.push_back(node.args[i]); - return astnode(call_code ? "call_code" : "call", args, m); + // Set up main call + + Node o = subst(main, kwargs, prefix, m); + return o; } // Transform an access of the form self.bob, self.users[5], etc into @@ -877,7 +501,8 @@ Node dotTransform(Node node, preprocessAux aux) { // obj2[0].a -> sha3([1, 0, 0]) // obj2[5].b[1][3] -> sha3([1, 5, 1, 1, 3]) // obj2[45].c -> sha3([1, 45, 2]) -Node storageTransform(Node node, preprocessAux aux, bool mapstyle=false) { +Node storageTransform(Node node, preprocessAux aux, + bool mapstyle=false, bool ref=false) { Metadata m = node.metadata; // Get a list of all of the "access parameters" used in order // eg. self.users[5].cow[4][m[2]][woof] -> @@ -909,7 +534,7 @@ Node storageTransform(Node node, preprocessAux aux, bool mapstyle=false) { // If the size of an object exceeds 2^176, we make it an infinite // array if (decimalGt(coefficients.back(), tt176) && !mapstyle) - return storageTransform(node, aux, true); + return storageTransform(node, aux, true, ref); offset = decimalAdd(offset, aux.storageVars.offsets[tempPrefix]); c = 0; if (mapstyle) @@ -940,28 +565,29 @@ Node storageTransform(Node node, preprocessAux aux, bool mapstyle=false) { if (c > (signed)coefficients.size() - 1) { err("Too many array index lookups", m); } + Node o; if (mapstyle) { - // We pre-declare variables, relying on the idea that sequentially - // declared variables are doing to appear beside each other in - // memory - std::vector main; + std::string t = "_temp_"+mkUniqueToken(); + std::vector sub; for (unsigned i = 0; i < terms.size(); i++) - main.push_back(astnode("declare", - token(varPrefix+unsignedToDecimal(i), m), - m)); - for (unsigned i = 0; i < terms.size(); i++) - main.push_back(astnode("set", - token(varPrefix+unsignedToDecimal(i), m), - terms[i], - m)); - main.push_back(astnode("ref", token(varPrefix+"0", m), m)); - Node sz = token(unsignedToDecimal(terms.size()), m); - return astnode("sload", - astnode("sha3", - astnode("seq", main, m), - sz, - m), - m); + sub.push_back(asn("mstore", + asn("add", + tkn(utd(i * 32), m), + asn("get", tkn(t+"pos", m), m), + m), + terms[i], + m)); + sub.push_back(tkn(t+"pos", m)); + Node main = asn("with", + tkn(t+"pos", m), + asn("alloc", tkn(utd(terms.size() * 32), m), m), + asn("seq", sub, m), + m); + Node sz = token(utd(terms.size() * 32), m); + o = astnode("~sha3", + main, + sz, + m); } else { // We add up all the index*coefficients @@ -972,42 +598,92 @@ Node storageTransform(Node node, preprocessAux aux, bool mapstyle=false) { temp.push_back(terms[i]); out = astnode("add", temp, node.metadata); } - std::vector temp2; - temp2.push_back(out); - return astnode("sload", temp2, node.metadata); + o = out; + } + if (ref) return o; + else return astnode("sload", o, node.metadata); +} + +// Basic rewrite rule execution +std::pair rulesTransform(Node node, rewriteRuleSet macros) { + std::string prefix = "_temp_"+mkUniqueToken(); + bool changed = false; + if (!macros.ruleLists.count(node.val)) + return std::pair(node, false); + std::vector rules = macros.ruleLists[node.val]; + for (unsigned pos = 0; pos < rules.size(); pos++) { + rewriteRule macro = rules[pos]; + matchResult mr = match(macro.pattern, node); + if (mr.success) { + node = subst(macro.substitution, mr.map, prefix, node.metadata); + std::pair o = rulesTransform(node, macros); + o.second = true; + return o; + } + } + return std::pair(node, changed); +} + +std::pair synonymTransform(Node node) { + bool changed = false; + if (node.type == ASTNODE && synonymMap.count(node.val)) { + node.val = synonymMap[node.val]; + changed = true; } + return std::pair(node, changed); } +rewriteRuleSet nodeMacros; +rewriteRuleSet setterMacros; -// Recursively applies rewrite rules -Node apply_rules(preprocessResult pr) { +bool dontDescend(std::string s) { + return s == "macro" || s == "comment" || s == "outer"; +} + +// Recursively applies any set of rewrite rules +std::pair apply_rules_iter(preprocessResult pr, rewriteRuleSet rules) { + bool changed = false; Node node = pr.first; - // If the rewrite rules have not yet been parsed, parse them - if (!nodeMacros.size()) { - for (int i = 0; i < 9999; i++) { - std::vector o; - if (macros[i][0] == "---END---") break; - o.push_back(parseLLL(macros[i][0])); - o.push_back(parseLLL(macros[i][1])); - nodeMacros.push_back(o); + if (dontDescend(node.val)) + return std::pair(node, false); + std::pair o = rulesTransform(node, rules); + node = o.first; + changed = changed || o.second; + if (node.type == ASTNODE) { + for (unsigned i = 0; i < node.args.size(); i++) { + std::pair r = + apply_rules_iter(preprocessResult(node.args[i], pr.second), rules); + node.args[i] = r.first; + changed = changed || r.second; } } - // Assignment transformations - for (int i = 0; i < 9999; i++) { - if (setters[i][0] == "---END---") break; - if (node.val == setters[i][0]) { - node = astnode("=", - node.args[0], - astnode(setters[i][1], - node.args[0], - node.args[1], - node.metadata), - node.metadata); - } + return std::pair(node, changed); +} + +// Recursively applies rewrite rules and other primary transformations +std::pair mainTransform(preprocessResult pr) { + bool changed = false; + Node node = pr.first; + + // Anything inside "outer" should be treated as a separate program + // and thus recursively compiled in its entirety + if (node.val == "outer") { + node = apply_rules(preprocess(node.args[0])); + changed = true; } + + // Don't descend into comments, macros and inner scopes + if (dontDescend(node.val)) + return std::pair(node, changed); + // Special storage transformation if (isNodeStorageVariable(node)) { node = storageTransform(node, pr.second); + changed = true; + } + if (node.val == "ref" && isNodeStorageVariable(node.args[0])) { + node = storageTransform(node.args[0], pr.second, false, true); + changed = true; } if (node.val == "=" && isNodeStorageVariable(node.args[0])) { Node t = storageTransform(node.args[0], pr.second); @@ -1017,195 +693,213 @@ Node apply_rules(preprocessResult pr) { o.push_back(node.args[1]); node = astnode("sstore", o, node.metadata); } + changed = true; } // Main code - unsigned pos = 0; - std::string prefix = "_temp"+mkUniqueToken()+"_"; - while(1) { - if (synonyms[pos][0] == "---END---") { - break; - } - else if (node.type == ASTNODE && node.val == synonyms[pos][0]) { - node.val = synonyms[pos][1]; - } - pos++; - } - for (pos = 0; pos < nodeMacros.size(); pos++) { - Node pattern = nodeMacros[pos][0]; - matchResult mr = match(pattern, node); - if (mr.success) { - Node pattern2 = nodeMacros[pos][1]; - node = subst(pattern2, mr.map, prefix, node.metadata); - pos = 0; - } - } + std::pair pnb = synonymTransform(node); + node = pnb.first; + changed = changed || pnb.second; + // std::cerr << priority << " " << macros.size() << "\n"; + std::pair pnc = rulesTransform(node, nodeMacros); + node = pnc.first; + changed = changed || pnc.second; + + // Special transformations - if (node.val == "outer") { - pr = preprocess(node); - node = pr.first; - } - if (node.val == "array_lit") + if (node.val == "array_lit") { node = array_lit_transform(node); + changed = true; + } if (node.val == "fun" && node.args[0].val == ".") { node = dotTransform(node, pr.second); + changed = true; + } + if (node.val == "text") { + node = string_transform(node); + changed = true; } - if (node.val == "call") - node = call_transform(node, "call"); - if (node.val == "call_code") - node = call_transform(node, "call_code"); if (node.type == ASTNODE) { unsigned i = 0; + // Arg 0 of all of these is a variable, so should not be changed if (node.val == "set" || node.val == "ref" - || node.val == "get" || node.val == "with" - || node.val == "def" || node.val == "declare") { - node.args[0].val = "'" + node.args[0].val; + || node.val == "get" || node.val == "with") { + if (node.args[0].type == TOKEN && + node.args[0].val.size() > 0 && node.args[0].val[0] != '\'') { + node.args[0].val = "'" + node.args[0].val; + changed = true; + } i = 1; } - if (node.val == "def") { - for (unsigned j = 0; j < node.args[0].args.size(); j++) { - if (node.args[0].args[j].val == ":") { - node.args[0].args[j].val = "kv"; - node.args[0].args[j].args[0].val = - "'" + node.args[0].args[j].args[0].val; - } - else { - node.args[0].args[j].val = "'" + node.args[0].args[j].val; - } - } + // Convert arglen(x) to '_len_x + else if (node.val == "arglen") { + node.val = "get"; + node.args[0].val = "'_len_" + node.args[0].val; + i = 1; + changed = true; } + // Recursively process children for (; i < node.args.size(); i++) { - node.args[i] = - apply_rules(preprocessResult(node.args[i], pr.second)); + std::pair r = + mainTransform(preprocessResult(node.args[i], pr.second)); + node.args[i] = r.first; + changed = changed || r.second; } } + // Add leading ' to variable names, and wrap them inside get else if (node.type == TOKEN && !isNumberLike(node)) { - node.val = "'" + node.val; - std::vector args; - args.push_back(node); - node = astnode("get", args, node.metadata); + if (node.val.size() && node.val[0] != '\'' && node.val[0] != '$') { + Node n = astnode("get", tkn("'"+node.val), node.metadata); + node = n; + changed = true; + } } - // This allows people to use ~x as a way of having functions with the same - // name and arity as macros; the idea is that ~x is a "final" form, and - // should not be remacroed, but it is converted back at the end - if (node.type == ASTNODE && node.val[0] == '~') - node.val = node.val.substr(1); - return node; + // Convert all numbers to normalized form + else if (node.type == TOKEN && isNumberLike(node) && !isDecimal(node.val)) { + node.val = strToNumeric(node.val); + changed = true; + } + return std::pair(node, changed); } -// Compile-time arithmetic calculations -Node optimize(Node inp) { - if (inp.type == TOKEN) { - Node o = tryNumberize(inp); - if (decimalGt(o.val, tt256, true)) - err("Value too large (exceeds 32 bytes or 2^256)", inp.metadata); - return o; +// Do some preprocessing to convert all of our macro lists into compiled +// forms that can then be reused +void parseMacros() { + for (int i = 0; i < 9999; i++) { + std::vector o; + if (macros[i][0] == "---END---") break; + nodeMacros.addRule(rewriteRule( + parseLLL(macros[i][0]), + parseLLL(macros[i][1]) + )); } - for (unsigned i = 0; i < inp.args.size(); i++) { - inp.args[i] = optimize(inp.args[i]); + for (int i = 0; i < 9999; i++) { + std::vector o; + if (setters[i][0] == "---END---") break; + setterMacros.addRule(rewriteRule( + asn(setters[i][0], tkn("$x"), tkn("$y")), + asn("=", tkn("$x"), asn(setters[i][1], tkn("$x"), tkn("$y"))) + )); } - // Degenerate cases for add and mul - if (inp.args.size() == 2) { - if (inp.val == "add" && inp.args[0].type == TOKEN && - inp.args[0].val == "0") { - inp = inp.args[1]; - } - if (inp.val == "add" && inp.args[1].type == TOKEN && - inp.args[1].val == "0") { - inp = inp.args[0]; - } - if (inp.val == "mul" && inp.args[0].type == TOKEN && - inp.args[0].val == "1") { - inp = inp.args[1]; - } - if (inp.val == "mul" && inp.args[1].type == TOKEN && - inp.args[1].val == "1") { - inp = inp.args[0]; - } + for (int i = 0; i < 9999; i++) { + if (synonyms[i][0] == "---END---") break; + synonymMap[synonyms[i][0]] = synonyms[i][1]; + } +} + +Node apply_rules(preprocessResult pr) { + // If the rewrite rules have not yet been parsed, parse them + if (!nodeMacros.ruleLists.size()) parseMacros(); + // Iterate over macros by priority list + std::map::iterator it; + std::pair r; + for(it=pr.second.customMacros.begin(); + it != pr.second.customMacros.end(); it++) { + while (1) { + // std::cerr << "STARTING ARI CYCLE: " << (*it).first <<"\n"; + // std::cerr << printAST(pr.first) << "\n"; + r = apply_rules_iter(pr, (*it).second); + pr.first = r.first; + if (!r.second) break; + } + } + // Apply setter macros + while (1) { + r = apply_rules_iter(pr, setterMacros); + pr.first = r.first; + if (!r.second) break; } - // Arithmetic computation - if (inp.args.size() == 2 - && inp.args[0].type == TOKEN - && inp.args[1].type == TOKEN) { - std::string o; - if (inp.val == "add") { - o = decimalMod(decimalAdd(inp.args[0].val, inp.args[1].val), tt256); - } - else if (inp.val == "sub") { - if (decimalGt(inp.args[0].val, inp.args[1].val, true)) - o = decimalSub(inp.args[0].val, inp.args[1].val); - } - else if (inp.val == "mul") { - o = decimalMod(decimalMul(inp.args[0].val, inp.args[1].val), tt256); - } - else if (inp.val == "div" && inp.args[1].val != "0") { - o = decimalDiv(inp.args[0].val, inp.args[1].val); - } - else if (inp.val == "sdiv" && inp.args[1].val != "0" - && decimalGt(tt255, inp.args[0].val) - && decimalGt(tt255, inp.args[1].val)) { - o = decimalDiv(inp.args[0].val, inp.args[1].val); - } - else if (inp.val == "mod" && inp.args[1].val != "0") { - o = decimalMod(inp.args[0].val, inp.args[1].val); - } - else if (inp.val == "smod" && inp.args[1].val != "0" - && decimalGt(tt255, inp.args[0].val) - && decimalGt(tt255, inp.args[1].val)) { - o = decimalMod(inp.args[0].val, inp.args[1].val); - } - else if (inp.val == "exp") { - o = decimalModExp(inp.args[0].val, inp.args[1].val, tt256); - } - if (o.length()) return token(o, inp.metadata); + // Apply all other mactos + while (1) { + r = mainTransform(pr); + pr.first = r.first; + if (!r.second) break; } - return inp; + return r.first; } +// Pre-validation Node validate(Node inp) { + Metadata m = inp.metadata; if (inp.type == ASTNODE) { int i = 0; - while(valid[i][0] != "---END---") { - if (inp.val == valid[i][0]) { + while(validFunctions[i][0] != "---END---") { + if (inp.val == validFunctions[i][0]) { std::string sz = unsignedToDecimal(inp.args.size()); - if (decimalGt(valid[i][1], sz)) { + if (decimalGt(validFunctions[i][1], sz)) { err("Too few arguments for "+inp.val, inp.metadata); } - if (decimalGt(sz, valid[i][2])) { + if (decimalGt(sz, validFunctions[i][2])) { err("Too many arguments for "+inp.val, inp.metadata); } } i++; } + } + else if (inp.type == TOKEN) { + if (!inp.val.size()) err("??? empty token", m); + if (inp.val[0] == '_') err("Variables cannot start with _", m); } for (unsigned i = 0; i < inp.args.size(); i++) validate(inp.args[i]); return inp; } Node postValidate(Node inp) { + // This allows people to use ~x as a way of having functions with the same + // name and arity as macros; the idea is that ~x is a "final" form, and + // should not be remacroed, but it is converted back at the end + if (inp.val.size() > 0 && inp.val[0] == '~') { + inp.val = inp.val.substr(1); + } if (inp.type == ASTNODE) { if (inp.val == ".") err("Invalid object member (ie. a foo.bar not mapped to anything)", inp.metadata); - for (unsigned i = 0; i < inp.args.size(); i++) - postValidate(inp.args[i]); + else if (opcode(inp.val) >= 0) { + if ((signed)inp.args.size() < opinputs(inp.val)) + err("Too few arguments for "+inp.val, inp.metadata); + if ((signed)inp.args.size() > opinputs(inp.val)) + err("Too many arguments for "+inp.val, inp.metadata); + } + else if (isValidLLLFunc(inp.val, inp.args.size())) { + // do nothing + } + else err ("Invalid argument count or LLL function: "+printSimple(inp), inp.metadata); + for (unsigned i = 0; i < inp.args.size(); i++) { + inp.args[i] = postValidate(inp.args[i]); + } } return inp; } -Node outerWrap(Node inp) { - std::vector args; - args.push_back(inp); - return astnode("outer", args, inp.metadata); + +Node rewriteChunk(Node inp) { + return postValidate(optimize(apply_rules( + preprocessResult( + validate(inp), preprocessAux())))); } -Node rewrite(Node inp) { - return postValidate(optimize(apply_rules(preprocessResult( - validate(outerWrap(inp)), preprocessAux())))); +// Flatten nested sequence into flat sequence +Node flattenSeq(Node inp) { + std::vector o; + if (inp.val == "seq" && inp.type == ASTNODE) { + for (unsigned i = 0; i < inp.args.size(); i++) { + if (inp.args[i].val == "seq" && inp.args[i].type == ASTNODE) + o = extend(o, flattenSeq(inp.args[i]).args); + else + o.push_back(flattenSeq(inp.args[i])); + } + } + else if (inp.type == ASTNODE) { + for (unsigned i = 0; i < inp.args.size(); i++) { + o.push_back(flattenSeq(inp.args[i])); + } + } + else return inp; + return asn(inp.val, o, inp.metadata); } -Node rewriteChunk(Node inp) { - return postValidate(optimize(apply_rules(preprocessResult( - validate(inp), preprocessAux())))); +Node rewrite(Node inp) { + return postValidate(optimize(apply_rules(preprocess(flattenSeq(inp))))); } using namespace std; diff --git a/libserpent/tokenize.cpp b/libserpent/tokenize.cpp index c6a211593..b60cc8a44 100644 --- a/libserpent/tokenize.cpp +++ b/libserpent/tokenize.cpp @@ -13,8 +13,8 @@ int chartype(char c) { if (c >= '0' && c <= '9') return ALPHANUM; else if (c >= 'a' && c <= 'z') return ALPHANUM; else if (c >= 'A' && c <= 'Z') return ALPHANUM; - else if (std::string("~_$").find(c) != std::string::npos) return ALPHANUM; - else if (c == '\t' || c == ' ' || c == '\n') return SPACE; + else if (std::string("~_$@").find(c) != std::string::npos) return ALPHANUM; + else if (c == '\t' || c == ' ' || c == '\n' || c == '\r') return SPACE; else if (std::string("()[]{}").find(c) != std::string::npos) return BRACK; else if (c == '"') return DQUOTE; else if (c == '\'') return SQUOTE; diff --git a/libserpent/util.cpp b/libserpent/util.cpp index fbce5e8b5..5e83c0e41 100644 --- a/libserpent/util.cpp +++ b/libserpent/util.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "util.h" #include "bignum.h" #include @@ -28,6 +27,11 @@ Node astnode(std::string val, std::vector args, Metadata met) { } //AST node constructors for a specific number of children +Node astnode(std::string val, Metadata met) { + std::vector args; + return astnode(val, args, met); +} + Node astnode(std::string val, Node a, Metadata met) { std::vector args; args.push_back(a); @@ -49,6 +53,16 @@ Node astnode(std::string val, Node a, Node b, Node c, Metadata met) { return astnode(val, args, met); } +Node astnode(std::string val, Node a, Node b, Node c, Node d, Metadata met) { + std::vector args; + args.push_back(a); + args.push_back(b); + args.push_back(c); + args.push_back(d); + return astnode(val, args, met); +} + + // Print token list std::string printTokens(std::vector tokens) { std::string s = ""; @@ -146,6 +160,15 @@ std::string indentLines(std::string inp) { return joinLines(lines); } +// Binary to hexadecimal +std::string binToNumeric(std::string inp) { + std::string o = "0"; + for (unsigned i = 0; i < inp.length(); i++) { + o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i])); + } + return o; +} + // Converts string to simple numeric format std::string strToNumeric(std::string inp) { std::string o = "0"; @@ -154,7 +177,7 @@ std::string strToNumeric(std::string inp) { } else if ((inp[0] == '"' && inp[inp.length()-1] == '"') || (inp[0] == '\'' && inp[inp.length()-1] == '\'')) { - for (unsigned i = 1; i < inp.length() - 1; i++) { + for (unsigned i = 1; i < inp.length() - 1; i++) { o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i])); } } @@ -181,6 +204,14 @@ bool isNumberLike(Node node) { return strToNumeric(node.val) != ""; } +// Is the number decimal? +bool isDecimal(std::string inp) { + for (unsigned i = 0; i < inp.length(); i++) { + if (inp[i] < '0' || inp[i] > '9') return false; + } + return true; +} + //Normalizes number representations Node nodeToNumeric(Node node) { std::string o = strToNumeric(node.val); @@ -246,6 +277,14 @@ void err(std::string errtext, Metadata met) { throw(err); } +//Report warning +void warn(std::string errtext, Metadata met) { + std::string err = "Warning (file \"" + met.file + "\", line " + + unsignedToDecimal(met.ln + 1) + ", char " + unsignedToDecimal(met.ch) + + "): " + errtext; + std::cerr << err << "\n"; +} + //Bin to hex std::string binToHex(std::string inp) { std::string o = ""; @@ -280,7 +319,15 @@ std::string upperCase(std::string inp) { //Three-int vector std::vector triple(int a, int b, int c) { - std::vector o; - o.push_back(a); o.push_back(b); o.push_back(c); - return o; + std::vector v; + v.push_back(a); + v.push_back(b); + v.push_back(c); + return v; +} + +//Extend node vector +std::vector extend(std::vector a, std::vector b) { + for (unsigned i = 0; i < b.size(); i++) a.push_back(b[i]); + return a; } diff --git a/libserpent/util.h b/libserpent/util.h index c0a2e9324..e25712d0f 100644 --- a/libserpent/util.h +++ b/libserpent/util.h @@ -28,30 +28,36 @@ const int TOKEN = 0, // Stores metadata about each token class Metadata { public: - Metadata(std::string File="main", int Ln=0, int Ch=0) { + Metadata(std::string File="main", int Ln=-1, int Ch=-1) { file = File; ln = Ln; ch = Ch; + fixed = false; } std::string file; int ln; int ch; + bool fixed; }; std::string mkUniqueToken(); // type can be TOKEN or ASTNODE -struct Node { - int type; - std::string val; - std::vector args; - Metadata metadata; +class Node { + public: + int type; + std::string val; + std::vector args; + Metadata metadata; }; Node token(std::string val, Metadata met=Metadata()); Node astnode(std::string val, std::vector args, Metadata met=Metadata()); +Node astnode(std::string val, Metadata met=Metadata()); Node astnode(std::string val, Node a, Metadata met=Metadata()); Node astnode(std::string val, Node a, Node b, Metadata met=Metadata()); Node astnode(std::string val, Node a, Node b, Node c, Metadata met=Metadata()); +Node astnode(std::string val, Node a, Node b, + Node c, Node d, Metadata met=Metadata()); // Number of tokens in a tree int treeSize(Node prog); @@ -74,6 +80,9 @@ std::string joinLines(std::vector lines); // Indent all lines by 4 spaces std::string indentLines(std::string inp); +// Converts binary to simple numeric format +std::string binToNumeric(std::string inp); + // Converts string to simple numeric format std::string strToNumeric(std::string inp); @@ -98,6 +107,9 @@ bool exists(std::string fileName); //Report error void err(std::string errtext, Metadata met); +//Report warning +void warn(std::string errtext, Metadata met); + //Bin to hex std::string binToHex(std::string inp); @@ -110,4 +122,16 @@ std::string upperCase(std::string inp); //Three-int vector std::vector triple(int a, int b, int c); +//Extend node vector +std::vector extend(std::vector a, std::vector b); + +// Is the number decimal? +bool isDecimal(std::string inp); + +#define asn astnode +#define tkn token +#define msi std::map +#define msn std::map +#define mss std::map + #endif diff --git a/sc/cmdline.cpp b/sc/cmdline.cpp index b44d2538c..a5fed37d6 100644 --- a/sc/cmdline.cpp +++ b/sc/cmdline.cpp @@ -10,6 +10,19 @@ int main(int argv, char** argc) { std::cerr << "Must provide a command and arguments! Try parse, rewrite, compile, assemble\n"; return 0; } + if (argv == 2 && (std::string(argc[1]) == "--help" || std::string(argc[1]) == "-h" )) { + std::cout << argc[1] << "\n"; + + std::cout << "serpent command input\n"; + std::cout << "where input -s for from stdin, a file, or interpreted as serpent code if does not exist as file."; + std::cout << "where command: \n"; + std::cout << " parse: Just parses and returns s-expression code.\n"; + std::cout << " rewrite: Parse, use rewrite rules print s-expressions of result.\n"; + std::cout << " compile: Return resulting compiled EVM code in hex.\n"; + std::cout << " assemble: Return result from step before compilation.\n"; + return 0; + } + std::string flag = ""; std::string command = argc[1]; std::string input; From c4ed74cc8ba923a2c15ac4f4e04962df5eec3727 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 18 Dec 2014 16:27:07 +0100 Subject: [PATCH 51/57] Additional serpent files. --- libserpent/functions.cpp | 203 ++++++++++++++++++++++ libserpent/functions.h | 39 +++++ libserpent/opcodes.cpp | 154 +++++++++++++++++ libserpent/optimize.cpp | 98 +++++++++++ libserpent/optimize.h | 19 +++ libserpent/preprocess.cpp | 327 ++++++++++++++++++++++++++++++++++++ libserpent/preprocess.h | 50 ++++++ libserpent/rewriteutils.cpp | 211 +++++++++++++++++++++++ libserpent/rewriteutils.h | 76 +++++++++ 9 files changed, 1177 insertions(+) create mode 100644 libserpent/functions.cpp create mode 100644 libserpent/functions.h create mode 100644 libserpent/opcodes.cpp create mode 100644 libserpent/optimize.cpp create mode 100644 libserpent/optimize.h create mode 100644 libserpent/preprocess.cpp create mode 100644 libserpent/preprocess.h create mode 100644 libserpent/rewriteutils.cpp create mode 100644 libserpent/rewriteutils.h diff --git a/libserpent/functions.cpp b/libserpent/functions.cpp new file mode 100644 index 000000000..78e12e84a --- /dev/null +++ b/libserpent/functions.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include "util.h" +#include "lllparser.h" +#include "bignum.h" +#include "optimize.h" +#include "rewriteutils.h" +#include "preprocess.h" +#include "functions.h" + +std::string getSignature(std::vector args) { + std::string o; + for (unsigned i = 0; i < args.size(); i++) { + if (args[i].val == ":" && args[i].args[1].val == "s") + o += "s"; + else if (args[i].val == ":" && args[i].args[1].val == "a") + o += "a"; + else + o += "i"; + } + return o; +} + +// Convert a list of arguments into a node containing a +// < datastart, datasz > pair + +Node packArguments(std::vector args, std::string sig, + int funId, Metadata m) { + // Plain old 32 byte arguments + std::vector nargs; + // Variable-sized arguments + std::vector vargs; + // Variable sizes + std::vector sizes; + // Is a variable an array? + std::vector isArray; + // Fill up above three argument lists + int argCount = 0; + for (unsigned i = 0; i < args.size(); i++) { + Metadata m = args[i].metadata; + if (args[i].val == "=") { + // do nothing + } + else { + // Determine the correct argument type + char argType; + if (sig.size() > 0) { + if (argCount >= (signed)sig.size()) + err("Too many args", m); + argType = sig[argCount]; + } + else argType = 'i'; + // Integer (also usable for short strings) + if (argType == 'i') { + if (args[i].val == ":") + err("Function asks for int, provided string or array", m); + nargs.push_back(args[i]); + } + // Long string + else if (argType == 's') { + if (args[i].val != ":") + err("Must specify string length", m); + vargs.push_back(args[i].args[0]); + sizes.push_back(args[i].args[1]); + isArray.push_back(false); + } + // Array + else if (argType == 'a') { + if (args[i].val != ":") + err("Must specify array length", m); + vargs.push_back(args[i].args[0]); + sizes.push_back(args[i].args[1]); + isArray.push_back(true); + } + else err("Invalid arg type in signature", m); + argCount++; + } + } + int static_arg_size = 1 + (vargs.size() + nargs.size()) * 32; + // Start off by saving the size variables and calculating the total + msn kwargs; + kwargs["funid"] = tkn(utd(funId), m); + std::string pattern = + "(with _sztot "+utd(static_arg_size)+" " + " (with _sizes (alloc "+utd(sizes.size() * 32)+") " + " (seq "; + for (unsigned i = 0; i < sizes.size(); i++) { + std::string sizeIncrement = + isArray[i] ? "(mul 32 _x)" : "_x"; + pattern += + "(with _x $sz"+utd(i)+"(seq " + " (mstore (add _sizes "+utd(i * 32)+") _x) " + " (set _sztot (add _sztot "+sizeIncrement+" )))) "; + kwargs["sz"+utd(i)] = sizes[i]; + } + // Allocate memory, and set first data byte + pattern += + "(with _datastart (alloc (add _sztot 32)) (seq " + " (mstore8 _datastart $funid) "; + // Copy over size variables + for (unsigned i = 0; i < sizes.size(); i++) { + int v = 1 + i * 32; + pattern += + " (mstore " + " (add _datastart "+utd(v)+") " + " (mload (add _sizes "+utd(v-1)+"))) "; + } + // Store normal arguments + for (unsigned i = 0; i < nargs.size(); i++) { + int v = 1 + (i + sizes.size()) * 32; + pattern += + " (mstore (add _datastart "+utd(v)+") $"+utd(i)+") "; + kwargs[utd(i)] = nargs[i]; + } + // Loop through variable-sized arguments, store them + pattern += + " (with _pos (add _datastart "+utd(static_arg_size)+") (seq"; + for (unsigned i = 0; i < vargs.size(); i++) { + std::string copySize = + isArray[i] ? "(mul 32 (mload (add _sizes "+utd(i * 32)+")))" + : "(mload (add _sizes "+utd(i * 32)+"))"; + pattern += + " (unsafe_mcopy _pos $vl"+utd(i)+" "+copySize+") " + " (set _pos (add _pos "+copySize+")) "; + kwargs["vl"+utd(i)] = vargs[i]; + } + // Return a 2-item array containing the start and size + pattern += " (array_lit _datastart _sztot))))))))"; + std::string prefix = "_temp_"+mkUniqueToken(); + // Fill in pattern, return triple + return subst(parseLLL(pattern), kwargs, prefix, m); +} + +// Create a node for argument unpacking +Node unpackArguments(std::vector vars, Metadata m) { + std::vector varNames; + std::vector longVarNames; + std::vector longVarIsArray; + // Fill in variable and long variable names, as well as which + // long variables are arrays and which are strings + for (unsigned i = 0; i < vars.size(); i++) { + if (vars[i].val == ":") { + if (vars[i].args.size() != 2) + err("Malformed def!", m); + longVarNames.push_back(vars[i].args[0].val); + std::string tag = vars[i].args[1].val; + if (tag == "s") + longVarIsArray.push_back(false); + else if (tag == "a") + longVarIsArray.push_back(true); + else + err("Function value can only be string or array", m); + } + else { + varNames.push_back(vars[i].val); + } + } + std::vector sub; + if (!varNames.size() && !longVarNames.size()) { + // do nothing if we have no arguments + } + else { + std::vector varNodes; + for (unsigned i = 0; i < longVarNames.size(); i++) + varNodes.push_back(token(longVarNames[i], m)); + for (unsigned i = 0; i < varNames.size(); i++) + varNodes.push_back(token(varNames[i], m)); + // Copy over variable lengths and short variables + for (unsigned i = 0; i < varNodes.size(); i++) { + int pos = 1 + i * 32; + std::string prefix = (i < longVarNames.size()) ? "_len_" : ""; + sub.push_back(asn("untyped", asn("set", + token(prefix+varNodes[i].val, m), + asn("calldataload", tkn(utd(pos), m), m), + m))); + } + // Copy over long variables + if (longVarNames.size() > 0) { + std::vector sub2; + int pos = varNodes.size() * 32 + 1; + Node tot = tkn("_tot", m); + for (unsigned i = 0; i < longVarNames.size(); i++) { + Node var = tkn(longVarNames[i], m); + Node varlen = longVarIsArray[i] + ? asn("mul", tkn("32", m), tkn("_len_"+longVarNames[i], m)) + : tkn("_len_"+longVarNames[i], m); + sub2.push_back(asn("untyped", + asn("set", var, asn("alloc", varlen)))); + sub2.push_back(asn("calldatacopy", var, tot, varlen)); + sub2.push_back(asn("set", tot, asn("add", tot, varlen))); + } + std::string prefix = "_temp_"+mkUniqueToken(); + sub.push_back(subst( + astnode("with", tot, tkn(utd(pos), m), asn("seq", sub2)), + msn(), + prefix, + m)); + } + } + return asn("seq", sub, m); +} diff --git a/libserpent/functions.h b/libserpent/functions.h new file mode 100644 index 000000000..68a1c69ce --- /dev/null +++ b/libserpent/functions.h @@ -0,0 +1,39 @@ +#ifndef ETHSERP_FUNCTIONS +#define ETHSERP_FUNCTIONS + +#include +#include +#include +#include +#include "util.h" +#include "lllparser.h" +#include "bignum.h" +#include "optimize.h" +#include "rewriteutils.h" +#include "preprocess.h" + + +class argPack { + public: + argPack(Node a, Node b, Node c) { + pre = a; + datastart = b; + datasz = c; + } + Node pre; + Node datastart; + Node datasz; +}; + +// Get a signature from a function +std::string getSignature(std::vector args); + +// Convert a list of arguments into a node +// triple, given the signature of a function +Node packArguments(std::vector args, std::string sig, + int funId, Metadata m); + +// Create a node for argument unpacking +Node unpackArguments(std::vector vars, Metadata m); + +#endif diff --git a/libserpent/opcodes.cpp b/libserpent/opcodes.cpp new file mode 100644 index 000000000..b24144e46 --- /dev/null +++ b/libserpent/opcodes.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include "opcodes.h" +#include "util.h" +#include "bignum.h" + +Mapping mapping[] = { + Mapping("STOP", 0x00, 0, 0), + Mapping("ADD", 0x01, 2, 1), + Mapping("MUL", 0x02, 2, 1), + Mapping("SUB", 0x03, 2, 1), + Mapping("DIV", 0x04, 2, 1), + Mapping("SDIV", 0x05, 2, 1), + Mapping("MOD", 0x06, 2, 1), + Mapping("SMOD", 0x07, 2, 1), + Mapping("ADDMOD", 0x08, 3, 1), + Mapping("MULMOD", 0x09, 3, 1), + Mapping("EXP", 0x0a, 2, 1), + Mapping("SIGNEXTEND", 0x0b, 2, 1), + Mapping("LT", 0x10, 2, 1), + Mapping("GT", 0x11, 2, 1), + Mapping("SLT", 0x12, 2, 1), + Mapping("SGT", 0x13, 2, 1), + Mapping("EQ", 0x14, 2, 1), + Mapping("ISZERO", 0x15, 1, 1), + Mapping("AND", 0x16, 2, 1), + Mapping("OR", 0x17, 2, 1), + Mapping("XOR", 0x18, 2, 1), + Mapping("NOT", 0x19, 1, 1), + Mapping("BYTE", 0x1a, 2, 1), + Mapping("SHA3", 0x20, 2, 1), + Mapping("ADDRESS", 0x30, 0, 1), + Mapping("BALANCE", 0x31, 1, 1), + Mapping("ORIGIN", 0x32, 0, 1), + Mapping("CALLER", 0x33, 0, 1), + Mapping("CALLVALUE", 0x34, 0, 1), + Mapping("CALLDATALOAD", 0x35, 1, 1), + Mapping("CALLDATASIZE", 0x36, 0, 1), + Mapping("CALLDATACOPY", 0x37, 3, 0), + Mapping("CODESIZE", 0x38, 0, 1), + Mapping("CODECOPY", 0x39, 3, 0), + Mapping("GASPRICE", 0x3a, 0, 1), + Mapping("EXTCODESIZE", 0x3b, 1, 1), + Mapping("EXTCODECOPY", 0x3c, 4, 0), + Mapping("PREVHASH", 0x40, 0, 1), + Mapping("COINBASE", 0x41, 0, 1), + Mapping("TIMESTAMP", 0x42, 0, 1), + Mapping("NUMBER", 0x43, 0, 1), + Mapping("DIFFICULTY", 0x44, 0, 1), + Mapping("GASLIMIT", 0x45, 0, 1), + Mapping("POP", 0x50, 1, 0), + Mapping("MLOAD", 0x51, 1, 1), + Mapping("MSTORE", 0x52, 2, 0), + Mapping("MSTORE8", 0x53, 2, 0), + Mapping("SLOAD", 0x54, 1, 1), + Mapping("SSTORE", 0x55, 2, 0), + Mapping("JUMP", 0x56, 1, 0), + Mapping("JUMPI", 0x57, 2, 0), + Mapping("PC", 0x58, 0, 1), + Mapping("MSIZE", 0x59, 0, 1), + Mapping("GAS", 0x5a, 0, 1), + Mapping("JUMPDEST", 0x5b, 0, 0), + Mapping("LOG0", 0xa0, 2, 0), + Mapping("LOG1", 0xa1, 3, 0), + Mapping("LOG2", 0xa2, 4, 0), + Mapping("LOG3", 0xa3, 5, 0), + Mapping("LOG4", 0xa4, 6, 0), + Mapping("CREATE", 0xf0, 3, 1), + Mapping("CALL", 0xf1, 7, 1), + Mapping("CALLCODE", 0xf2, 7, 1), + Mapping("RETURN", 0xf3, 2, 0), + Mapping("SUICIDE", 0xff, 1, 0), + Mapping("---END---", 0x00, 0, 0), +}; + +std::map > opcodes; +std::map reverseOpcodes; + +// Fetches everything EXCEPT PUSH1..32 +std::pair > _opdata(std::string ops, int opi) { + if (!opcodes.size()) { + int i = 0; + while (mapping[i].op != "---END---") { + Mapping mi = mapping[i]; + opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out); + i++; + } + for (i = 1; i <= 16; i++) { + opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1); + opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1); + } + for (std::map >::iterator it=opcodes.begin(); + it != opcodes.end(); + it++) { + reverseOpcodes[(*it).second[0]] = (*it).first; + } + } + ops = upperCase(ops); + std::string op; + std::vector opdata; + op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : ""; + opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1); + return std::pair >(op, opdata); +} + +int opcode(std::string op) { + return _opdata(op, -1).second[0]; +} + +int opinputs(std::string op) { + return _opdata(op, -1).second[1]; +} + +int opoutputs(std::string op) { + return _opdata(op, -1).second[2]; +} + +std::string op(int opcode) { + return _opdata("", opcode).first; +} + +std::string lllSpecials[][3] = { + { "ref", "1", "1" }, + { "get", "1", "1" }, + { "set", "2", "2" }, + { "with", "3", "3" }, + { "comment", "0", "2147483647" }, + { "ops", "0", "2147483647" }, + { "lll", "2", "2" }, + { "seq", "0", "2147483647" }, + { "if", "3", "3" }, + { "unless", "2", "2" }, + { "until", "2", "2" }, + { "alloc", "1", "1" }, + { "---END---", "0", "0" }, +}; + +std::map > lllMap; + +// Is a function name one of the valid functions above? +bool isValidLLLFunc(std::string f, int argc) { + if (lllMap.size() == 0) { + for (int i = 0; ; i++) { + if (lllSpecials[i][0] == "---END---") break; + lllMap[lllSpecials[i][0]] = std::pair( + dtu(lllSpecials[i][1]), dtu(lllSpecials[i][2])); + } + } + return lllMap.count(f) + && argc >= lllMap[f].first + && argc <= lllMap[f].second; +} diff --git a/libserpent/optimize.cpp b/libserpent/optimize.cpp new file mode 100644 index 000000000..e689fcb69 --- /dev/null +++ b/libserpent/optimize.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include "util.h" +#include "lllparser.h" +#include "bignum.h" + +// Compile-time arithmetic calculations +Node optimize(Node inp) { + if (inp.type == TOKEN) { + Node o = tryNumberize(inp); + if (decimalGt(o.val, tt256, true)) + err("Value too large (exceeds 32 bytes or 2^256)", inp.metadata); + return o; + } + for (unsigned i = 0; i < inp.args.size(); i++) { + inp.args[i] = optimize(inp.args[i]); + } + // Arithmetic-specific transform + if (inp.val == "+") inp.val = "add"; + if (inp.val == "*") inp.val = "mul"; + if (inp.val == "-") inp.val = "sub"; + if (inp.val == "/") inp.val = "sdiv"; + if (inp.val == "^") inp.val = "exp"; + if (inp.val == "**") inp.val = "exp"; + if (inp.val == "%") inp.val = "smod"; + // Degenerate cases for add and mul + if (inp.args.size() == 2) { + if (inp.val == "add" && inp.args[0].type == TOKEN && + inp.args[0].val == "0") { + Node x = inp.args[1]; + inp = x; + } + if (inp.val == "add" && inp.args[1].type == TOKEN && + inp.args[1].val == "0") { + Node x = inp.args[0]; + inp = x; + } + if (inp.val == "mul" && inp.args[0].type == TOKEN && + inp.args[0].val == "1") { + Node x = inp.args[1]; + inp = x; + } + if (inp.val == "mul" && inp.args[1].type == TOKEN && + inp.args[1].val == "1") { + Node x = inp.args[0]; + inp = x; + } + } + // Arithmetic computation + if (inp.args.size() == 2 + && inp.args[0].type == TOKEN + && inp.args[1].type == TOKEN) { + std::string o; + if (inp.val == "add") { + o = decimalMod(decimalAdd(inp.args[0].val, inp.args[1].val), tt256); + } + else if (inp.val == "sub") { + if (decimalGt(inp.args[0].val, inp.args[1].val, true)) + o = decimalSub(inp.args[0].val, inp.args[1].val); + } + else if (inp.val == "mul") { + o = decimalMod(decimalMul(inp.args[0].val, inp.args[1].val), tt256); + } + else if (inp.val == "div" && inp.args[1].val != "0") { + o = decimalDiv(inp.args[0].val, inp.args[1].val); + } + else if (inp.val == "sdiv" && inp.args[1].val != "0" + && decimalGt(tt255, inp.args[0].val) + && decimalGt(tt255, inp.args[1].val)) { + o = decimalDiv(inp.args[0].val, inp.args[1].val); + } + else if (inp.val == "mod" && inp.args[1].val != "0") { + o = decimalMod(inp.args[0].val, inp.args[1].val); + } + else if (inp.val == "smod" && inp.args[1].val != "0" + && decimalGt(tt255, inp.args[0].val) + && decimalGt(tt255, inp.args[1].val)) { + o = decimalMod(inp.args[0].val, inp.args[1].val); + } + else if (inp.val == "exp") { + o = decimalModExp(inp.args[0].val, inp.args[1].val, tt256); + } + if (o.length()) return token(o, inp.metadata); + } + return inp; +} + +// Is a node degenerate (ie. trivial to calculate) ? +bool isDegenerate(Node n) { + return optimize(n).type == TOKEN; +} + +// Is a node purely arithmetic? +bool isPureArithmetic(Node n) { + return isNumberLike(optimize(n)); +} diff --git a/libserpent/optimize.h b/libserpent/optimize.h new file mode 100644 index 000000000..06ea3bba1 --- /dev/null +++ b/libserpent/optimize.h @@ -0,0 +1,19 @@ +#ifndef ETHSERP_OPTIMIZER +#define ETHSERP_OPTIMIZER + +#include +#include +#include +#include +#include "util.h" + +// Compile-time arithmetic calculations +Node optimize(Node inp); + +// Is a node degenerate (ie. trivial to calculate) ? +bool isDegenerate(Node n); + +// Is a node purely arithmetic? +bool isPureArithmetic(Node n); + +#endif diff --git a/libserpent/preprocess.cpp b/libserpent/preprocess.cpp new file mode 100644 index 000000000..2df149945 --- /dev/null +++ b/libserpent/preprocess.cpp @@ -0,0 +1,327 @@ +#include +#include +#include +#include +#include "util.h" +#include "lllparser.h" +#include "bignum.h" +#include "rewriteutils.h" +#include "optimize.h" +#include "preprocess.h" +#include "functions.h" +#include "opcodes.h" + +// Convert a function of the form (def (f x y z) (do stuff)) into +// (if (first byte of ABI is correct) (seq (setup x y z) (do stuff))) +Node convFunction(Node node, int functionCount) { + std::string prefix = "_temp"+mkUniqueToken()+"_"; + Metadata m = node.metadata; + + if (node.args.size() != 2) + err("Malformed def!", m); + // Collect the list of variable names and variable byte counts + Node unpack = unpackArguments(node.args[0].args, m); + // And the actual code + Node body = node.args[1]; + // Main LLL-based function body + return astnode("if", + astnode("eq", + astnode("get", token("__funid", m), m), + token(unsignedToDecimal(functionCount), m), + m), + astnode("seq", unpack, body, m)); +} + +// Populate an svObj with the arguments needed to determine +// the storage position of a node +svObj getStorageVars(svObj pre, Node node, std::string prefix, + int index) { + Metadata m = node.metadata; + if (!pre.globalOffset.size()) pre.globalOffset = "0"; + std::vector h; + std::vector coefficients; + // Array accesses or atoms + if (node.val == "access" || node.type == TOKEN) { + std::string tot = "1"; + h = listfyStorageAccess(node); + coefficients.push_back("1"); + for (unsigned i = h.size() - 1; i >= 1; i--) { + // Array sizes must be constant or at least arithmetically + // evaluable at compile time + if (!isPureArithmetic(h[i])) + err("Array size must be fixed value", m); + // Create a list of the coefficient associated with each + // array index + coefficients.push_back(decimalMul(coefficients.back(), h[i].val)); + } + } + // Tuples + else { + int startc; + // Handle the (fun args...) case + if (node.val == "fun") { + startc = 1; + h = listfyStorageAccess(node.args[0]); + } + // Handle the ( args...) case, which + // the serpent parser produces when the function + // is a simple name and not a complex astnode + else { + startc = 0; + h = listfyStorageAccess(token(node.val, m)); + } + svObj sub = pre; + sub.globalOffset = "0"; + // Evaluate tuple elements recursively + for (unsigned i = startc; i < node.args.size(); i++) { + sub = getStorageVars(sub, + node.args[i], + prefix+h[0].val.substr(2)+".", + i-startc); + } + coefficients.push_back(sub.globalOffset); + for (unsigned i = h.size() - 1; i >= 1; i--) { + // Array sizes must be constant or at least arithmetically + // evaluable at compile time + if (!isPureArithmetic(h[i])) + err("Array size must be fixed value", m); + // Create a list of the coefficient associated with each + // array index + coefficients.push_back(decimalMul(coefficients.back(), h[i].val)); + } + pre.offsets = sub.offsets; + pre.coefficients = sub.coefficients; + pre.nonfinal = sub.nonfinal; + pre.nonfinal[prefix+h[0].val.substr(2)] = true; + } + pre.coefficients[prefix+h[0].val.substr(2)] = coefficients; + pre.offsets[prefix+h[0].val.substr(2)] = pre.globalOffset; + pre.indices[prefix+h[0].val.substr(2)] = index; + if (decimalGt(tt176, coefficients.back())) + pre.globalOffset = decimalAdd(pre.globalOffset, coefficients.back()); + return pre; +} + +// Preprocess input containing functions +// +// localExterns is a map of the form, eg, +// +// { x: { foo: 0, bar: 1, baz: 2 }, y: { qux: 0, foo: 1 } ... } +// +// localExternSigs is a map of the form, eg, +// +// { x : { foo: iii, bar: iis, baz: ia }, y: { qux: i, foo: as } ... } +// +// Signifying that x.foo = 0, x.baz = 2, y.foo = 1, etc +// and that x.foo has three integers as arguments, x.bar has two +// integers and a variable-length string, and baz has an integer +// and an array +// +// globalExterns is a one-level map, eg from above +// +// { foo: 1, bar: 1, baz: 2, qux: 0 } +// +// globalExternSigs is a one-level map, eg from above +// +// { foo: as, bar: iis, baz: ia, qux: i} +// +// Note that globalExterns and globalExternSigs may be ambiguous +// Also, a null signature implies an infinite tail of integers +preprocessResult preprocessInit(Node inp) { + Metadata m = inp.metadata; + if (inp.val != "seq") + inp = astnode("seq", inp, m); + std::vector empty = std::vector(); + Node init = astnode("seq", empty, m); + Node shared = astnode("seq", empty, m); + std::vector any; + std::vector functions; + preprocessAux out = preprocessAux(); + out.localExterns["self"] = std::map(); + int functionCount = 0; + int storageDataCount = 0; + for (unsigned i = 0; i < inp.args.size(); i++) { + Node obj = inp.args[i]; + // Functions + if (obj.val == "def") { + if (obj.args.size() == 0) + err("Empty def", m); + std::string funName = obj.args[0].val; + // Init, shared and any are special functions + if (funName == "init" || funName == "shared" || funName == "any") { + if (obj.args[0].args.size()) + err(funName+" cannot have arguments", m); + } + if (funName == "init") init = obj.args[1]; + else if (funName == "shared") shared = obj.args[1]; + else if (funName == "any") any.push_back(obj.args[1]); + else { + // Other functions + functions.push_back(convFunction(obj, functionCount)); + out.localExterns["self"][obj.args[0].val] = functionCount; + out.localExternSigs["self"][obj.args[0].val] + = getSignature(obj.args[0].args); + functionCount++; + } + } + // Extern declarations + else if (obj.val == "extern") { + std::string externName = obj.args[0].val; + Node al = obj.args[1]; + if (!out.localExterns.count(externName)) + out.localExterns[externName] = std::map(); + for (unsigned i = 0; i < al.args.size(); i++) { + if (al.args[i].val == ":") { + std::string v = al.args[i].args[0].val; + std::string sig = al.args[i].args[1].val; + out.globalExterns[v] = i; + out.globalExternSigs[v] = sig; + out.localExterns[externName][v] = i; + out.localExternSigs[externName][v] = sig; + } + else { + std::string v = al.args[i].val; + out.globalExterns[v] = i; + out.globalExternSigs[v] = ""; + out.localExterns[externName][v] = i; + out.localExternSigs[externName][v] = ""; + } + } + } + // Custom macros + else if (obj.val == "macro" || (obj.val == "fun" && obj.args[0].val == "macro")) { + // Rules for valid macros: + // + // There are only four categories of valid macros: + // + // 1. a macro where the outer function is something + // which is NOT an existing valid function/extern/datum + // 2. a macro of the form set(c(x), d) where c must NOT + // be an existing valid function/extern/datum + // 3. something of the form access(c(x)), where c must NOT + // be an existing valid function/extern/datum + // 4. something of the form set(access(c(x)), d) where c must + // NOT be an existing valid function/extern/datum + // 5. something of the form with(c(x), d, e) where c must + // NOT be an existing valid function/extern/datum + bool valid = false; + Node pattern; + Node substitution; + int priority; + // Priority not set: default zero + if (obj.val == "macro") { + pattern = obj.args[0]; + substitution = obj.args[1]; + priority = 0; + } + // Specified priority + else { + pattern = obj.args[1]; + substitution = obj.args[2]; + if (obj.args[0].args.size()) + priority = dtu(obj.args[0].args[0].val); + else + priority = 0; + } + if (opcode(pattern.val) < 0 && !isValidFunctionName(pattern.val)) + valid = true; + if (pattern.val == "set" && + opcode(pattern.args[0].val) < 0 && + !isValidFunctionName(pattern.args[0].val)) + valid = true; + if (pattern.val == "access" && + opcode(pattern.args[0].val) < 0 && + !isValidFunctionName(pattern.args[0].val)) + if (pattern.val == "set" && + pattern.args[0].val == "access" && + opcode(pattern.args[0].args[0].val) < 0 && + !isValidFunctionName(pattern.args[0].args[0].val)) + valid = true; + if (pattern.val == "with" && + opcode(pattern.args[0].val) < 0 && + !isValidFunctionName(pattern.args[0].val)) + valid = true; + if (valid) { + if (!out.customMacros.count(priority)) + out.customMacros[priority] = rewriteRuleSet(); + out.customMacros[priority].addRule + (rewriteRule(pattern, substitution)); + } + else warn("Macro does not fit valid template: "+printSimple(pattern), m); + } + // Variable types + else if (obj.val == "type") { + std::string typeName = obj.args[0].val; + std::vector vars = obj.args[1].args; + for (unsigned i = 0; i < vars.size(); i++) + out.types[vars[i].val] = typeName; + } + // Storage variables/structures + else if (obj.val == "data") { + out.storageVars = getStorageVars(out.storageVars, + obj.args[0], + "", + storageDataCount); + storageDataCount += 1; + } + else any.push_back(obj); + } + // Set up top-level AST structure + std::vector main; + if (shared.args.size()) main.push_back(shared); + if (init.args.size()) main.push_back(init); + + std::vector code; + if (shared.args.size()) code.push_back(shared); + for (unsigned i = 0; i < any.size(); i++) + code.push_back(any[i]); + for (unsigned i = 0; i < functions.size(); i++) + code.push_back(functions[i]); + Node codeNode; + if (functions.size() > 0) { + codeNode = astnode("with", + token("__funid", m), + astnode("byte", + token("0", m), + astnode("calldataload", token("0", m), m), + m), + astnode("seq", code, m), + m); + } + else codeNode = astnode("seq", code, m); + main.push_back(astnode("~return", + token("0", m), + astnode("lll", + codeNode, + token("0", m), + m), + m)); + + + Node result; + if (main.size() == 1) result = main[0]; + else result = astnode("seq", main, inp.metadata); + return preprocessResult(result, out); +} + +preprocessResult processTypes (preprocessResult pr) { + preprocessAux aux = pr.second; + Node node = pr.first; + if (node.type == TOKEN && aux.types.count(node.val)) + node = asn(aux.types[node.val], node, node.metadata); + else if (node.val == "untyped") + return preprocessResult(node.args[0], aux); + else if (node.val == "outer") + return preprocessResult(node, aux); + else { + for (unsigned i = 0; i < node.args.size(); i++) { + node.args[i] = + processTypes(preprocessResult(node.args[i], aux)).first; + } + } + return preprocessResult(node, aux); +} + +preprocessResult preprocess(Node n) { + return processTypes(preprocessInit(n)); +} diff --git a/libserpent/preprocess.h b/libserpent/preprocess.h new file mode 100644 index 000000000..321fb8527 --- /dev/null +++ b/libserpent/preprocess.h @@ -0,0 +1,50 @@ +#ifndef ETHSERP_PREPROCESSOR +#define ETHSERP_PREPROCESSOR + +#include +#include +#include +#include +#include "util.h" +#include "rewriteutils.h" + +// Storage variable index storing object +struct svObj { + std::map offsets; + std::map indices; + std::map > coefficients; + std::map nonfinal; + std::string globalOffset; +}; + + + +// Preprocessing result storing object +class preprocessAux { + public: + preprocessAux() { + globalExterns = std::map(); + localExterns = std::map >(); + localExterns["self"] = std::map(); + } + std::map globalExterns; + std::map globalExternSigs; + std::map > localExterns; + std::map > localExternSigs; + std::map customMacros; + std::map types; + svObj storageVars; +}; + +#define preprocessResult std::pair + +// Populate an svObj with the arguments needed to determine +// the storage position of a node +svObj getStorageVars(svObj pre, Node node, std::string prefix="", + int index=0); + +// Preprocess a function (see cpp for details) +preprocessResult preprocess(Node inp); + + +#endif diff --git a/libserpent/rewriteutils.cpp b/libserpent/rewriteutils.cpp new file mode 100644 index 000000000..0d810bdbc --- /dev/null +++ b/libserpent/rewriteutils.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include "util.h" +#include "lllparser.h" +#include "bignum.h" +#include "rewriteutils.h" +#include "optimize.h" + +// Valid functions and their min and max argument counts +std::string validFunctions[][3] = { + { "if", "2", "3" }, + { "unless", "2", "2" }, + { "while", "2", "2" }, + { "until", "2", "2" }, + { "alloc", "1", "1" }, + { "array", "1", "1" }, + { "call", "2", tt256 }, + { "callcode", "2", tt256 }, + { "create", "1", "4" }, + { "getch", "2", "2" }, + { "setch", "3", "3" }, + { "sha3", "1", "2" }, + { "return", "1", "2" }, + { "inset", "1", "1" }, + { "min", "2", "2" }, + { "max", "2", "2" }, + { "array_lit", "0", tt256 }, + { "seq", "0", tt256 }, + { "log", "1", "6" }, + { "outer", "1", "1" }, + { "set", "2", "2" }, + { "get", "1", "1" }, + { "ref", "1", "1" }, + { "declare", "1", tt256 }, + { "with", "3", "3" }, + { "outer", "1", "1" }, + { "mcopy", "3", "3" }, + { "unsafe_mcopy", "3", "3" }, + { "save", "3", "3" }, + { "load", "2", "2" }, + { "---END---", "", "" } //Keep this line at the end of the list +}; + +std::map vfMap; + +// Is a function name one of the valid functions above? +bool isValidFunctionName(std::string f) { + if (vfMap.size() == 0) { + for (int i = 0; ; i++) { + if (validFunctions[i][0] == "---END---") break; + vfMap[validFunctions[i][0]] = true; + } + } + return vfMap.count(f); +} + +// Cool function for debug purposes (named cerrStringList to make +// all prints searchable via 'cerr') +void cerrStringList(std::vector s, std::string suffix) { + for (unsigned i = 0; i < s.size(); i++) std::cerr << s[i] << " "; + std::cerr << suffix << "\n"; +} + +// Convert: +// self.cow -> ["cow"] +// self.horse[0] -> ["horse", "0"] +// self.a[6][7][self.storage[3]].chicken[9] -> +// ["6", "7", (sload 3), "chicken", "9"] +std::vector listfyStorageAccess(Node node) { + std::vector out; + std::vector nodez; + nodez.push_back(node); + while (1) { + if (nodez.back().type == TOKEN) { + out.push_back(token("--" + nodez.back().val, node.metadata)); + std::vector outrev; + for (int i = (signed)out.size() - 1; i >= 0; i--) { + outrev.push_back(out[i]); + } + return outrev; + } + if (nodez.back().val == ".") + nodez.back().args[1].val = "--" + nodez.back().args[1].val; + if (nodez.back().args.size() == 0) + err("Error parsing storage variable statement", node.metadata); + if (nodez.back().args.size() == 1) + out.push_back(token(tt256m1, node.metadata)); + else + out.push_back(nodez.back().args[1]); + nodez.push_back(nodez.back().args[0]); + } +} + +// Is the given node something of the form +// self.cow +// self.horse[0] +// self.a[6][7][self.storage[3]].chicken[9] +bool isNodeStorageVariable(Node node) { + std::vector nodez; + nodez.push_back(node); + while (1) { + if (nodez.back().type == TOKEN) return false; + if (nodez.back().args.size() == 0) return false; + if (nodez.back().val != "." && nodez.back().val != "access") + return false; + if (nodez.back().args[0].val == "self") return true; + nodez.push_back(nodez.back().args[0]); + } +} + +// Main pattern matching routine, for those patterns that can be expressed +// using our standard mini-language above +// +// Returns two values. First, a boolean to determine whether the node matches +// the pattern, second, if the node does match then a map mapping variables +// in the pattern to nodes +matchResult match(Node p, Node n) { + matchResult o; + o.success = false; + if (p.type == TOKEN) { + if (p.val == n.val && n.type == TOKEN) o.success = true; + else if (p.val[0] == '$' || p.val[0] == '@') { + o.success = true; + o.map[p.val.substr(1)] = n; + } + } + else if (n.type==TOKEN || p.val!=n.val || p.args.size()!=n.args.size()) { + // do nothing + } + else { + for (unsigned i = 0; i < p.args.size(); i++) { + matchResult oPrime = match(p.args[i], n.args[i]); + if (!oPrime.success) { + o.success = false; + return o; + } + for (std::map::iterator it = oPrime.map.begin(); + it != oPrime.map.end(); + it++) { + o.map[(*it).first] = (*it).second; + } + } + o.success = true; + } + return o; +} + + +// Fills in the pattern with a dictionary mapping variable names to +// nodes (these dicts are generated by match). Match and subst together +// create a full pattern-matching engine. +Node subst(Node pattern, + std::map dict, + std::string varflag, + Metadata m) { + // Swap out patterns at the token level + if (pattern.metadata.ln == -1) + pattern.metadata = m; + if (pattern.type == TOKEN && + pattern.val[0] == '$') { + if (dict.count(pattern.val.substr(1))) { + return dict[pattern.val.substr(1)]; + } + else { + return token(varflag + pattern.val.substr(1), m); + } + } + // Other tokens are untouched + else if (pattern.type == TOKEN) { + return pattern; + } + // Substitute recursively for ASTs + else { + std::vector args; + for (unsigned i = 0; i < pattern.args.size(); i++) { + args.push_back(subst(pattern.args[i], dict, varflag, m)); + } + return asn(pattern.val, args, m); + } +} + +// Transforms a sequence containing two-argument with statements +// into a statement containing those statements in nested form +Node withTransform (Node source) { + Node o = token("--"); + Metadata m = source.metadata; + std::vector args; + for (int i = source.args.size() - 1; i >= 0; i--) { + Node a = source.args[i]; + if (a.val == "with" && a.args.size() == 2) { + std::vector flipargs; + for (int j = args.size() - 1; j >= 0; j--) + flipargs.push_back(args[i]); + if (o.val != "--") + flipargs.push_back(o); + o = asn("with", a.args[0], a.args[1], asn("seq", flipargs, m), m); + args = std::vector(); + } + else { + args.push_back(a); + } + } + std::vector flipargs; + for (int j = args.size() - 1; j >= 0; j--) + flipargs.push_back(args[j]); + if (o.val != "--") + flipargs.push_back(o); + return asn("seq", flipargs, m); +} diff --git a/libserpent/rewriteutils.h b/libserpent/rewriteutils.h new file mode 100644 index 000000000..3a9a837ad --- /dev/null +++ b/libserpent/rewriteutils.h @@ -0,0 +1,76 @@ +#ifndef ETHSERP_REWRITEUTILS +#define ETHSERP_REWRITEUTILS + +#include +#include +#include +#include +#include "util.h" + +// Valid functions and their min and max argument counts +extern std::string validFunctions[][3]; + +extern std::map vfMap; + +bool isValidFunctionName(std::string f); + +// Converts deep array access into ordered list of the arguments +// along the descent +std::vector listfyStorageAccess(Node node); + +// Cool function for debug purposes (named cerrStringList to make +// all prints searchable via 'cerr') +void cerrStringList(std::vector s, std::string suffix=""); + +// Is the given node something of the form +// self.cow +// self.horse[0] +// self.a[6][7][self.storage[3]].chicken[9] +bool isNodeStorageVariable(Node node); + +// Applies rewrite rules adding without wrapper +Node rewriteChunk(Node inp); + +// Match result storing object +struct matchResult { + bool success; + std::map map; +}; + +// Match node to pattern +matchResult match(Node p, Node n); + +// Substitute node using pattern +Node subst(Node pattern, + std::map dict, + std::string varflag, + Metadata m); + +Node withTransform(Node source); + +class rewriteRule { + public: + rewriteRule(Node p, Node s) { + pattern = p; + substitution = s; + } + Node pattern; + Node substitution; +}; + +class rewriteRuleSet { + public: + rewriteRuleSet() { + ruleLists = std::map >(); + } + void addRule(rewriteRule r) { + if (!ruleLists.count(r.pattern.val)) + ruleLists[r.pattern.val] = std::vector(); + ruleLists[r.pattern.val].push_back(r); + } + std::map > ruleLists; +}; + + + +#endif From 0b5b6c7cd40adc20944067bca42a9434c70a51d1 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 16:27:17 +0100 Subject: [PATCH 52/57] Adressing some natspec issues --- libsolidity/InterfaceHandler.cpp | 4 ++-- libsolidity/Scanner.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index 9ae284b40..5b5682ff1 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -170,7 +170,7 @@ std::string::const_iterator InterfaceHandler::parseDocTagLine(std::string::const bool _appending) { auto nlPos = std::find(_pos, _end, '\n'); - if (_appending && *_pos != ' ') + if (_appending && _pos < _end && *_pos != ' ') _tagString += " "; std::copy(_pos, nlPos, back_inserter(_tagString)); m_lastTag = _tagType; @@ -204,7 +204,7 @@ std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::con solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); auto pair = m_params.back(); - if (*_pos != ' ') + if (_pos < _end && *_pos != ' ') pair.second += " "; auto nlPos = std::find(_pos, _end, '\n'); std::copy(_pos, nlPos, back_inserter(pair.second)); diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 69b61ce4b..90a3b4545 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -328,6 +328,7 @@ Token::Value Scanner::scanMultiLineDocComment() void Scanner::scanToken() { + int savedPosition; m_nextToken.literal.clear(); m_nextSkippedComment.literal.clear(); Token::Value token; @@ -428,6 +429,7 @@ void Scanner::scanToken() break; case '/': // / // /* /= + savedPosition = getSourcePos(); advance(); if (m_char == '/') { @@ -436,7 +438,7 @@ void Scanner::scanToken() else if (m_char == '/') { Token::Value comment; - m_nextSkippedComment.location.start = getSourcePos(); + m_nextSkippedComment.location.start = savedPosition; comment = scanSingleLineDocComment(); m_nextSkippedComment.location.end = getSourcePos(); m_nextSkippedComment.token = comment; @@ -447,12 +449,13 @@ void Scanner::scanToken() } else if (m_char == '*') { + // /** doxygent style natspec comment if (!advance()) /* slash star comment before EOS */ token = Token::WHITESPACE; else if (m_char == '*') { Token::Value comment; - m_nextSkippedComment.location.start = getSourcePos(); + m_nextSkippedComment.location.start = savedPosition; comment = scanMultiLineDocComment(); m_nextSkippedComment.location.end = getSourcePos(); m_nextSkippedComment.token = comment; From 73593674419a4c2e2aea7a03a554b5577aa31405 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 16:48:25 +0100 Subject: [PATCH 53/57] More multiline natspec tests and small issue fix --- libsolidity/Scanner.cpp | 4 ++-- test/solidityScanner.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 90a3b4545..124c88d92 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -212,7 +212,7 @@ bool Scanner::skipWhitespace() bool Scanner::skipWhitespaceExceptLF() { int const startPosition = getSourcePos(); - while (m_char == ' ' || m_char == '\t') + while (isWhiteSpace(m_char) && !isLineTerminator(m_char)) advance(); // Return whether or not we skipped any characters. return getSourcePos() != startPosition; @@ -305,7 +305,7 @@ Token::Value Scanner::scanMultiLineDocComment() endFound = true; break; } - else + else if (charsAdded) addCommentLiteralChar('\n'); } diff --git a/test/solidityScanner.cpp b/test/solidityScanner.cpp index 159e53055..355ea9e22 100644 --- a/test/solidityScanner.cpp +++ b/test/solidityScanner.cpp @@ -189,6 +189,30 @@ BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed) BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); } +BOOST_AUTO_TEST_CASE(multiline_documentation_no_stars) +{ + Scanner scanner(CharStream("some other tokens /**\n" + " Send $(value / 1000) chocolates to the user\n" + "*/")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); +} + +BOOST_AUTO_TEST_CASE(multiline_documentation_whitespace_hell) +{ + Scanner scanner(CharStream("some other tokens /** \t \r \n" + "\t \r * Send $(value / 1000) chocolates to the user\n" + "*/")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); + BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user"); +} + BOOST_AUTO_TEST_CASE(comment_before_eos) { Scanner scanner(CharStream("//")); From b162e267acee2654e000a71af05b5d93db0d613f Mon Sep 17 00:00:00 2001 From: ethdev Date: Thu, 18 Dec 2014 17:10:00 +0100 Subject: [PATCH 54/57] windows fixes --- libserpent/compiler.cpp | 2 +- libserpent/rewriteutils.cpp | 1 + libserpent/util.cpp | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libserpent/compiler.cpp b/libserpent/compiler.cpp index a3e5b1c60..886eee6df 100644 --- a/libserpent/compiler.cpp +++ b/libserpent/compiler.cpp @@ -173,7 +173,7 @@ programData opcodeify(Node node, } // Comments do nothing else if (node.val == "comment") { - Node nodelist[] = { }; + Node* nodelist = nullptr; return pd(aux, multiToken(nodelist, 0, m), 0); } // Custom operation sequence diff --git a/libserpent/rewriteutils.cpp b/libserpent/rewriteutils.cpp index 0d810bdbc..e6429434a 100644 --- a/libserpent/rewriteutils.cpp +++ b/libserpent/rewriteutils.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "util.h" #include "lllparser.h" #include "bignum.h" diff --git a/libserpent/util.cpp b/libserpent/util.cpp index 5e83c0e41..4b99a5ea5 100644 --- a/libserpent/util.cpp +++ b/libserpent/util.cpp @@ -5,6 +5,7 @@ #include "util.h" #include "bignum.h" #include +#include #include //Token or value node constructor From 49f60a1d6d0b93fad28cea57e03beb261d9f09b1 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 17:30:10 +0100 Subject: [PATCH 55/57] Factoring forward slash scanning out to its own function --- libsolidity/Scanner.cpp | 85 ++++++++++++++++++++++------------------- libsolidity/Scanner.h | 2 + 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 124c88d92..f22e69bc8 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -326,9 +326,52 @@ Token::Value Scanner::scanMultiLineDocComment() return Token::COMMENT_LITERAL; } +Token::Value Scanner::scanSlash() +{ + int firstSlashPosition = getSourcePos(); + advance(); + if (m_char == '/') + { + if (!advance()) /* double slash comment directly before EOS */ + return Token::WHITESPACE; + else if (m_char == '/') + { + // doxygen style /// comment + Token::Value comment; + m_nextSkippedComment.location.start = firstSlashPosition; + comment = scanSingleLineDocComment(); + m_nextSkippedComment.location.end = getSourcePos(); + m_nextSkippedComment.token = comment; + return Token::WHITESPACE; + } + else + return skipSingleLineComment(); + } + else if (m_char == '*') + { + // doxygen style /** natspec comment + if (!advance()) /* slash star comment before EOS */ + return Token::WHITESPACE; + else if (m_char == '*') + { + Token::Value comment; + m_nextSkippedComment.location.start = firstSlashPosition; + comment = scanMultiLineDocComment(); + m_nextSkippedComment.location.end = getSourcePos(); + m_nextSkippedComment.token = comment; + return Token::WHITESPACE; + } + else + return skipMultiLineComment(); + } + else if (m_char == '=') + return selectToken(Token::ASSIGN_DIV); + else + return Token::DIV; +} + void Scanner::scanToken() { - int savedPosition; m_nextToken.literal.clear(); m_nextSkippedComment.literal.clear(); Token::Value token; @@ -429,45 +472,7 @@ void Scanner::scanToken() break; case '/': // / // /* /= - savedPosition = getSourcePos(); - advance(); - if (m_char == '/') - { - if (!advance()) /* double slash comment directly before EOS */ - token = Token::WHITESPACE; - else if (m_char == '/') - { - Token::Value comment; - m_nextSkippedComment.location.start = savedPosition; - comment = scanSingleLineDocComment(); - m_nextSkippedComment.location.end = getSourcePos(); - m_nextSkippedComment.token = comment; - token = Token::WHITESPACE; - } - else - token = skipSingleLineComment(); - } - else if (m_char == '*') - { - // /** doxygent style natspec comment - if (!advance()) /* slash star comment before EOS */ - token = Token::WHITESPACE; - else if (m_char == '*') - { - Token::Value comment; - m_nextSkippedComment.location.start = savedPosition; - comment = scanMultiLineDocComment(); - m_nextSkippedComment.location.end = getSourcePos(); - m_nextSkippedComment.token = comment; - token = Token::WHITESPACE; - } - else - token = skipMultiLineComment(); - } - else if (m_char == '=') - token = selectToken(Token::ASSIGN_DIV); - else - token = Token::DIV; + token = scanSlash(); break; case '&': // & && &= diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h index 5e70db51d..5b90a94eb 100644 --- a/libsolidity/Scanner.h +++ b/libsolidity/Scanner.h @@ -194,6 +194,8 @@ private: Token::Value scanString(); Token::Value scanSingleLineDocComment(); Token::Value scanMultiLineDocComment(); + /// Scans a slash '/' and depending on the characters returns the appropriate token + Token::Value scanSlash(); /// Scans an escape-sequence which is part of a string and adds the /// decoded character to the current literal. Returns true if a pattern From 4165e45464eaec9ed8f62d2571f2a10111599376 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 18 Dec 2014 18:16:43 +0100 Subject: [PATCH 56/57] updating solidity parser natspec tests to comply with recent changes --- test/SolidityParser.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/SolidityParser.cpp b/test/SolidityParser.cpp index f978cdd9b..c14c05122 100644 --- a/test/SolidityParser.cpp +++ b/test/SolidityParser.cpp @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(function_natspec_documentation) BOOST_REQUIRE_NO_THROW(contract = parseText(text)); auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is a test function"); + BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is a test function"); } BOOST_AUTO_TEST_CASE(function_normal_comments) @@ -166,17 +166,17 @@ BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 1"); + BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 1"); BOOST_REQUIRE_NO_THROW(function = functions.at(1)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 2"); + BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 2"); BOOST_REQUIRE_NO_THROW(function = functions.at(2)); BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr, "Should not have gotten natspec comment for functionName3()"); BOOST_REQUIRE_NO_THROW(function = functions.at(3)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 4"); + BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 4"); } BOOST_AUTO_TEST_CASE(multiline_function_documentation) @@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(multiline_function_documentation) BOOST_REQUIRE_NO_THROW(function = functions.at(0)); BOOST_CHECK_EQUAL(*function->getDocumentation(), - " This is a test function\n" + "This is a test function\n" " and it has 2 lines"); } @@ -220,11 +220,11 @@ BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), " fun1 description"); + BOOST_CHECK_EQUAL(*function->getDocumentation(), "fun1 description"); BOOST_REQUIRE_NO_THROW(function = functions.at(1)); BOOST_CHECK_EQUAL(*function->getDocumentation(), - " This is a test function\n" + "This is a test function\n" " and it has 2 lines"); } From 672c1ca15b5d749609ef8c68267fe58e9a69e6e8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 18 Dec 2014 18:44:55 +0100 Subject: [PATCH 57/57] Version bump. --- libdevcore/Common.cpp | 2 +- libethcore/CommonEth.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 55250b418..1de7ef957 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.7.12"; +char const* Version = "0.7.13"; } diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 5e510572e..ff5e6aed0 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -32,7 +32,7 @@ namespace dev namespace eth { -const unsigned c_protocolVersion = 49; +const unsigned c_protocolVersion = 50; const unsigned c_databaseVersion = 5; static const vector> g_units =