From cc8583ec7d6fd86ca7e129475fde32b76d102e79 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 27 Sep 2016 21:37:32 +0200 Subject: Function types. --- libsolidity/analysis/ReferencesResolver.cpp | 17 +++++ libsolidity/analysis/ReferencesResolver.h | 1 + libsolidity/ast/AST.h | 35 +++++++++ libsolidity/ast/ASTForward.h | 1 + libsolidity/ast/ASTJsonConverter.cpp | 14 ++++ libsolidity/ast/ASTJsonConverter.h | 2 + libsolidity/ast/ASTPrinter.cpp | 12 ++++ libsolidity/ast/ASTPrinter.h | 2 + libsolidity/ast/ASTVisitor.h | 4 ++ libsolidity/ast/AST_accept.h | 20 ++++++ libsolidity/ast/Types.cpp | 65 +++++++++++++++-- libsolidity/ast/Types.h | 10 ++- libsolidity/parsing/Parser.cpp | 83 +++++++++++++--------- libsolidity/parsing/Parser.h | 14 ++++ test/libsolidity/SolidityEndToEndTest.cpp | 18 +++++ test/libsolidity/SolidityNameAndTypeResolution.cpp | 49 +++++++++++++ test/libsolidity/SolidityParser.cpp | 51 +++++++++++++ 17 files changed, 359 insertions(+), 39 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index a7b9e8b8..41cad922 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -83,6 +83,23 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } +void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) +{ + switch (_typeName.visibility()) + { + case VariableDeclaration::Visibility::Default: + case VariableDeclaration::Visibility::Internal: + case VariableDeclaration::Visibility::External: + break; + default: + typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + } + + // Do we allow storage references for external functions? + + _typeName.annotation().type = make_shared(_typeName); +} + void ReferencesResolver::endVisit(Mapping const& _typeName) { TypePointer keyType = _typeName.keyType().annotation().type; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 1986b2bb..bfaef2e1 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -62,6 +62,7 @@ private: virtual bool visit(Identifier const& _identifier) override; virtual bool visit(ElementaryTypeName const& _typeName) override; virtual void endVisit(UserDefinedTypeName const& _typeName) override; + virtual void endVisit(FunctionTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; virtual void endVisit(ArrayTypeName const& _typeName) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 1b42c499..a2b70fe9 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -822,6 +822,41 @@ private: std::vector m_namePath; }; +/** + * A literal function type. Its source form is "function (paramType1, paramType2) internal / external returns (retType1, retType2)" + */ +class FunctionTypeName: public TypeName +{ +public: + FunctionTypeName( + SourceLocation const& _location, + ASTPointer const& _parameterTypes, + ASTPointer const& _returnTypes, + Declaration::Visibility _visibility, + bool _isDeclaredConst, + bool _isPayable + ): + TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + m_visibility(_visibility), m_isDeclaredConst(_isDeclaredConst), m_isPayable(_isPayable) + {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector> const& parameterTypes() const { return m_parameterTypes->parameters(); } + std::vector> const& returnParameterTypes() const { return m_returnTypes->parameters(); } + + Declaration::Visibility visibility() const { return m_visibility; } + bool isDeclaredConst() const { return m_isDeclaredConst; } + bool isPayable() const { return m_isPayable; } + +private: + ASTPointer m_parameterTypes; + ASTPointer m_returnTypes; + Declaration::Visibility m_visibility; + bool m_isDeclaredConst; + bool m_isPayable; +}; + /** * A mapping type. Its source form is "mapping('keyType' => 'valueType')" */ diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 59fc1b57..52bbf396 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -54,6 +54,7 @@ class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; class UserDefinedTypeName; +class FunctionTypeName; class Mapping; class ArrayTypeName; class Statement; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 3fce1180..717a80ee 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -226,6 +226,15 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) return true; } +bool ASTJsonConverter::visit(FunctionTypeName const& _node) +{ + addJsonNode(_node, "FunctionTypeName", { + make_pair("payable", _node.isPayable()), + make_pair("constant", _node.isDeclaredConst()) + }); + return true; +} + bool ASTJsonConverter::visit(Mapping const& _node) { addJsonNode(_node, "Mapping", {}, true); @@ -507,6 +516,11 @@ void ASTJsonConverter::endVisit(UserDefinedTypeName const&) { } +void ASTJsonConverter::endVisit(FunctionTypeName const&) +{ + goUp(); +} + void ASTJsonConverter::endVisit(Mapping const&) { goUp(); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 7c7b37f8..0a71779c 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -69,6 +69,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -114,6 +115,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 27266968..053b9b82 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -164,6 +164,13 @@ bool ASTPrinter::visit(UserDefinedTypeName const& _node) return goDeeper(); } +bool ASTPrinter::visit(FunctionTypeName const& _node) +{ + writeLine("FunctionTypeName"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Mapping const& _node) { writeLine("Mapping"); @@ -442,6 +449,11 @@ void ASTPrinter::endVisit(UserDefinedTypeName const&) m_indentation--; } +void ASTPrinter::endVisit(FunctionTypeName const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Mapping const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index f0ab1098..9f88a1fd 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -63,6 +63,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -106,6 +107,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 3a1b55d3..e72afe69 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -61,6 +61,7 @@ public: virtual bool visit(TypeName& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); } virtual bool visit(Mapping& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } virtual bool visit(InlineAssembly& _node) { return visitNode(_node); } @@ -106,6 +107,7 @@ public: virtual void endVisit(TypeName& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); } virtual void endVisit(Mapping& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } virtual void endVisit(InlineAssembly& _node) { endVisitNode(_node); } @@ -163,6 +165,7 @@ public: virtual bool visit(TypeName const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); } virtual bool visit(Mapping const& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName const& _node) { return visitNode(_node); } virtual bool visit(Block const& _node) { return visitNode(_node); } @@ -208,6 +211,7 @@ public: virtual void endVisit(TypeName const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Mapping const& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Block const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index b5a3806b..f521e092 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -327,6 +327,26 @@ void UserDefinedTypeName::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void FunctionTypeName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void FunctionTypeName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Mapping::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6ad74d28..808b0c55 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1805,6 +1805,23 @@ FunctionType::FunctionType(EventDefinition const& _event): swap(paramNames, m_parameterNames); } +FunctionType::FunctionType(FunctionTypeName const& _typeName): + m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), + m_isConstant(_typeName.isDeclaredConst()), + m_isPayable(_typeName.isPayable()) +{ + for (auto const& t: _typeName.parameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + m_parameterTypes.push_back(t->annotation().type); + } + for (auto const& t: _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for return parameter."); + m_returnParameterTypes.push_back(t->annotation().type); + } +} + FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _contract) { FunctionDefinition const* constructor = _contract.constructor(); @@ -1885,17 +1902,47 @@ string FunctionType::toString(bool _short) const string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); - name += ") returns ("; + name += ") "; + if (m_isConstant) + name += "constant "; + if (m_isPayable) + name += "payable "; + if (m_location == Location::External) + name += "external "; + name += "returns ("; for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); return name + ")"; } +unsigned FunctionType::calldataEncodedSize(bool _padded) const +{ + unsigned size = storageBytes(); + if (_padded) + size = ((size + 31) / 32) * 32; + return size; +} + u256 FunctionType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable function type requested.")); + if (m_location == Location::External || m_location == Location::Internal) + return 1; + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + +unsigned FunctionType::storageBytes() const +{ + if (m_location == Location::External) + return 20 + 4; + else if (m_location == Location::Internal) + return 8; // it should really not be possible to create larger programs + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); } unsigned FunctionType::sizeOnStack() const @@ -2018,6 +2065,16 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con } } +TypePointer FunctionType::interfaceType(bool _inLibrary) const +{ + if (m_location != Location::External && m_location != Location::Internal) + return TypePointer(); + if (_inLibrary) + return shared_from_this(); + else + return make_shared(storageBytes()); +} + bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const { solAssert(!bound() || _selfType, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 082e16a6..358c7efc 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -821,6 +821,8 @@ public: explicit FunctionType(VariableDeclaration const& _varDecl); /// Creates the function type of an event. explicit FunctionType(EventDefinition const& _event); + /// Creates the type of a function type name. + explicit FunctionType(FunctionTypeName const& _typeName); /// Function type constructor to be used for a plain type (not derived from a declaration). FunctionType( strings const& _parameterTypes, @@ -891,11 +893,15 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; - virtual bool canBeStored() const override { return false; } + virtual unsigned calldataEncodedSize(bool _padded) const override; + virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned storageBytes() const override; + virtual bool isValueType() const override { return true; } + virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + virtual TypePointer interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an /// appropriate external types (i.e. the interfaceType()s) of input/return parameters of diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index df3ed7b2..421e358f 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -288,59 +288,61 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) return visibility; } -ASTPointer Parser::parseFunctionDefinition(ASTString const* _contractName) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) { - ASTNodeFactory nodeFactory(*this); - ASTPointer docstring; - if (m_scanner->currentCommentLiteral() != "") - docstring = make_shared(m_scanner->currentCommentLiteral()); - + FunctionHeaderParserResult result; expectToken(Token::Function); - ASTPointer name; - if (m_scanner->currentToken() == Token::LParen) - name = make_shared(); // anonymous function + if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared(); // anonymous function else - name = expectIdentifierToken(); + result.name = expectIdentifierToken(); VarDeclParserOptions options; options.allowLocationSpecifier = true; - ASTPointer parameters(parseParameterList(options)); - bool isDeclaredConst = false; - bool isPayable = false; - Declaration::Visibility visibility(Declaration::Visibility::Default); - vector> modifiers; + result.parameters = parseParameterList(options); while (true) { Token::Value token = m_scanner->currentToken(); if (token == Token::Const) { - isDeclaredConst = true; + result.isDeclaredConst = true; m_scanner->next(); } else if (m_scanner->currentToken() == Token::Payable) { - isPayable = true; + result.isPayable = true; m_scanner->next(); } - else if (token == Token::Identifier) - modifiers.push_back(parseModifierInvocation()); + else if (_allowModifiers && token == Token::Identifier) + result.modifiers.push_back(parseModifierInvocation()); else if (Token::isVisibilitySpecifier(token)) { - if (visibility != Declaration::Visibility::Default) + if (result.visibility != Declaration::Visibility::Default) fatalParserError(string("Multiple visibility specifiers.")); - visibility = parseVisibilitySpecifier(token); + result.visibility = parseVisibilitySpecifier(token); } else break; } - ASTPointer returnParameters; if (m_scanner->currentToken() == Token::Returns) { bool const permitEmptyParameterList = false; m_scanner->next(); - returnParameters = parseParameterList(options, permitEmptyParameterList); + result.returnParameters = parseParameterList(options, permitEmptyParameterList); } else - returnParameters = createEmptyParameterList(); + result.returnParameters = createEmptyParameterList(); + return result; +} + +ASTPointer Parser::parseFunctionDefinition(ASTString const* _contractName) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer docstring; + if (m_scanner->currentCommentLiteral() != "") + docstring = make_shared(m_scanner->currentCommentLiteral()); + + FunctionHeaderParserResult header = parseFunctionHeader(false, true); + ASTPointer block = ASTPointer(); nodeFactory.markEndPosition(); if (m_scanner->currentToken() != Token::Semicolon) @@ -350,17 +352,17 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* } else m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *name == *_contractName); + bool const c_isConstructor = (_contractName && *header.name == *_contractName); return nodeFactory.createNode( - name, - visibility, + header.name, + header.visibility, c_isConstructor, docstring, - parameters, - isDeclaredConst, - modifiers, - returnParameters, - isPayable, + header.parameters, + header.isDeclaredConst, + header.modifiers, + header.returnParameters, + header.isPayable, block ); } @@ -631,6 +633,8 @@ ASTPointer Parser::parseTypeName(bool _allowVar) fatalParserError(string("Expected explicit type name.")); m_scanner->next(); } + else if (token == Token::Function) + type = parseFunctionType(); else if (token == Token::Mapping) type = parseMapping(); else if (token == Token::Identifier) @@ -653,6 +657,19 @@ ASTPointer Parser::parseTypeName(bool _allowVar) return type; } +ASTPointer Parser::parseFunctionType() +{ + ASTNodeFactory nodeFactory(*this); + FunctionHeaderParserResult header = parseFunctionHeader(true, false); + return nodeFactory.createNode( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); +} + ASTPointer Parser::parseMapping() { ASTNodeFactory nodeFactory(*this); @@ -1278,7 +1295,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const Token::Value token(m_scanner->currentToken()); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); - if (token == Token::Mapping || token == Token::Var) + if (token == Token::Mapping || token == Token::Function || token == Token::Var) return LookAheadInfo::VariableDeclarationStatement; if (mightBeTypeName) { diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 26f347cb..a59d2688 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -53,6 +53,18 @@ private: bool allowLocationSpecifier = false; }; + /// This struct is shared for parsing a function header and a function type. + struct FunctionHeaderParserResult + { + ASTPointer name; + ASTPointer parameters; + ASTPointer returnParameters; + Declaration::Visibility visibility = Declaration::Visibility::Default; + bool isDeclaredConst = false; + bool isPayable = false; + std::vector> modifiers; + }; + ///@{ ///@name Parsing functions for the AST nodes ASTPointer parsePragmaDirective(); @@ -60,6 +72,7 @@ private: ASTPointer parseContractDefinition(bool _isLibrary); ASTPointer parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); ASTPointer parseFunctionDefinition(ASTString const* _contractName); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); @@ -75,6 +88,7 @@ private: ASTPointer parseIdentifier(); ASTPointer parseUserDefinedTypeName(); ASTPointer parseTypeName(bool _allowVar); + ASTPointer parseFunctionType(); ASTPointer parseMapping(); ASTPointer parseParameterList( VarDeclParserOptions const& _options, diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d8924250..c9097663 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7606,6 +7606,24 @@ BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(pass_function_types_internally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return eval(g, x); + } + function eval(function(uint) returns (uint) x, uint a) returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); +} + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index f498a0b9..7f12cd86 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4132,6 +4132,55 @@ BOOST_AUTO_TEST_CASE(using_directive_for_missing_selftype) BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) returns (uint) x; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_parameter) +{ + char const* text = R"( + contract C { + function f(function(uint) returns (uint) g) returns (function(uint) returns (uint)) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(private_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) private returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(public_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) public returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index ec23d5fd..69b8d0f0 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1241,6 +1241,57 @@ BOOST_AUTO_TEST_CASE(payable_accessor) BOOST_CHECK(!successParse(text)); } +BOOST_AUTO_TEST_CASE(function_type_in_expression) +{ + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function g() { + function (uint, uint) internal returns (uint) f1 = f; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) +{ + // TODO disambiguate from fallback function + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function (uint, uint) internal returns (uint) f1 = f; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_in_struct) +{ + char const* text = R"( + contract test { + struct S { + function (uint x, uint y) internal returns (uint a) f; + function (uint, uint) external returns (uint) g; + uint d; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_parameter) +{ + char const* text = R"( + contract test { + function f(function(uint) external returns (uint) g) internal returns (uint a) { + return g(1); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit From dd173f83e3a6a9046d1aa7e64cb171598a73b272 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 28 Sep 2016 19:22:23 +0200 Subject: Code generator for function types. --- libsolidity/ast/Types.cpp | 12 +++++- libsolidity/ast/Types.h | 1 + libsolidity/codegen/CompilerUtils.cpp | 34 ++++++++++++++- libsolidity/codegen/ContractCompiler.cpp | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 49 +++++++++++++++++++++- test/libsolidity/SolidityNameAndTypeResolution.cpp | 33 +++++++++++++++ 6 files changed, 127 insertions(+), 3 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 808b0c55..19a1b9d1 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2065,6 +2065,16 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con } } +TypePointer FunctionType::encodingType() const +{ + // Only external functions can be encoded, internal functions cannot leave code boundaries. + if (m_location == Location::External) + // This looks like bytes24, but bytes24 is stored differently on the stack. + return shared_from_this(); + else + return TypePointer(); +} + TypePointer FunctionType::interfaceType(bool _inLibrary) const { if (m_location != Location::External && m_location != Location::Internal) @@ -2072,7 +2082,7 @@ TypePointer FunctionType::interfaceType(bool _inLibrary) const if (_inLibrary) return shared_from_this(); else - return make_shared(storageBytes()); + return make_shared(8 * storageBytes()); } bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 358c7efc..691ddf29 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -901,6 +901,7 @@ public: virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + virtual TypePointer encodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 58d1caa9..38a7a23b 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -133,6 +133,17 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << u256(str->value().size()); m_context << Instruction::ADD; } + else if ( + _type.category() == Type::Category::Function && + dynamic_cast(_type).location() == FunctionType::Location::External + ) + { + solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + m_context << Instruction::DUP2 << Instruction::MSTORE; + m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; + } else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); @@ -206,7 +217,8 @@ void CompilerUtils::encodeToMemory( else if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::CallData) || - _givenTypes[i]->category() == Type::Category::StringLiteral + _givenTypes[i]->category() == Type::Category::StringLiteral || + _givenTypes[i]->category() == Type::Category::Function ) type = _givenTypes[i]; // delay conversion else @@ -678,6 +690,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp void CompilerUtils::pushZeroValue(Type const& _type) { + if (auto const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + m_context << m_context.errorTag(); + return; + } + } auto const* referenceType = dynamic_cast(&_type); if (!referenceType || referenceType->location() == DataLocation::Storage) { @@ -839,6 +859,18 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda } } + if (auto const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::External) + { + // We have to split the right-aligned
into two stack slots: + // address (right aligned), function identifier (right aligned) + m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; + m_context << u256(0xffffffffUL) << Instruction::AND; + } + } + return numBytes; } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 2aec3055..9cd893e8 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -293,6 +293,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); + solAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast(*type); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c9097663..76df1970 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7606,6 +7606,29 @@ BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function) +{ + char const* sourceCode = R"( + contract C { + function intern() returns (uint) { + function (uint) internal returns (uint) x; + x(); + return 7; + } + function extern() returns (uint) { + function (uint) external returns (uint) x; + x(); + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + // This should throw exceptions + BOOST_CHECK(callContractFunction("intern()") == encodeArgs()); + BOOST_CHECK(callContractFunction("extern()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(pass_function_types_internally) { char const* sourceCode = R"( @@ -7613,7 +7636,28 @@ BOOST_AUTO_TEST_CASE(pass_function_types_internally) function f(uint x) returns (uint) { return eval(g, x); } - function eval(function(uint) returns (uint) x, uint a) returns (uint) { + function eval(function(uint) returns (uint) x, uint a) internal returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); +} + +BOOST_AUTO_TEST_CASE(pass_function_types_externally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return this.eval(this.g, x); + } + function f2(uint x) returns (uint) { + return eval(this.g, x); + } + function eval(function(uint) external returns (uint) x, uint a) returns (uint) { return x(a); } function g(uint x) returns (uint) { return x + 1; } @@ -7622,8 +7666,11 @@ BOOST_AUTO_TEST_CASE(pass_function_types_internally) compileAndRun(sourceCode, 0, "C"); BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); } +// TODO: storage, arrays + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 7f12cd86..2d469cdc 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4180,6 +4180,39 @@ BOOST_AUTO_TEST_CASE(public_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) +{ + // It should not be possible to give internal functions + // as parameters to external functions. + char const* text = R"( + contract C { + function f(function(uint) returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal) +{ + char const* text = R"( + library L { + function f(function(uint) returns (uint) x) internal { + } + } + )"; + BOOST_CHECK(success(text)); +} +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external) +{ + char const* text = R"( + library L { + function f(function(uint) returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { -- cgit From 97a3588701edafe9112f35272b5d4c6e23e574b9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 10 Oct 2016 23:06:44 +0200 Subject: Function type state variables. --- libsolidity/parsing/Parser.cpp | 97 +++++++++++++++++++++---------- libsolidity/parsing/Parser.h | 2 + test/libsolidity/SolidityEndToEndTest.cpp | 28 ++++++++- test/libsolidity/SolidityParser.cpp | 21 ++++++- 4 files changed, 115 insertions(+), 33 deletions(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 421e358f..e844861b 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -217,7 +217,9 @@ ASTPointer Parser::parseContractDefinition(bool _isLibrary) if (currentTokenValue == Token::RBrace) break; else if (currentTokenValue == Token::Function) - subNodes.push_back(parseFunctionDefinition(name.get())); + // This can be a function or a state variable of function type (especially + // complicated to distinguish fallback function from function type state variable) + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); else if (currentTokenValue == Token::Struct) subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) @@ -334,7 +336,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN return result; } -ASTPointer Parser::parseFunctionDefinition(ASTString const* _contractName) +ASTPointer Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName) { ASTNodeFactory nodeFactory(*this); ASTPointer docstring; @@ -343,28 +345,55 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* FunctionHeaderParserResult header = parseFunctionHeader(false, true); - ASTPointer block = ASTPointer(); - nodeFactory.markEndPosition(); - if (m_scanner->currentToken() != Token::Semicolon) + if ( + !header.modifiers.empty() || + !header.name->empty() || + m_scanner->currentToken() == Token::Semicolon || + m_scanner->currentToken() == Token::LBrace + ) { - block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + // this has to be a function + ASTPointer block = ASTPointer(); + nodeFactory.markEndPosition(); + if (m_scanner->currentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' + bool const c_isConstructor = (_contractName && *header.name == *_contractName); + return nodeFactory.createNode( + header.name, + header.visibility, + c_isConstructor, + docstring, + header.parameters, + header.isDeclaredConst, + header.modifiers, + header.returnParameters, + header.isPayable, + block + ); } else - m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *header.name == *_contractName); - return nodeFactory.createNode( - header.name, - header.visibility, - c_isConstructor, - docstring, - header.parameters, - header.isDeclaredConst, - header.modifiers, - header.returnParameters, - header.isPayable, - block - ); + { + // this has to be a state variable + ASTPointer type = nodeFactory.createNode( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); + type = parseTypeNameSuffix(type, nodeFactory); + VarDeclParserOptions options; + options.isStateVariable = true; + options.allowInitialValue = true; + auto node = parseVariableDeclaration(options, type); + expectToken(Token::Semicolon); + return node; + } } ASTPointer Parser::parseStructDefinition() @@ -613,6 +642,21 @@ ASTPointer Parser::parseUserDefinedTypeName() return nodeFactory.createNode(identifierPath); } +ASTPointer Parser::parseTypeNameSuffix(ASTPointer type, ASTNodeFactory& nodeFactory) +{ + while (m_scanner->currentToken() == Token::LBrack) + { + m_scanner->next(); + ASTPointer length; + if (m_scanner->currentToken() != Token::RBrack) + length = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + type = nodeFactory.createNode(type, length); + } + return type; +} + ASTPointer Parser::parseTypeName(bool _allowVar) { ASTNodeFactory nodeFactory(*this); @@ -644,16 +688,7 @@ ASTPointer Parser::parseTypeName(bool _allowVar) if (type) // Parse "[...]" postfixes for arrays. - while (m_scanner->currentToken() == Token::LBrack) - { - m_scanner->next(); - ASTPointer length; - if (m_scanner->currentToken() != Token::RBrack) - length = parseExpression(); - nodeFactory.markEndPosition(); - expectToken(Token::RBrack); - type = nodeFactory.createNode(type, length); - } + type = parseTypeNameSuffix(type, nodeFactory); return type; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index a59d2688..9295a7fa 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -73,6 +73,7 @@ private: ASTPointer parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + ASTPointer parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer parseFunctionDefinition(ASTString const* _contractName); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); @@ -87,6 +88,7 @@ private: ASTPointer parseModifierInvocation(); ASTPointer parseIdentifier(); ASTPointer parseUserDefinedTypeName(); + ASTPointer parseTypeNameSuffix(ASTPointer type, ASTNodeFactory& nodeFactory); ASTPointer parseTypeName(bool _allowVar); ASTPointer parseFunctionType(); ASTPointer parseMapping(); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 76df1970..0f392cab 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7669,7 +7669,33 @@ BOOST_AUTO_TEST_CASE(pass_function_types_externally) BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); } -// TODO: storage, arrays +BOOST_AUTO_TEST_CASE(store_function) +{ + char const* sourceCode = R"( + contract Other { + function addTwo(uint x) returns (uint) { return x + 2; } + } + contract C { + function (unction (uint) external returns (uint)) returns (uint) ev = eval; + function (uint) external returns (uint) x; + function store(function(uint) external returns (uint) y) { + x = y; + } + function eval(function(uint) external returns (uint) y) returns (uint) { + return y(7); + } + function t() returns (uint) { + this.store((new Other()).addTwo); + return ev(x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); +} + +// TODO: public function state variables, arrays BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 69b8d0f0..496f4703 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1256,7 +1256,26 @@ BOOST_AUTO_TEST_CASE(function_type_in_expression) BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) { - // TODO disambiguate from fallback function + char const* text = R"( + contract test { + function (uint, uint) internal returns (uint) f1; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_modifiers) +{ + char const* text = R"( + contract test { + function (uint, uint) modifier1() returns (uint) f1; + } + )"; + BOOST_CHECK(!successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_assignment) +{ char const* text = R"( contract test { function f(uint x, uint y) returns (uint a) {} -- cgit From 6f19559de02e0bf2b53e743678d53a4ea0414eae Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 13 Oct 2016 17:51:46 +0200 Subject: Fix some type checks and tests for internal / external function parameters. --- libsolidity/ast/Types.cpp | 6 ++-- test/libsolidity/SolidityEndToEndTest.cpp | 8 +++--- test/libsolidity/SolidityNameAndTypeResolution.cpp | 32 +++++++++++++++++++--- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 19a1b9d1..3afbee13 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2075,12 +2075,12 @@ TypePointer FunctionType::encodingType() const return TypePointer(); } -TypePointer FunctionType::interfaceType(bool _inLibrary) const +TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location != Location::External && m_location != Location::Internal) return TypePointer(); - if (_inLibrary) - return shared_from_this(); + if (m_location != Location::External) + return TypePointer(); else return make_shared(8 * storageBytes()); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 0f392cab..709e63b2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7612,12 +7612,12 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) contract C { function intern() returns (uint) { function (uint) internal returns (uint) x; - x(); + x(2); return 7; } function extern() returns (uint) { function (uint) external returns (uint) x; - x(); + x(2); return 7; } } @@ -7676,7 +7676,7 @@ BOOST_AUTO_TEST_CASE(store_function) function addTwo(uint x) returns (uint) { return x + 2; } } contract C { - function (unction (uint) external returns (uint)) returns (uint) ev = eval; + function (function (uint) external returns (uint)) returns (uint) ev = eval; function (uint) external returns (uint) x; function store(function(uint) external returns (uint) y) { x = y; @@ -7695,7 +7695,7 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } -// TODO: public function state variables, arrays +// TODO: arrays, libraries BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 2d469cdc..8b8c3aeb 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4148,7 +4148,19 @@ BOOST_AUTO_TEST_CASE(function_type_parameter) { char const* text = R"( contract C { - function f(function(uint) returns (uint) g) returns (function(uint) returns (uint)) { + function f(function(uint) external returns (uint) g) returns (function(uint) external returns (uint)) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_returned) +{ + char const* text = R"( + contract C { + function f() returns (function(uint) external returns (uint) g) { return g; } } @@ -4186,7 +4198,19 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) // as parameters to external functions. char const* text = R"( contract C { - function f(function(uint) returns (uint) x) { + function f(function(uint) internal returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) +{ + // It should not be possible to return internal functions from external functions. + char const* text = R"( + contract C { + function f() returns (function(uint) internal returns (uint) x) { } } )"; @@ -4197,7 +4221,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal { char const* text = R"( library L { - function f(function(uint) returns (uint) x) internal { + function f(function(uint) internal returns (uint) x) internal { } } )"; @@ -4207,7 +4231,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external { char const* text = R"( library L { - function f(function(uint) returns (uint) x) { + function f(function(uint) internal returns (uint) x) { } } )"; -- cgit From 95d7555e3c0e8fc4826114a336e0e717fe7a1a2d Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 14 Oct 2016 12:27:46 +0200 Subject: External functions in storage. --- libsolidity/codegen/CompilerUtils.cpp | 28 ++++++++++++++++----------- libsolidity/codegen/CompilerUtils.h | 7 +++++++ libsolidity/codegen/LValue.cpp | 32 ++++++++++++++++++++++++------- test/libsolidity/SolidityEndToEndTest.cpp | 28 ++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 38a7a23b..8a06268c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -139,8 +139,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ) { solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + combineExternalFunctionType(); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; } @@ -316,6 +315,21 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } +void CompilerUtils::splitExternalFunctionType() +{ + // We have to split the right-aligned
into two stack slots: + // address (right aligned), function identifier (right aligned) + m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; + m_context << u256(0xffffffffUL) << Instruction::AND; +} + +void CompilerUtils::combineExternalFunctionType() +{ + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in @@ -860,16 +874,8 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda } if (auto const* funType = dynamic_cast(&_type)) - { if (funType->location() == FunctionType::Location::External) - { - // We have to split the right-aligned
into two stack slots: - // address (right aligned), function identifier (right aligned) - m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; - m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - m_context << u256(0xffffffffUL) << Instruction::AND; - } - } + splitExternalFunctionType(); return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index da74dc90..0c9adf29 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,6 +114,13 @@ public: /// Stack post: void memoryCopy(); + /// Converts the combined and right-aligned external function type + ///
into two stack slots: + /// address (right aligned), function identifier (right aligned) + void splitExternalFunctionType(); + /// Performs the opposite operation of splitExternalFunctionType() + void combineExternalFunctionType(); + /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory /// if a reference type is converted from calldata or storage to memory. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 69a80b6a..98ab6d41 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -153,7 +153,8 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): { if (m_dataType->isValueType()) { - solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + if (m_dataType->category() != Type::Category::Function) + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } @@ -189,8 +190,16 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const dynamic_cast(*m_dataType).isSigned() ) m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; + else if ( + m_dataType->category() == Type::Category::Function && + dynamic_cast(*m_dataType).location() == FunctionType::Location::External + ) + CompilerUtils(m_context).splitExternalFunctionType(); else + { + solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; + } } } @@ -204,6 +213,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); if (m_dataType->storageBytes() == 32) { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size."); // offset should be zero m_context << Instruction::POP; if (!_move) @@ -222,16 +232,23 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::MUL; - m_context << Instruction::NOT << Instruction::AND; - // stack: value storage_ref multiplier cleared_value - m_context - << Instruction::SWAP1 << Instruction::DUP4; + m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; + // stack: value storage_ref cleared_value multiplier + utils.copyToStackTop(4, m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if (m_dataType->category() == Type::Category::FixedBytes) + if ( + m_dataType->category() == Type::Category::Function && + dynamic_cast(*m_dataType).location() == FunctionType::Location::External + ) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(); + else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) << Instruction::SWAP1 << Instruction::DIV; else + { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type."); // remove the higher order bits m_context << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) @@ -239,11 +256,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::DUP2 << Instruction::MUL << Instruction::DIV; + } m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; if (_move) - m_context << Instruction::POP; + utils.popStackElement(*m_dataType); } } else diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 709e63b2..b1529f8f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7695,7 +7695,33 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } -// TODO: arrays, libraries +BOOST_AUTO_TEST_CASE(function_type_library_internal) +{ + char const* sourceCode = R"( + library Utils { + function reduce(uint[] memory array, function(uint, uint) returns (uint) f, uint init) internal returns (uint) { + for (uint i = 0; i < array.length; i++) { + init = f(array[i], init); + } + return init; + } + function sum(uint a, uint b) internal returns (uint) { + return a + b; + } + } + contract C { + function f(uint[] x) returns (uint) { + return Utils.reduce(x, Utils.sum, 0); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256[])", 0x20, 3, u256(1), u256(7), u256(3)) == encodeArgs(u256(11))); +} + + +// TODO: arrays, libraries with external functions BOOST_AUTO_TEST_CASE(shift_constant_left) { -- cgit From ab3d1b024db6e208e4b63e2ecb07754af1540d6f Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:32:13 +0200 Subject: Add tests around calling functions returning functions returning functions --- test/libsolidity/SolidityEndToEndTest.cpp | 30 ++++++++++++++++++++++++++++++ test/libsolidity/SolidityParser.cpp | 15 +++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index b1529f8f..79dfd90e 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7721,6 +7721,36 @@ BOOST_AUTO_TEST_CASE(function_type_library_internal) } +BOOST_AUTO_TEST_CASE(call_function_returning_function) +{ + char const* sourceCode = R"( + contract test { + function f0() returns (uint) { + return 2; + } + function f1() returns (function() returns (uint)) { + returns f0; + } + function f2() returns (function() returns (function () returns (uint))) { + returns f1; + } + function f3() returns (function() returns (function () returns (function () returns (uint)))) + { + returns f2; + } + function f() returns (uint) { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + x = f3; + return x()()()(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); +} + + // TODO: arrays, libraries with external functions BOOST_AUTO_TEST_CASE(shift_constant_left) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 496f4703..d8c6fa10 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1311,6 +1311,21 @@ BOOST_AUTO_TEST_CASE(function_type_as_parameter) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(calling_function) +{ + char const* text = R"( + contract test { + function f() { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + uint y; + y = x()()()(); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + + BOOST_AUTO_TEST_SUITE_END() } -- cgit From 708b7b35adc337500b9c42832f4a5461e7579e55 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:46:48 +0200 Subject: Add a parser test for arrays containing functions --- test/libsolidity/SolidityParser.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index d8c6fa10..b1b1d858 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1325,6 +1325,16 @@ BOOST_AUTO_TEST_CASE(calling_function) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(array_of_functions) +{ + char const* text = R"( + contract test { + mapping (address => function() internal returns ()) stages; + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() -- cgit From 6172590b870832d7aa132209afb890125f301c15 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:54:12 +0200 Subject: Add a test around storing functions in an array --- test/libsolidity/SolidityEndToEndTest.cpp | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 79dfd90e..704fc1a1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7750,6 +7750,45 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); } +BOOST_AUTO_TEST_CASE(array_of_functions) +{ + char const* sourceCode = R"( + contract Flow { + bool success; + function checkSuccess() returns(bool) { + return success; + } + + mapping (address => function () internal returns()) stages; + + function stage0() internal { + stages[msg.sender] = stage1; + } + + function stage1() internal { + stages[msg.sender] = stage2; + } + + function stage2() internal { + success = true; + } + + function f () { + if (0 == steps[msg.sender]) + stages[msg.sender] = stage0; + stages[msg.sender](); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); + callContractFunction("f()"); + callContractFunction("f()"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); + callContractFunction("f()"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); +} // TODO: arrays, libraries with external functions -- cgit From 62492b67e783dd19c67850e2b8ae107aeeb83217 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 16:39:16 +0200 Subject: Changelog entry and small fixes. --- Changelog.md | 1 + libsolidity/ast/ASTJsonConverter.cpp | 5 +++++ libsolidity/ast/Types.cpp | 8 +++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index a392ec01..ce3343d8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.5 (unreleased) Features: + * Function types * Do-while loops: support for a C-style do{}while(); control structure * Inline assembly: support ``invalidJumpLabel`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 717a80ee..d6aca175 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -228,8 +228,13 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) bool ASTJsonConverter::visit(FunctionTypeName const& _node) { + string visibility = "internal"; + if (_node.visibility() == Declaration::Visibility::External) + visibility = "external"; + addJsonNode(_node, "FunctionTypeName", { make_pair("payable", _node.isPayable()), + make_pair("visibility", visibility), make_pair("constant", _node.isDeclaredConst()) }); return true; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3afbee13..15747a8b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2077,12 +2077,10 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { - if (m_location != Location::External && m_location != Location::Internal) - return TypePointer(); - if (m_location != Location::External) - return TypePointer(); - else + if (m_location == Location::External) return make_shared(8 * storageBytes()); + else + return TypePointer(); } bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const -- cgit From 679ea2820fd6bb76ddd294101ef548bab6cd6425 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:07 +0200 Subject: Part of the documentation. --- docs/types.rst | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 9ec9e526..2e1cbee9 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -234,7 +234,7 @@ Hexademical Literals behave like String Literals and have the same convertibilit .. _enums: Enums -===== +----- Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversions @@ -267,6 +267,46 @@ check the value ranges at runtime and a failure causes an exception. Enums need } } +.. index:: ! function type, ! type; function + +.. _function_types: + +Function Types +-------------- + +Functions can be assigned to variables and passed on together with function calls. +These types come in two flavours: *internal* and *external* functions. + +Internal functions can only be used inside the current contract (more specifically, +inside the current code unit, which also includes internal library functions) +because they cannot be executed outside of the +context of the current function. Calling an internal function is realized +by jumping to its entry label, just like when calling an function of the current +contract internally. + +External functions consist of an address and a function signature and they can +be passed via and returned from external function calls. + +Function types are notated as follows: + + function () internal / external returns () + +As always, the ``returns ()`` is optional. + +By default, function types are internal, so the ``internal`` keyword can be +omitted. + +If a function type variable is not initialized, calling it will result +in an exception. + +If external function types are used outside of the context of Solidity, +they are converted into the ``bytes24`` type. + +Example usage: + + library ArrayUtils { + + .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct Reference Types -- cgit From ff3553a34895c70c473a27c29464ebfc15375416 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:31 +0200 Subject: Change alignment. --- libsolidity/ast/Types.cpp | 2 +- libsolidity/codegen/CompilerUtils.cpp | 58 +++++++++++++++++++++-------------- libsolidity/codegen/CompilerUtils.h | 10 +++--- libsolidity/codegen/LValue.cpp | 4 +-- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 15747a8b..8cc125fe 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2078,7 +2078,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location == Location::External) - return make_shared(8 * storageBytes()); + return make_shared(storageBytes()); else return TypePointer(); } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 8a06268c..dedb53e7 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -139,7 +139,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ) { solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); - combineExternalFunctionType(); + combineExternalFunctionType(true); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; } @@ -315,19 +315,29 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } -void CompilerUtils::splitExternalFunctionType() +void CompilerUtils::splitExternalFunctionType(bool _leftAligned) { - // We have to split the right-aligned
into two stack slots: + // We have to split the left-aligned
into two stack slots: // address (right aligned), function identifier (right aligned) + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - m_context << u256(0xffffffffUL) << Instruction::AND; + if (!_leftAligned) + m_context << u256(0xffffffffUL) << Instruction::AND; } -void CompilerUtils::combineExternalFunctionType() +void CompilerUtils::combineExternalFunctionType(bool _leftAligned) { - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + if (_leftAligned) + m_context << (u256(1) << 224); + else + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160); + m_context << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::MUL; + m_context << Instruction::OR; } void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) @@ -856,27 +866,29 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.category() == Type::Category::FixedBytes; + bool isExternalFunctionType = false; + if (auto const* funType = dynamic_cast(&_type)) + if (funType->location() == FunctionType::Location::External) + isExternalFunctionType = true; if (numBytes == 0) + { m_context << Instruction::POP << u256(0); - else + return numBytes; + } + solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); + m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); + if (isExternalFunctionType) + splitExternalFunctionType(true); + else if (numBytes != 32) { - solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); - m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); - if (numBytes != 32) - { - // add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); - m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; - if (leftAligned) - m_context << shiftFactor << Instruction::MUL; - } + bool leftAligned = _type.category() == Type::Category::FixedBytes; + // add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); + m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; + if (leftAligned) + m_context << shiftFactor << Instruction::MUL; } - if (auto const* funType = dynamic_cast(&_type)) - if (funType->location() == FunctionType::Location::External) - splitExternalFunctionType(); - return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 0c9adf29..690452f9 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,12 +114,12 @@ public: /// Stack post: void memoryCopy(); - /// Converts the combined and right-aligned external function type - ///
into two stack slots: + /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) + /// external function type
into two stack slots: /// address (right aligned), function identifier (right aligned) - void splitExternalFunctionType(); - /// Performs the opposite operation of splitExternalFunctionType() - void combineExternalFunctionType(); + void splitExternalFunctionType(bool _rightAligned); + /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) + void combineExternalFunctionType(bool _rightAligned); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 98ab6d41..cb7cbbe3 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -194,7 +194,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_dataType->category() == Type::Category::Function && dynamic_cast(*m_dataType).location() == FunctionType::Location::External ) - CompilerUtils(m_context).splitExternalFunctionType(); + CompilerUtils(m_context).splitExternalFunctionType(false); else { solAssert(m_dataType->sizeOnStack() == 1, ""); @@ -241,7 +241,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc dynamic_cast(*m_dataType).location() == FunctionType::Location::External ) // Combine the two-item function type into a single stack slot. - utils.combineExternalFunctionType(); + utils.combineExternalFunctionType(false); else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) -- cgit From 87b148494bcf1dd4814fafe658dd81fef79cf8b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:42 +0200 Subject: Tests. --- test/libsolidity/SolidityEndToEndTest.cpp | 198 ++++++++++++++++++--- test/libsolidity/SolidityNameAndTypeResolution.cpp | 18 ++ test/libsolidity/SolidityParser.cpp | 7 +- 3 files changed, 197 insertions(+), 26 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 704fc1a1..ef64ad5a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7669,6 +7669,42 @@ BOOST_AUTO_TEST_CASE(pass_function_types_externally) BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); } +BOOST_AUTO_TEST_CASE(receive_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() returns (uint) { return 7; } + function f(function() external returns (uint) g) returns (uint) { + return g(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction( + "f(bytes24)", + FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + ) == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(return_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() {} + function f() returns (function() external) { + return this.g; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK( + callContractFunction("f()") == + FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + ); +} + BOOST_AUTO_TEST_CASE(store_function) { char const* sourceCode = R"( @@ -7676,7 +7712,7 @@ BOOST_AUTO_TEST_CASE(store_function) function addTwo(uint x) returns (uint) { return x + 2; } } contract C { - function (function (uint) external returns (uint)) returns (uint) ev = eval; + function (function (uint) external returns (uint)) returns (uint) ev; function (uint) external returns (uint) x; function store(function(uint) external returns (uint) y) { x = y; @@ -7685,6 +7721,7 @@ BOOST_AUTO_TEST_CASE(store_function) return y(7); } function t() returns (uint) { + ev = eval; this.store((new Other()).addTwo); return ev(x); } @@ -7728,15 +7765,15 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) function f0() returns (uint) { return 2; } - function f1() returns (function() returns (uint)) { - returns f0; + function f1() internal returns (function() returns (uint)) { + return f0; } - function f2() returns (function() returns (function () returns (uint))) { - returns f1; + function f2() internal returns (function() returns (function () returns (uint))) { + return f1; } - function f3() returns (function() returns (function () returns (function () returns (uint)))) + function f3() internal returns (function() returns (function () returns (function () returns (uint)))) { - returns f2; + return f2; } function f() returns (uint) { function() returns(function() returns(function() returns(function() returns(uint)))) x; @@ -7746,51 +7783,164 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) } )"; - compileAndRun(sourceCode, 0, "C"); + compileAndRun(sourceCode, 0, "test"); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); } -BOOST_AUTO_TEST_CASE(array_of_functions) +BOOST_AUTO_TEST_CASE(mapping_of_functions) { char const* sourceCode = R"( contract Flow { - bool success; - function checkSuccess() returns(bool) { - return success; - } + bool public success; - mapping (address => function () internal returns()) stages; + mapping (address => function () internal) stages; function stage0() internal { - stages[msg.sender] = stage1; + stages[msg.sender] = stage1; } function stage1() internal { - stages[msg.sender] = stage2; + stages[msg.sender] = stage2; } function stage2() internal { success = true; } - function f () { - if (0 == steps[msg.sender]) - stages[msg.sender] = stage0; + function Flow() { + stages[msg.sender] = stage0; + } + + function f() { stages[msg.sender](); } } )"; - compileAndRun(sourceCode, 0, "C"); + compileAndRun(sourceCode, 0, "Flow"); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - callContractFunction("f()"); - callContractFunction("f()"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - callContractFunction("f()"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); } -// TODO: arrays, libraries with external functions +BOOST_AUTO_TEST_CASE(packed_functions) +{ + char const* sourceCode = R"( + contract C { + // these should take the same slot + function() returns (uint) a; + function() external returns (uint) b; + uint8 public x; + + function set() { + x = 2; + a = g; + b = h; + } + function t1() returns (uint) { + return a(); + } + function t2() returns (uint) { + return b(); + } + function g() returns (uint) { + return 7; + } + function h() returns (uint) { + return 8; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs()); + BOOST_CHECK(callContractFunction("t1()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(2))); +} + +BOOST_AUTO_TEST_CASE(function_memory_array) +{ + char const* sourceCode = R"( + contract C { + function a(uint x) returns (uint) { return x + 1; } + function b(uint x) returns (uint) { return x + 2; } + function c(uint x) returns (uint) { return x + 3; } + function d(uint x) returns (uint) { return x + 5; } + function e(uint x) returns (uint) { return x + 8; } + function test(uint x, uint i) returns (uint) { + function(uint) internal returns (uint)[] arr = + new function(uint) internal returns (uint)[](10); + arr[0] = a; + arr[1] = b; + arr[2] = c; + arr[3] = d; + arr[4] = e; + return arr[i](x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(0)) == encodeArgs(u256(11))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(1)) == encodeArgs(u256(12))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(2)) == encodeArgs(u256(13))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(3)) == encodeArgs(u256(15))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(4)) == encodeArgs(u256(18))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(5)) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(function_delete) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function() internal returns (uint) y; + function set() returns (uint) { + y = a; + return y(); + } + funciton d() returns (uint) { + delete y; + return 1; + } + function ca() returns (uint) { + return y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("d()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(copy_function_storage_array) +{ + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[] x; + function() internal returns (uint)[] y; + function test() returns (uint) { + x.length = 10; + x[9] = a; + y = x; + return y[9](); + } + function a() returns (uint) { + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); +} BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 8b8c3aeb..e85d0fe6 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4227,6 +4227,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal )"; BOOST_CHECK(success(text)); } + BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external) { char const* text = R"( @@ -4238,6 +4239,23 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(function_type_arrays) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint)[] public x; + function(uint) internal returns (uint)[10] y; + function f() { + function(uint) returns (uint)[10] memory a; + function(uint) returns (uint)[10] storage b = y; + function(uint) external returns (uint)[] memory c; + c = new function(uint) external returns (uint)[](200); + } + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index b1b1d858..796da782 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1325,11 +1325,14 @@ BOOST_AUTO_TEST_CASE(calling_function) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(array_of_functions) +BOOST_AUTO_TEST_CASE(mapping_and_array_of_functions) { char const* text = R"( contract test { - mapping (address => function() internal returns ()) stages; + mapping (address => function() internal returns (uint)) a; + mapping (address => function() external) b; + mapping (address => function() external[]) c; + function() external[] d; } )"; BOOST_CHECK(successParse(text)); -- cgit From 502cc319d79c28cf398baa737e96c54563b9aafa Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 26 Oct 2016 18:01:08 +0200 Subject: Documentation examples. --- docs/types.rst | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 2e1cbee9..cac8b774 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -302,10 +302,82 @@ in an exception. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. -Example usage: +Example that shows how to use internal function types: library ArrayUtils { + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) returns (uint) f) + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + function reduce( + uint[] memory self, + function (uint) returns (uint) f + ) + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + function range(uint length) returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } + } + contract Pyramid { + using ArrayUtils for *; + function pyramid(uint l) return (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + function square(uint x) internal returns (uint) { + return x * x; + } + function sum(uint x, uint y) internal returns (uint) { + return x + y; + } + } + +Another example that uses external function types: + + contract Oracle { + struct Request { + bytes data; + function(bytes) external callback; + } + Request[] requests; + event NewRequest(uint); + function query(bytes data, function(bytes) external callback) { + requests.push(Request(data, callback)); + NewRequest(requests.length - 1); + } + function reply(uint requestID, bytes response) { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } + } + + contract OracleUser { + Oracle constant oracle = 0x1234567; // known contract + function buySomething() { + oracle.query("USD", oracleResponse); + } + function oracleResponse(bytes response) { + if (msg.sender != oracle) throw; + // Use the data + } + } + +Note that lambda or inline functions are planned but not yet supported. .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct -- cgit From cc847df3c20982372d601016382b0a93266467a4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 28 Oct 2016 17:30:56 +0200 Subject: Bugfix in code generator. --- libsolidity/codegen/LValue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index cb7cbbe3..d2c75445 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -234,7 +234,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::MUL; m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; // stack: value storage_ref cleared_value multiplier - utils.copyToStackTop(4, m_dataType->sizeOnStack()); + utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value if ( m_dataType->category() == Type::Category::Function && -- cgit From 3158a8ea7b06888472b09ac4bc5f6a5a2f7ae2ce Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 4 Nov 2016 15:23:48 +0100 Subject: test: add a test for storing an internal function in the constructor and then using the stored function in runtime --- test/libsolidity/SolidityEndToEndTest.cpp | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ef64ad5a..7dbadc48 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7732,6 +7732,52 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } +BOOST_AUTO_TEST_CASE(store_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + uint result_in_constructor; + function (uint) internal returns (uint) x; + function C () { + x = double; + result_in_constructor = use(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function use(uint _arg) returns (uint) { + return x(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("use(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4))); +} + +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) +{ + char const* sourceCode = R"( + contract C { + uint public initial; + function C() { + initial = double(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function runtime(uint _arg) returns (uint) { + return double(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("runtime(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4))); +} + BOOST_AUTO_TEST_CASE(function_type_library_internal) { char const* sourceCode = R"( -- cgit From b6992d740a849d28f7c2a52bef0ba6b3740c9179 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 7 Nov 2016 20:07:55 +0100 Subject: Tests for uninitialized storage functions. --- test/libsolidity/SolidityEndToEndTest.cpp | 54 ++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7dbadc48..44dba9b2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7629,6 +7629,29 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) BOOST_CHECK(callContractFunction("extern()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) +{ + // Storage default value of zero would be correct jump dest, this tests that + // that is properly handled. + char const* sourceCode = R"( + contract C { + function() internal returns (uint) x; + int mutex; + function t() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // If this test fails, it will jump to "0" and re-execute this function. + x(); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(pass_function_types_internally) { char const* sourceCode = R"( @@ -7949,7 +7972,7 @@ BOOST_AUTO_TEST_CASE(function_delete) y = a; return y(); } - funciton d() returns (uint) { + function d() returns (uint) { delete y; return 1; } @@ -7988,6 +8011,35 @@ BOOST_AUTO_TEST_CASE(copy_function_storage_array) BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) +{ + // This has to apply NOT to the functions because encoding in storage + // is different than encoding in memory. + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[20] x; + int mutex; + function one() returns (uint) { + function() internal returns (uint)[20] xmem; + x = xmem; + return 3; + } + function two() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // If this test fails, it will jump to "0" and re-execute this function. + x[0](); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("one()") == encodeArgs(u256(3))); + BOOST_CHECK(callContractFunction("two()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( -- cgit From 47794c1da406a28f0e8a10e3e57cd935f5cc7f3d Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 7 Nov 2016 20:08:05 +0100 Subject: Implement uninitialized storage functions. --- libevmasm/Assembly.cpp | 1 + libsolidity/codegen/CompilerContext.h | 2 ++ libsolidity/codegen/LValue.cpp | 26 +++++++++++++++----------- test/libsolidity/SolidityEndToEndTest.cpp | 4 +--- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e881c1e2..e19b6b0d 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -485,6 +485,7 @@ LinkerObject const& Assembly::assemble() const break; case Tag: tagPos[(unsigned)i.data()] = ret.bytecode.size(); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); assertThrow(i.data() != 0, AssemblyException, ""); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 0c1500b0..6c509685 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,6 +44,8 @@ namespace solidity { class CompilerContext { public: + bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } + void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index d2c75445..66449fc4 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -190,11 +190,11 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const dynamic_cast(*m_dataType).isSigned() ) m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; - else if ( - m_dataType->category() == Type::Category::Function && - dynamic_cast(*m_dataType).location() == FunctionType::Location::External - ) - CompilerUtils(m_context).splitExternalFunctionType(false); + else if (FunctionType const* fun = dynamic_cast(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + CompilerUtils(m_context).splitExternalFunctionType(false); + } else { solAssert(m_dataType->sizeOnStack() == 1, ""); @@ -236,12 +236,16 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storage_ref cleared_value multiplier utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if ( - m_dataType->category() == Type::Category::Function && - dynamic_cast(*m_dataType).location() == FunctionType::Location::External - ) - // Combine the two-item function type into a single stack slot. - utils.combineExternalFunctionType(false); + if (FunctionType const* fun = dynamic_cast(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(false); + else + m_context << + ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << + Instruction::AND; + } else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 44dba9b2..c665b050 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7631,8 +7631,6 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) { - // Storage default value of zero would be correct jump dest, this tests that - // that is properly handled. char const* sourceCode = R"( contract C { function() internal returns (uint) x; @@ -7641,7 +7639,7 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) if (mutex > 0) return 7; mutex = 1; - // If this test fails, it will jump to "0" and re-execute this function. + // Avoid re-executing this function if we jump somewhere. x(); return 2; } -- cgit From 0e5507c78c5b1602ff0f9e5530ab05f0a35ecc0d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 8 Nov 2016 11:43:42 +0100 Subject: Updates to the documentation. --- docs/types.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index cac8b774..1673c30d 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -274,14 +274,16 @@ check the value ranges at runtime and a failure causes an exception. Enums need Function Types -------------- -Functions can be assigned to variables and passed on together with function calls. -These types come in two flavours: *internal* and *external* functions. +Function types can be used to assign functions to variables and passing them +to or return them from function calls. Such variables and parameters have +to have function types. These types come in two flavours: *internal* and *external* +functions. Internal functions can only be used inside the current contract (more specifically, -inside the current code unit, which also includes internal library functions) -because they cannot be executed outside of the -context of the current function. Calling an internal function is realized -by jumping to its entry label, just like when calling an function of the current +inside the current code unit, which also includes internal library functions +and inherited functions) because they cannot be executed outside of the +context of the current contract. Calling an internal function is realized +by jumping to its entry label, just like when calling a function of the current contract internally. External functions consist of an address and a function signature and they can @@ -289,9 +291,11 @@ be passed via and returned from external function calls. Function types are notated as follows: - function () internal / external returns () + function () {internal|external} [constant] [returns ()] -As always, the ``returns ()`` is optional. +In contrast to the parameter types, the return types cannot be empty - if the +function type should not return anything, the whole ``returns ()`` +part has to be omitted. By default, function types are internal, so the ``internal`` keyword can be omitted. -- cgit From 5011d6339a185a55d6cdc3e952dea7bdd14a2ff7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 8 Nov 2016 11:48:28 +0100 Subject: Added function types to the grammar. --- libsolidity/grammar.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 6ad8994a..cfa779ec 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -31,13 +31,16 @@ EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}' IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')' ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')' +TypeNameList = '(' ( TypeName (',' TypeName )* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists VariableDeclaration = TypeName Identifier -TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName +TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' +FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' )* + ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' Block = '{' Statement* '}' -- cgit From c9f9b2ab4d421e99055425ace5deeb76d8f4fdd2 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 8 Nov 2016 15:25:32 +0100 Subject: codegen: add a compilation mode and a runtime context to CompilerContext --- libsolidity/codegen/Compiler.cpp | 6 +++--- libsolidity/codegen/Compiler.h | 4 +++- libsolidity/codegen/CompilerContext.h | 15 +++++++++++++++ libsolidity/codegen/ContractCompiler.h | 4 ++-- test/libsolidity/SolidityExpressionCompiler.cpp | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 1ae5dda9..bb8211ad 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,10 +33,10 @@ void Compiler::compileContract( std::map const& _contracts ) { - ContractCompiler runtimeCompiler(m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(m_context, m_optimize); + ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); if (m_optimize) @@ -54,7 +54,7 @@ void Compiler::compileClone( map const& _contracts ) { - ContractCompiler cloneCompiler(m_context, m_optimize); + ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); if (m_optimize) diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fccb68a9..56849ea0 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -35,7 +35,9 @@ class Compiler public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), - m_optimizeRuns(_runs) + m_optimizeRuns(_runs), + m_context(CompilationMode::Creation, &m_runtimeContext), + m_runtimeContext(CompilationMode::Runtime) { } void compileContract( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 6c509685..8b95c9f5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -37,6 +37,10 @@ namespace dev { namespace solidity { +/// Depending on the compilation is on the runtime code or the creation code, +/// the interpretation of internal function values differ. +enum class CompilationMode { Runtime, Creation }; + /** * Context to be shared by all units that compile the same contract. * It stores the generated bytecode and the position of identifiers in memory and on the stack. @@ -44,6 +48,13 @@ namespace solidity { class CompilerContext { public: + CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) : + m_mode(_mode), m_runtimeContext(_runtimeContext) + { + solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided"); + solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided"); + } + bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } void addMagicGlobal(MagicVariableDeclaration const& _declaration); @@ -230,6 +241,10 @@ private: std::vector m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; + /// The current mode of the compilation + CompilationMode m_mode; + /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. + CompilerContext *m_runtimeContext; }; } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 0799a543..fecf6f5a 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,11 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilerContext& _context, bool _optimise): + explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise): m_optimise(_optimise), m_context(_context) { - m_context = CompilerContext(); + m_context = CompilerContext(_mode, _runtimeContext); } void compileContract( diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index e9a05745..09d556a5 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -136,7 +136,7 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context; + CompilerContext context { CompilationMode::Runtime /* probably simpler */ }; context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack -- cgit From f21f794f3c380c9382ace4908c38d0f6c776ae17 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 09:36:38 +0100 Subject: delete for function types --- libsolidity/ast/Types.cpp | 7 ++++ libsolidity/ast/Types.h | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 19 ++++++++- test/libsolidity/SolidityNameAndTypeResolution.cpp | 45 ++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 8cc125fe..395faf55 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1897,6 +1897,13 @@ bool FunctionType::operator==(Type const& _other) const return true; } +TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Value::Delete) + return make_shared(); + return TypePointer(); +} + string FunctionType::toString(bool _short) const { string name = "function ("; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 691ddf29..9831bc42 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -892,6 +892,7 @@ public: TypePointer selfType() const; virtual bool operator==(Type const& _other) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c665b050..a1236803 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7960,7 +7960,7 @@ BOOST_AUTO_TEST_CASE(function_memory_array) BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(5)) == encodeArgs()); } -BOOST_AUTO_TEST_CASE(function_delete) +BOOST_AUTO_TEST_CASE(function_delete_storage) { char const* sourceCode = R"( contract C { @@ -7987,6 +7987,23 @@ BOOST_AUTO_TEST_CASE(function_delete) BOOST_CHECK(callContractFunction("ca()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(function_delete_stack) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function test() returns (uint) { + y = a; + delete y; + y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(copy_function_storage_array) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index e85d0fe6..62fb55f7 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4256,6 +4256,51 @@ BOOST_AUTO_TEST_CASE(function_type_arrays) BOOST_CHECK(success(text)); } +BOOST_AUTO_TEST_CASE(delete_function_type) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint) x; + function(uint) internal returns (uint) y; + function f() { + delete x; + var a = y; + delete a; + delete y; + var c = f; + delete c; + function(uint) internal returns (uint) g; + delete g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(delete_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete this.f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( -- cgit From e1df3bd77f78d5564fc173474015e5d84b192824 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 13:34:51 +0100 Subject: Fix tests. --- libsolidity/codegen/LValue.cpp | 12 +++++++++++- test/libsolidity/SolidityEndToEndTest.cpp | 31 ++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 66449fc4..2ec7f800 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -177,6 +177,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::POP << Instruction::SLOAD; else { + bool cleaned = false; m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; @@ -184,18 +185,27 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const // implementation should be very similar to the integer case. solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) + { m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; + cleaned = true; + } else if ( m_dataType->category() == Type::Category::Integer && dynamic_cast(*m_dataType).isSigned() ) + { m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; + cleaned = true; + } else if (FunctionType const* fun = dynamic_cast(m_dataType)) { if (fun->location() == FunctionType::Location::External) + { CompilerUtils(m_context).splitExternalFunctionType(false); + cleaned = true; + } } - else + if (!cleaned) { solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a1236803..8f9edadf 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7878,19 +7878,20 @@ BOOST_AUTO_TEST_CASE(mapping_of_functions) stages[msg.sender] = stage0; } - function f() { + function f() returns (uint) { stages[msg.sender](); + return 7; } } )"; compileAndRun(sourceCode, 0, "Flow"); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(true)); } BOOST_AUTO_TEST_CASE(packed_functions) @@ -7900,12 +7901,16 @@ BOOST_AUTO_TEST_CASE(packed_functions) // these should take the same slot function() returns (uint) a; function() external returns (uint) b; + function() external returns (uint) c; + function() returns (uint) d; uint8 public x; function set() { x = 2; + d = g; + c = this.h; + b = this.h; a = g; - b = h; } function t1() returns (uint) { return a(); @@ -7913,6 +7918,12 @@ BOOST_AUTO_TEST_CASE(packed_functions) function t2() returns (uint) { return b(); } + function t3() returns (uint) { + return a(); + } + function t4() returns (uint) { + return b(); + } function g() returns (uint) { return 7; } @@ -7926,6 +7937,8 @@ BOOST_AUTO_TEST_CASE(packed_functions) BOOST_CHECK(callContractFunction("set()") == encodeArgs()); BOOST_CHECK(callContractFunction("t1()") == encodeArgs(u256(7))); BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("t3()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t4()") == encodeArgs(u256(8))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(2))); } @@ -7939,7 +7952,7 @@ BOOST_AUTO_TEST_CASE(function_memory_array) function d(uint x) returns (uint) { return x + 5; } function e(uint x) returns (uint) { return x + 8; } function test(uint x, uint i) returns (uint) { - function(uint) internal returns (uint)[] arr = + function(uint) internal returns (uint)[] memory arr = new function(uint) internal returns (uint)[](10); arr[0] = a; arr[1] = b; -- cgit From f7a62c1e69e98cc612b7ed98497260b38234a162 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 14:15:01 +0100 Subject: Mention "payable" in the documentation. --- docs/types.rst | 9 +++++++-- libsolidity/grammar.txt | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 1673c30d..7f4570ed 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -291,7 +291,7 @@ be passed via and returned from external function calls. Function types are notated as follows: - function () {internal|external} [constant] [returns ()] + function () {internal|external} [constant] [payable] [returns ()] In contrast to the parameter types, the return types cannot be empty - if the function type should not return anything, the whole ``returns ()`` @@ -300,8 +300,13 @@ part has to be omitted. By default, function types are internal, so the ``internal`` keyword can be omitted. +There are two ways to access a function in the current contract: Either directly +by its name, ``f``, or using ``this.f``. The former will result in an internal +function, the latter in an external function. + If a function type variable is not initialized, calling it will result -in an exception. +in an exception. The same happens if you call a function after using ``delete`` +on it. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index cfa779ec..c8bc3aed 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -22,7 +22,7 @@ StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* )? '}' ModifierDefinition = 'modifier' Identifier ParameterList? Block FunctionDefinition = 'function' Identifier? ParameterList - ( FunctionCall | Identifier | 'constant' | 'external' | 'public' | 'internal' | 'private' )* + ( FunctionCall | Identifier | 'constant' |' payable' | 'external' | 'public' | 'internal' | 'private' )* ( 'returns' ParameterList )? Block EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';' @@ -39,7 +39,7 @@ VariableDeclaration = TypeName Identifier TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' -FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' )* +FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )* ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' -- cgit From 925d6741466a423b58cb519b6cc400863426607e Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 15:14:16 +0100 Subject: Disallow payable internal functions. --- libsolidity/analysis/ReferencesResolver.cpp | 3 +- libsolidity/ast/Types.cpp | 4 ++- test/libsolidity/SolidityNameAndTypeResolution.cpp | 36 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 41cad922..c49af013 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -95,7 +95,8 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); } - // Do we allow storage references for external functions? + if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) + fatalTypeError(_typeName.location(), "Only external function types can be payable."); _typeName.annotation().type = make_shared(_typeName); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 395faf55..e18735d9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1704,7 +1704,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): m_location(_isInternal ? Location::Internal : Location::External), m_isConstant(_function.isDeclaredConst()), - m_isPayable(_function.isPayable()), + m_isPayable(_isInternal ? false : _function.isPayable()), m_declaration(&_function) { TypePointers params; @@ -1810,6 +1810,8 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): m_isConstant(_typeName.isDeclaredConst()), m_isPayable(_typeName.isPayable()) { + if (_typeName.isPayable()) + solAssert(m_location == Location::External, "Internal payable function type used."); for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 62fb55f7..5916ed10 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4192,6 +4192,42 @@ BOOST_AUTO_TEST_CASE(public_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(payable_internal_function_type) +{ + char const* text = R"( + contract C { + function (uint) internal payable returns (uint) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external returns (uint) x; + function f() { + x.value(2)(); + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external payable returns (uint) x; + function f() { + x.value(2)(1); + } + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) { // It should not be possible to give internal functions -- cgit From 08763a206d4391f94546ef32a1d0f1495eeb99e4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 15:58:45 +0100 Subject: Test passing functions as arrays to other contracts. --- test/libsolidity/SolidityEndToEndTest.cpp | 43 ++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8f9edadf..5054e275 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8039,10 +8039,47 @@ BOOST_AUTO_TEST_CASE(copy_function_storage_array) BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(function_array_cross_calls) +{ + char const* sourceCode = R"( + contract D { + function f(function() external returns (function() external returns (uint))[] x) + returns (function() external returns (uint)[3] r) + { + r[0] = x[0](); + r[1] = x[1](); + r[2] = x[2](); + } + } + contract C { + function test() returns (uint, uint, uint) { + function() external returns (function() external returns (uint))[] memory x = + new function() external returns (function() external returns (uint))[](10); + for (uint i = 0; i < x.length; i ++) + x[i] = this.h; + x[0] = this.htwo; + var y = (new D()).f(x); + return (y[0](), y[1](), y[2]()); + } + function e() returns (uint) { return 5; } + function f() returns (uint) { return 6; } + function g() returns (uint) { return 7; } + uint counter; + function h() returns (function() external returns (uint)) { + return counter++ == 0 ? this.f : this.g; + } + function htwo() returns (function() external returns (uint)) { + return this.e; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(5), u256(6), u256(7))); +} + BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) { - // This has to apply NOT to the functions because encoding in storage - // is different than encoding in memory. char const* sourceCode = R"( contract C { function() internal returns (uint)[20] x; @@ -8056,7 +8093,7 @@ BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) if (mutex > 0) return 7; mutex = 1; - // If this test fails, it will jump to "0" and re-execute this function. + // If this test fails, it might re-execute this function. x[0](); return 2; } -- cgit From 746266b8fc9c94e1a21aa18ad646dda90643a1f7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 17:51:28 +0100 Subject: ABI: Use external function. --- libsolidity/interface/InterfaceHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 5705856c..0f7b6c35 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -66,7 +66,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe { Json::Value method; method["type"] = "constructor"; - auto externalFunction = FunctionType(*_contractDef.constructor()).interfaceFunctionType(); + auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["inputs"] = populateParameters( externalFunction->parameterNames(), @@ -76,7 +76,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe } if (_contractDef.fallbackFunction()) { - auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction()).interfaceFunctionType(); + auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "fallback"; -- cgit From ee3efa67a8d3eb4077786fd745c1925a916419f5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 17:51:48 +0100 Subject: Fix tests. --- test/libsolidity/SolidityABIJSON.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 0ad9e928..c01ff11b 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -684,7 +684,7 @@ BOOST_AUTO_TEST_CASE(payable_function) checkInterface(sourceCode, interface); } -BOOST_AUTO_TEST_CASE(payable_fallback_unction) +BOOST_AUTO_TEST_CASE(payable_fallback_function) { char const* sourceCode = R"( contract test { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 5054e275..b05316dd 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8006,7 +8006,7 @@ BOOST_AUTO_TEST_CASE(function_delete_stack) contract C { function a() returns (uint) { return 7; } function test() returns (uint) { - y = a; + var y = a; delete y; y(); } -- cgit From e543bd34c0b4884b5a27555f698f50af6a1c0b81 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 10 Nov 2016 18:16:21 +0100 Subject: Stored combined creation and runtime tags. Includes a change to Assembly to allow tags from sub-assemblies to be used. Sorry, this get a bit bigger than I thought. --- libevmasm/Assembly.cpp | 102 ++++++++++++++---------- libevmasm/Assembly.h | 6 +- libevmasm/AssemblyItem.cpp | 23 ++++++ libevmasm/AssemblyItem.h | 8 ++ libevmasm/BlockDeduplicator.cpp | 37 ++++++--- libevmasm/BlockDeduplicator.h | 16 ++++ libevmasm/ControlFlowGraph.h | 5 ++ liblll/CodeFragment.cpp | 4 +- libsolidity/codegen/Compiler.cpp | 11 ++- libsolidity/codegen/Compiler.h | 8 +- libsolidity/codegen/CompilerContext.cpp | 16 ++-- libsolidity/codegen/CompilerContext.h | 45 ++++++----- libsolidity/codegen/CompilerUtils.cpp | 13 +++ libsolidity/codegen/CompilerUtils.h | 4 + libsolidity/codegen/ContractCompiler.cpp | 43 +++++++--- libsolidity/codegen/ContractCompiler.h | 10 ++- libsolidity/codegen/ExpressionCompiler.cpp | 31 +++++-- libsolidity/interface/CompilerStack.cpp | 17 +++- test/libsolidity/SolidityEndToEndTest.cpp | 63 +++++++++++++++ test/libsolidity/SolidityExpressionCompiler.cpp | 2 +- 20 files changed, 347 insertions(+), 117 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e19b6b0d..23a8180b 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -308,9 +308,20 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - if (!_enable) - return *this; + if (_enable) + optimiseInternal(_isCreation, _runs); + return *this; +} +map Assembly::optimiseInternal(bool _isCreation, size_t _runs) +{ + for (size_t subId = 0; subId < m_subs.size(); ++subId) + { + map subTagReplacements = m_subs[subId].optimiseInternal(false, _runs); + BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); + } + + map tagReplacements; unsigned total = 0; for (unsigned count = 1; count > 0; total += count) { @@ -319,30 +330,39 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) // This only modifies PushTags, we have to run again to actually remove code. BlockDeduplicator dedup(m_items); if (dedup.deduplicate()) + { + tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); count++; + } { - // Control flow graph that resets knowledge at path joins. - ControlFlowGraph cfg(m_items, false); + // Control flow graph optimization has been here before but is disabled because it + // assumes we only jump to tags that are pushed. This is not the case anymore with + // function types that can be stored in storage. AssemblyItems optimisedItems; - for (BasicBlock const& block: cfg.optimisedBlocks()) + + auto iter = m_items.begin(); + while (iter != m_items.end()) { - // We used to start with the block's initial state but it caused - // too many inconsistencies. + auto end = iter; + while (end != m_items.end()) + if (SemanticInformation::altersControlFlow(*end++)) + break; + KnownState emptyState; CommonSubexpressionEliminator eliminator(emptyState); - auto iter = m_items.begin() + block.begin; - auto const end = m_items.begin() + block.end; - while (iter < end) + auto blockIter = iter; + auto const blockEnd = end; + while (blockIter < blockEnd) { - auto orig = iter; - iter = eliminator.feedItems(iter, end); + auto orig = blockIter; + blockIter = eliminator.feedItems(blockIter, blockEnd); bool shouldReplace = false; AssemblyItems optimisedChunk; try { optimisedChunk = eliminator.getOptimizedItems(); - shouldReplace = (optimisedChunk.size() < size_t(iter - orig)); + shouldReplace = (optimisedChunk.size() < size_t(blockIter - orig)); } catch (StackTooDeepException const&) { @@ -361,15 +381,11 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) optimisedItems += optimisedChunk; } else - copy(orig, iter, back_inserter(optimisedItems)); + copy(orig, blockIter, back_inserter(optimisedItems)); } + iter = end; } - if (optimisedItems.size() < m_items.size()) - { - m_items = move(optimisedItems); - count++; - } } } @@ -380,10 +396,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) m_items ); - for (auto& sub: m_subs) - sub.optimise(true, false, _runs); - - return *this; + return tagReplacements; } LinkerObject const& Assembly::assemble() const @@ -394,8 +407,8 @@ LinkerObject const& Assembly::assemble() const LinkerObject& ret = m_assembledObject; unsigned totalBytes = bytesRequired(); - vector tagPos(m_usedTags); - map tagRef; + m_tagPositionsInBytecode = vector(m_usedTags, -1); + map> tagRef; multimap dataRef; multimap subRef; vector sizeRef; ///< Pointers to code locations where the size of the program is inserted @@ -413,8 +426,8 @@ LinkerObject const& Assembly::assemble() const for (AssemblyItem const& i: m_items) { // store position of the invalid jump destination - if (i.type() != Tag && tagPos[0] == 0) - tagPos[0] = ret.bytecode.size(); + if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1)) + m_tagPositionsInBytecode[0] = ret.bytecode.size(); switch (i.type()) { @@ -446,7 +459,7 @@ LinkerObject const& Assembly::assemble() const case PushTag: { ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = (unsigned)i.data(); + tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); break; } @@ -484,26 +497,16 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + 20); break; case Tag: - tagPos[(unsigned)i.data()] = ret.bytecode.size(); - assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); assertThrow(i.data() != 0, AssemblyException, ""); + assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag."); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); + m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size(); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } } - for (auto const& i: tagRef) - { - bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); - auto tag = i.second; - if (tag >= tagPos.size()) - tag = 0; - if (tag == 0) - assertThrow(tagPos[tag] != 0, AssemblyException, ""); - - toBigEndian(tagPos[tag], r); - } if (!dataRef.empty() && !subRef.empty()) ret.bytecode.push_back(0); @@ -519,6 +522,23 @@ LinkerObject const& Assembly::assemble() const } ret.append(m_subs[i].assemble()); } + for (auto const& i: tagRef) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = i.second; + assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id"); + std::vector const& tagPositions = + subId == size_t(-1) ? + m_tagPositionsInBytecode : + m_subs[subId].m_tagPositionsInBytecode; + assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); + size_t pos = tagPositions[tagId]; + assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); + assertThrow(dev::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); + bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); + toBigEndian(pos, r); + } for (auto const& dataItem: m_data) { auto references = dataRef.equal_range(dataItem.first); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index dae1e1da..0ae81e1d 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -53,7 +53,6 @@ public: AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); - AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); void append(Assembly const& _a, int _deposit); AssemblyItem const& append(AssemblyItem const& _i); @@ -110,6 +109,10 @@ public: ) const; protected: + /// Does the same operations as @a optimise, but should only be applied to a sub and + /// returns the replaced tags. + std::map optimiseInternal(bool _isCreation, size_t _runs); + std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } unsigned bytesRequired() const; @@ -129,6 +132,7 @@ protected: std::map m_libraries; ///< Identifiers of libraries to be linked. mutable LinkerObject m_assembledObject; + mutable std::vector m_tagPositionsInBytecode; int m_deposit = 0; int m_baseDeposit = 0; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 599ded85..2eae20af 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -26,6 +26,29 @@ using namespace std; using namespace dev; using namespace dev::eth; +AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const +{ + assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set."); + + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + AssemblyItem r = *this; + r.m_type = PushTag; + r.setPushTagSubIdAndTag(_subId, size_t(m_data)); + return r; +} + +pair AssemblyItem::splitForeignPushTag() const +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data)); +} + +void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + setData(_tag + ((u256(_subId) + 1) << 64)); +} + unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const { switch (m_type) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 1c3d9789..1a2fb1e6 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -69,6 +69,14 @@ public: AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } + /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. + /// @param _subId the identifier of the subassembly the tag is taken from. + AssemblyItem toSubAssemblyTag(size_t _subId) const; + /// @returns splits the data of the push tag into sub assembly id and actual tag id. + /// The sub assembly id of non-foreign push tags is -1. + std::pair splitForeignPushTag() const; + /// Sets sub-assembly part and tag for a push tag. + void setPushTagSubIdAndTag(size_t _subId, size_t _tag); AssemblyItemType type() const { return m_type; } u256 const& data() const { return m_data; } diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index 3bb7a797..18b595cd 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -77,7 +77,6 @@ bool BlockDeduplicator::deduplicate() { //@todo this should probably be optimized. set> blocksSeen(comparator); - map tagReplacement; for (size_t i = 0; i < m_items.size(); ++i) { if (m_items.at(i).type() != Tag) @@ -86,22 +85,40 @@ bool BlockDeduplicator::deduplicate() if (it == blocksSeen.end()) blocksSeen.insert(i); else - tagReplacement[m_items.at(i).data()] = m_items.at(*it).data(); + m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data(); } - bool changed = false; - for (AssemblyItem& item: m_items) - if (item.type() == PushTag && tagReplacement.count(item.data())) - { - changed = true; - item.setData(tagReplacement.at(item.data())); - } - if (!changed) + if (!applyTagReplacement(m_items, m_replacedTags)) break; } return iterations > 0; } +bool BlockDeduplicator::applyTagReplacement( + AssemblyItems& _items, + map const& _replacements, + size_t _subId +) +{ + bool changed = false; + for (AssemblyItem& item: _items) + if (item.type() == PushTag) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = item.splitForeignPushTag(); + if (subId != _subId) + continue; + auto it = _replacements.find(tagId); + if (it != _replacements.end()) + { + changed = true; + item.setPushTagSubIdAndTag(subId, size_t(it->second)); + } + } + return changed; +} + BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() { if (it == end) diff --git a/libevmasm/BlockDeduplicator.h b/libevmasm/BlockDeduplicator.h index c48835fd..5ecab7f3 100644 --- a/libevmasm/BlockDeduplicator.h +++ b/libevmasm/BlockDeduplicator.h @@ -23,9 +23,12 @@ #pragma once +#include + #include #include #include +#include namespace dev { @@ -45,6 +48,18 @@ public: BlockDeduplicator(AssemblyItems& _items): m_items(_items) {} /// @returns true if something was changed bool deduplicate(); + /// @returns the tags that were replaced. + std::map const& replacedTags() const { return m_replacedTags; } + + /// Replaces all PushTag operations insied @a _items that match a key in + /// @a _replacements by the respective value. If @a _subID is not -1, only + /// apply the replacement for foreign tags from this sub id. + /// @returns true iff a replacement was performed. + static bool applyTagReplacement( + AssemblyItems& _items, + std::map const& _replacements, + size_t _subID = size_t(-1) + ); private: /// Iterator that skips tags and skips to the end if (all branches of) the control @@ -70,6 +85,7 @@ private: AssemblyItem const* replaceWith; }; + std::map m_replacedTags; AssemblyItems& m_items; }; diff --git a/libevmasm/ControlFlowGraph.h b/libevmasm/ControlFlowGraph.h index 03a1f717..78998262 100644 --- a/libevmasm/ControlFlowGraph.h +++ b/libevmasm/ControlFlowGraph.h @@ -89,6 +89,11 @@ struct BasicBlock using BasicBlocks = std::vector; +/** + * Control flow graph optimizer. + * ASSUMES THAT WE ONLY JUMP TO TAGS THAT WERE PREVIOUSLY PUSHED. THIS IS NOT TRUE ANYMORE + * NOW THAT FUNCTION TAGS CAN BE STORED IN STORAGE. + */ class ControlFlowGraph { public: diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 39b6376c..73a09aad 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -474,7 +474,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireSize(2); requireDeposit(0, 1); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[0].m_asm); if (us == "WHILE") m_asm.append(Instruction::ISZERO); @@ -489,7 +489,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireDeposit(1, 1); m_asm.append(code[0].m_asm, 0); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[1].m_asm); m_asm.append(Instruction::ISZERO); auto end = m_asm.appendJumpI(); diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index bb8211ad..eefa50c5 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,11 +33,13 @@ void Compiler::compileContract( std::map const& _contracts ) { - ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); - m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); + // This might modify m_runtimeContext because it can access runtime functions at + // creation time. + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); if (m_optimize) m_context.optimise(m_optimizeRuns); @@ -54,7 +56,8 @@ void Compiler::compileClone( map const& _contracts ) { - ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); + ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); if (m_optimize) diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 56849ea0..4a87de0e 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -36,8 +36,8 @@ public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), m_optimizeRuns(_runs), - m_context(CompilationMode::Creation, &m_runtimeContext), - m_runtimeContext(CompilationMode::Runtime) + m_runtimeContext(), + m_context(&m_runtimeContext) { } void compileContract( @@ -71,9 +71,9 @@ public: private: bool const m_optimize; unsigned const m_optimizeRuns; - CompilerContext m_context; - size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_runtimeContext; + size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. + CompilerContext m_context; }; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3ac5bd3c..f1d306ce 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -92,22 +92,22 @@ eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& return m_functionCompilationQueue.entryLabelIfExists(_declaration); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function) +FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function) { // Libraries do not allow inheritance and their functions can be inlined, so we should not // search the inheritance hierarchy (which will be the wrong one in case the function // is inlined). if (auto scope = dynamic_cast(_function.scope())) if (scope->isLibrary()) - return functionEntryLabel(_function); + return _function; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); + return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin()); } -eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) +FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base) { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, superContract(_base)); + return resolveVirtualFunction(_function, superContract(_base)); } FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const @@ -227,7 +227,7 @@ void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) sub.injectStart(fromBigEndian(binaryVersion())); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( +FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector::const_iterator _searchStart ) @@ -242,9 +242,9 @@ eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( !function->isConstructor() && FunctionType(*function).hasEqualArgumentTypes(functionType) ) - return functionEntryLabel(*function); + return *function; solAssert(false, "Super function " + name + " not found."); - return m_asm.newTag(); // not reached + return _function; // not reached } vector::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 8b95c9f5..c4724ee0 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -37,10 +37,6 @@ namespace dev { namespace solidity { -/// Depending on the compilation is on the runtime code or the creation code, -/// the interpretation of internal function values differ. -enum class CompilationMode { Runtime, Creation }; - /** * Context to be shared by all units that compile the same contract. * It stores the generated bytecode and the position of identifiers in memory and on the stack. @@ -48,15 +44,13 @@ enum class CompilationMode { Runtime, Creation }; class CompilerContext { public: - CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) : - m_mode(_mode), m_runtimeContext(_runtimeContext) + CompilerContext(CompilerContext* _runtimeContext = nullptr) : + m_runtimeContext(_runtimeContext) { - solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided"); - solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided"); + if (m_runtimeContext) + m_runtimeSub = registerSubroutine(m_runtimeContext->assembly()); } - bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } - void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); @@ -80,10 +74,10 @@ public: eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. - eth::AssemblyItem virtualFunctionEntryLabel(FunctionDefinition const& _function); - /// @returns the entry label of a function that overrides the given declaration from the most derived class just + FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); + /// @returns the function that overrides the given declaration from the most derived class just /// above _base in the current inheritance hierarchy. - eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); + FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; /// @returns the next function in the queue of functions that are still to be compiled @@ -123,11 +117,15 @@ public: eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data eth::AssemblyItem newTag() { return m_asm.newTag(); } + /// Adds a subroutine to the code (in the data section) + /// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list. + size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); } /// 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); } + /// on the stack. @returns the pushsub assembly item. + eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; } + void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { return m_asm.appendProgramSize(); } + void appendProgramSize() { 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); } /// Appends the address (virtual, will be filled in by linker) of a library. @@ -159,6 +157,11 @@ public: void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. + CompilerContext* runtimeContext() { return m_runtimeContext; } + /// @returns the identifier of the runtime subroutine. + size_t runtimeSub() const { return m_runtimeSub; } + eth::Assembly const& assembly() const { return m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. @@ -185,9 +188,9 @@ public: }; private: - /// @returns the entry label of the given function - searches the inheritance hierarchy - /// startig from the given point towards the base. - eth::AssemblyItem virtualFunctionEntryLabel( + /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns + /// the first function definition that is overwritten by _function. + FunctionDefinition const& resolveVirtualFunction( FunctionDefinition const& _function, std::vector::const_iterator _searchStart ); @@ -241,10 +244,10 @@ private: std::vector m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; - /// The current mode of the compilation - CompilationMode m_mode; /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. CompilerContext *m_runtimeContext; + /// The index of the runtime subroutine. + size_t m_runtimeSub = -1; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dedb53e7..1e21c020 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -340,6 +340,19 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned) m_context << Instruction::OR; } +void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) +{ + m_context << m_context.functionEntryLabel(_function).pushTag(); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + m_context << + (u256(1) << 32) << + Instruction::MUL << + rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << + Instruction::OR; +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 690452f9..2ebec81a 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -120,6 +120,10 @@ public: void splitExternalFunctionType(bool _rightAligned); /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) void combineExternalFunctionType(bool _rightAligned); + /// Appends code that combines the construction-time (if available) and runtime function + /// entry label of the given function into a single stack slot. + /// Note: This might cause the compilation queue of the runtime context to be extended. + void pushCombinedFunctionEntryLabel(Declaration const& _function); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9cd893e8..79987af6 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,14 +60,13 @@ void ContractCompiler::compileContract( } size_t ContractCompiler::compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract, _runtimeContext); + return packIntoContractCreator(_contract); } size_t ContractCompiler::compileClone( @@ -141,21 +140,31 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendBaseConstructor(*c); } -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { + solAssert(!!m_runtimeCompiler, ""); + appendInitAndConstructorCode(_contract); - eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + // We jump to the deploy routine because we first have to append all missing functions, + // which can cause further functions to be added to the runtime context. + eth::AssemblyItem deployRoutine = m_context.appendJumpToNew(); + + // We have to include copies of functions in the construction time and runtime context + // because of absolute jumps. + appendMissingFunctions(); + m_runtimeCompiler->appendMissingFunctions(); + + m_context << deployRoutine; + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.appendSubroutineSize(m_context.runtimeSub()); // stack contains sub size - m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; + m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY; m_context << u256(0) << Instruction::RETURN; - // note that we have to include the functions again because of absolute jump labels - appendMissingFunctions(); - - solAssert(runtimeSub.data() < numeric_limits::max(), ""); - return size_t(runtimeSub.data()); + return m_context.runtimeSub(); } void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -516,7 +525,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast(decl)) - _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + { + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + { + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); + } + } else if (auto variable = dynamic_cast(decl)) { solAssert(!variable->isConstant(), ""); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index fecf6f5a..9e24523c 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,12 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): m_optimise(_optimise), + m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_mode, _runtimeContext); + m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -52,7 +53,6 @@ public: /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ); @@ -74,7 +74,7 @@ private: /// 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. /// @returns the identifier of the runtime sub assembly - size_t packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + size_t packIntoContractCreator(ContractDefinition const& _contract); /// Appends state variable initialisation and constructor code. void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); @@ -117,6 +117,8 @@ private: static eth::Assembly cloneRuntime(); bool const m_optimise; + /// Pointer to the runtime compiler in case this is a creation compiler. + ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; std::vector m_breakTags; ///< tag to jump to for a "break" statement std::vector m_continueTags; ///< tag to jump to for a "continue" statement diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index e3f05c21..83c3a2c4 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -488,6 +488,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) parameterSize += function.selfType()->sizeOnStack(); } + if (m_context.runtimeContext()) + // We have a runtime context, so we need the creation part. + m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + else + // Extract the runtime part. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; @@ -845,9 +852,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.functionEntryLabel( - dynamic_cast(funType->declaration()) - ).pushTag(); + FunctionDefinition const& funDef = dynamic_cast(funType->declaration()); + utils().pushCombinedFunctionEntryLabel(funDef); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); } else @@ -883,7 +889,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // us to link against it although we actually do not need it. auto const* function = dynamic_cast(_memberAccess.annotation().referencedDeclaration); solAssert(!!function, "Function not found in member access"); - m_context << m_context.functionEntryLabel(*function).pushTag(); + utils().pushCombinedFunctionEntryLabel(*function); } } else if (dynamic_cast(_memberAccess.annotation().type.get())) @@ -915,10 +921,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (type.isSuper()) { solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); - m_context << m_context.superFunctionEntryLabel( + utils().pushCombinedFunctionEntryLabel(m_context.superFunction( dynamic_cast(*_memberAccess.annotation().referencedDeclaration), type.contractDefinition() - ).pushTag(); + )); } else { @@ -1203,7 +1209,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) - m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast(declaration)) appendVariable(*variable, static_cast(_identifier)); else if (auto contract = dynamic_cast(declaration)) @@ -1266,6 +1272,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (_operator == Token::Equal || _operator == Token::NotEqual) { + if (FunctionType const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + // We have to remove the upper bits (construction time value) because they might + // be "unknown" in one of the operands and not in the other. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + } + } m_context << Instruction::EQ; if (_operator == Token::NotEqual) m_context << Instruction::ISZERO; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 519027bc..c86de43f 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -590,9 +591,19 @@ void CompilerStack::compileContract( compiledContract.runtimeObject = compiler->runtimeObject(); _compiledContracts[compiledContract.contract] = &compiler->assembly(); - Compiler cloneCompiler(_optimize, _runs); - cloneCompiler.compileClone(_contract, _compiledContracts); - compiledContract.cloneObject = cloneCompiler.assembledObject(); + try + { + Compiler cloneCompiler(_optimize, _runs); + cloneCompiler.compileClone(_contract, _compiledContracts); + compiledContract.cloneObject = cloneCompiler.assembledObject(); + } + catch (eth::AssemblyException const& _e) + { + // In some cases (if the constructor requests a runtime function), it is not + // possible to compile the clone. + + // TODO: Report error / warning + } } std::string CompilerStack::defaultContractName() const diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index b05316dd..8f49b9c4 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7777,6 +7777,48 @@ BOOST_AUTO_TEST_CASE(store_function_in_constructor) BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4))); } +// TODO: store bound internal library functions + +BOOST_AUTO_TEST_CASE(store_internal_unused_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + function () internal returns (uint) x; + function C () { + x = unused; + } + function unused() internal returns (uint) { + return 7; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(store_internal_unused_library_function_in_constructor) +{ + char const* sourceCode = R"( + library L { function x() internal returns (uint) { return 7; } } + contract C { + function () internal returns (uint) x; + function C () { + x = L.x; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) { char const* sourceCode = R"( @@ -7799,6 +7841,27 @@ BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4))); } +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime_equality_check) +{ + char const* sourceCode = R"( + contract C { + function (uint) internal returns (uint) x; + function C() { + x = double; + } + function test() returns (bool) { + return x == double; + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + BOOST_AUTO_TEST_CASE(function_type_library_internal) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 09d556a5..e9a05745 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -136,7 +136,7 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context { CompilationMode::Runtime /* probably simpler */ }; + CompilerContext context; context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack -- cgit From e51f852504556f952ae1350c070409e3c4981cc0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 11:41:50 +0100 Subject: Converted sub assembly to smart pointer. --- libevmasm/Assembly.cpp | 54 +++++++++++++++++--------- libevmasm/Assembly.h | 33 ++++++++-------- libevmasm/AssemblyItem.cpp | 12 ++++-- liblll/CodeFragment.cpp | 3 +- libsolidity/codegen/CompilerContext.cpp | 14 +++---- libsolidity/codegen/CompilerContext.h | 62 +++++++++++++++--------------- libsolidity/codegen/ContractCompiler.cpp | 15 ++++---- libsolidity/codegen/ContractCompiler.h | 2 +- libsolidity/codegen/ExpressionCompiler.cpp | 5 ++- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- test/libsolidity/SolidityOptimizer.cpp | 18 +++++++++ 11 files changed, 133 insertions(+), 87 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 23a8180b..0ee3f421 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -75,17 +75,17 @@ string Assembly::out() const return ret.str(); } -unsigned Assembly::bytesRequired() const +unsigned Assembly::bytesRequired(unsigned subTagSize) const { - for (unsigned br = 1;; ++br) + for (unsigned tagSize = subTagSize;; ++tagSize) { unsigned ret = 1; for (auto const& i: m_data) ret += i.second.size(); for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(br); - if (dev::bytesRequired(ret) <= br) + ret += i.bytesRequired(tagSize); + if (dev::bytesRequired(ret) <= tagSize) return ret; } } @@ -132,13 +132,19 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con if (i.data() == 0) _out << " PUSH [ErrorTag]"; else - _out << " PUSH [tag" << dec << i.data() << "]"; + { + size_t subId = i.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]"; + else + _out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]"; + } break; case PushSub: - _out << " PUSH [$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH [$" << size_t(i.data()) << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH #[$" << size_t(i.data()) << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; @@ -167,7 +173,7 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con for (size_t i = 0; i < m_subs.size(); ++i) { _out << _prefix << " " << hex << i << ": " << endl; - m_subs[i].stream(_out, _prefix + " ", _sourceCodes); + m_subs[i]->stream(_out, _prefix + " ", _sourceCodes); } } return _out; @@ -266,7 +272,7 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes { std::stringstream hexStr; hexStr << hex << i; - data[hexStr.str()] = m_subs[i].stream(_out, "", _sourceCodes, true); + data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true); } root[".data"] = data; _out << root; @@ -317,7 +323,7 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) { for (size_t subId = 0; subId < m_subs.size(); ++subId) { - map subTagReplacements = m_subs[subId].optimiseInternal(false, _runs); + map subTagReplacements = m_subs[subId]->optimiseInternal(false, _runs); BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } @@ -385,7 +391,11 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) } iter = end; } - + if (optimisedItems.size() < m_items.size()) + { + m_items = move(optimisedItems); + count++; + } } } @@ -404,20 +414,28 @@ LinkerObject const& Assembly::assemble() const if (!m_assembledObject.bytecode.empty()) return m_assembledObject; + size_t subTagSize = 1; + for (auto const& sub: m_subs) + { + sub->assemble(); + if (!sub->m_tagPositionsInBytecode.empty()) + subTagSize = max(subTagSize, *max_element(sub->m_tagPositionsInBytecode.begin(), sub->m_tagPositionsInBytecode.end())); + } + LinkerObject& ret = m_assembledObject; - unsigned totalBytes = bytesRequired(); + size_t bytesRequiredForCode = bytesRequired(subTagSize); m_tagPositionsInBytecode = vector(m_usedTags, -1); map> tagRef; multimap dataRef; multimap subRef; vector sizeRef; ///< Pointers to code locations where the size of the program is inserted - unsigned bytesPerTag = dev::bytesRequired(totalBytes); + unsigned bytesPerTag = dev::bytesRequired(bytesRequiredForCode); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; - unsigned bytesRequiredIncludingData = bytesRequired(); + unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1; for (auto const& sub: m_subs) - bytesRequiredIncludingData += sub.assemble().bytecode.size(); + bytesRequiredIncludingData += sub->assemble().bytecode.size(); unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); byte dataRefPush = (byte)Instruction::PUSH1 - 1 + bytesPerDataRef; @@ -475,7 +493,7 @@ LinkerObject const& Assembly::assemble() const break; case PushSubSize: { - auto s = m_subs.at(size_t(i.data())).assemble().bytecode.size(); + auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); byte b = max(1, dev::bytesRequired(s)); ret.bytecode.push_back((byte)Instruction::PUSH1 - 1 + b); @@ -520,7 +538,7 @@ LinkerObject const& Assembly::assemble() const bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); toBigEndian(ret.bytecode.size(), r); } - ret.append(m_subs[i].assemble()); + ret.append(m_subs[i]->assemble()); } for (auto const& i: tagRef) { @@ -531,7 +549,7 @@ LinkerObject const& Assembly::assemble() const std::vector const& tagPositions = subId == size_t(-1) ? m_tagPositionsInBytecode : - m_subs[subId].m_tagPositionsInBytecode; + m_subs[subId]->m_tagPositionsInBytecode; assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); size_t pos = tagPositions[tagId]; assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 0ae81e1d..3ce82cce 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -14,30 +14,32 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Assembly.h - * @author Gav Wood - * @date 2014 - */ #pragma once -#include -#include -#include -#include -#include #include #include #include #include -#include "Exceptions.h" +#include + +#include +#include +#include + #include +#include +#include +#include + namespace dev { namespace eth { +using AssemblyPointer = std::shared_ptr; + class Assembly { public: @@ -46,9 +48,9 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } - AssemblyItem newSub(Assembly const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } - Assembly const& sub(size_t _sub) const { return m_subs.at(_sub); } - Assembly& sub(size_t _sub) { return m_subs.at(_sub); } + AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } + Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } + Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } AssemblyItem newPushString(std::string const& _data) { h256 h(dev::keccak256(_data)); m_strings[h] = _data; return AssemblyItem(PushString, h); } AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); @@ -58,7 +60,6 @@ public: AssemblyItem const& append(AssemblyItem const& _i); 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)); } @@ -115,7 +116,7 @@ protected: std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } - unsigned bytesRequired() const; + unsigned bytesRequired(unsigned subTagSize) const; private: Json::Value streamAsmJson(std::ostream& _out, StringMap const& _sourceCodes) const; @@ -127,7 +128,7 @@ protected: unsigned m_usedTags = 1; AssemblyItems m_items; std::map m_data; - std::vector m_subs; + std::vector> m_subs; std::map m_strings; std::map m_libraries; ///< Identifiers of libraries to be linked. diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 2eae20af..bc8c87aa 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -127,8 +127,14 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushString" << hex << (unsigned)_item.data(); break; case PushTag: - _out << " PushTag " << _item.data(); + { + size_t subId = _item.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PushTag " << _item.splitForeignPushTag().second; + else + _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; break; + } case Tag: _out << " Tag " << _item.data(); break; @@ -136,10 +142,10 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushData " << hex << (unsigned)_item.data(); break; case PushSub: - _out << " PushSub " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSub " << hex << size_t(_item.data()); break; case PushSubSize: - _out << " PushSubSize " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSubSize " << hex << size_t(_item.data()); break; case PushProgramSize: _out << " PushProgramSize"; diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 73a09aad..d757dcf1 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -520,7 +520,8 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireMaxSize(3); requireDeposit(1, 1); - auto subPush = m_asm.appendSubSize(code[0].assembly(ns)); + auto subPush = m_asm.newSub(make_shared(code[0].assembly(ns))); + m_asm.append(m_asm.newPushSubSize(subPush.data())); m_asm.append(Instruction::DUP1); if (code.size() == 3) { diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index f1d306ce..c76db2a6 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -60,8 +60,8 @@ void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { - solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); - m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; + solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } void CompilerContext::removeVariable(VariableDeclaration const& _declaration) @@ -145,12 +145,12 @@ unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declarat unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return m_asm.deposit() - _baseOffset - 1; + return m_asm->deposit() - _baseOffset - 1; } unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const { - return m_asm.deposit() - _offset - 1; + return m_asm->deposit() - _offset - 1; } pair CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const @@ -217,12 +217,12 @@ void CompilerContext::appendInlineAssembly( return true; }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, m_asm, identifierAccess), ""); + solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), ""); } void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) { - eth::Assembly& sub = m_asm.sub(_subIndex); + eth::Assembly& sub = m_asm->sub(_subIndex); sub.injectStart(Instruction::POP); sub.injectStart(fromBigEndian(binaryVersion())); } @@ -257,7 +257,7 @@ vector::const_iterator CompilerContext::superContract void CompilerContext::updateSourceLocation() { - m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); + m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index c4724ee0..ee3fb068 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,11 +44,12 @@ namespace solidity { class CompilerContext { public: - CompilerContext(CompilerContext* _runtimeContext = nullptr) : + CompilerContext(CompilerContext* _runtimeContext = nullptr): + m_asm(std::make_shared()), m_runtimeContext(_runtimeContext) { if (m_runtimeContext) - m_runtimeSub = registerSubroutine(m_runtimeContext->assembly()); + m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } void addMagicGlobal(MagicVariableDeclaration const& _declaration); @@ -59,9 +60,9 @@ public: void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; - void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } - void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } - unsigned stackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } + void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } + void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } + unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; @@ -102,34 +103,33 @@ public: std::pair storageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag - eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } /// Appends a JUMPI instruction to @a _tag - CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJumpI(_tag); return *this; } /// Appends a JUMP to a new tag and @returns the tag - eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } + eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Returns an "ErrorTag" - eth::AssemblyItem errorTag() { return m_asm.errorTag(); } + eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. - eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data - eth::AssemblyItem newTag() { return m_asm.newTag(); } - /// Adds a subroutine to the code (in the data section) - /// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list. - size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); } + eth::AssemblyItem newTag() { return m_asm->newTag(); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. - eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; } - void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); } + eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; } + void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); } + /// Pushes the offset of the subroutine. + void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { m_asm.appendProgramSize(); } + void appendProgramSize() { 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); } + eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. - void appendLibraryAddress(std::string const& _identifier) { m_asm.appendLibraryAddress(_identifier); } + void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes @@ -138,10 +138,10 @@ public: void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm.append(_instruction); return *this; } - CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } - CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } + CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } /// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// prior to parsing the inline assembly. @@ -155,27 +155,27 @@ public: /// Prepends "PUSH POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + void optimise(unsigned _runs = 200) { m_asm->optimise(true, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } - eth::Assembly const& assembly() const { return m_asm; } + eth::Assembly const& assembly() const { return *m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return m_asm; } + eth::Assembly& nonConstAssembly() { return *m_asm; } /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFormat shows whether the out should be in Json format Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const { - return m_asm.stream(_stream, "", _sourceCodes, _inJsonFormat); + return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); } - eth::LinkerObject const& assembledObject() { return m_asm.assemble(); } - eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm.sub(_subIndex).assemble(); } + eth::LinkerObject const& assembledObject() { return m_asm->assemble(); } + eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); } /** * Helper class to pop the visited nodes stack when a scope closes @@ -231,7 +231,7 @@ private: mutable std::queue m_functionsToCompile; } m_functionCompilationQueue; - eth::Assembly m_asm; + eth::AssemblyPointer 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. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 79987af6..2e3f5d7f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -79,7 +79,7 @@ size_t ContractCompiler::compileClone( appendInitAndConstructorCode(_contract); //@todo determine largest return size of all runtime functions - eth::AssemblyItem runtimeSub = m_context.addSubroutine(cloneRuntime()); + auto runtimeSub = m_context.addSubroutine(cloneRuntime()); // stack contains sub size m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; @@ -87,7 +87,6 @@ size_t ContractCompiler::compileClone( appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits::max(), ""); return size_t(runtimeSub.data()); } @@ -158,10 +157,10 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont m_context << deployRoutine; solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); - m_context.appendSubroutineSize(m_context.runtimeSub()); - - // stack contains sub size - m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY; + m_context.pushSubroutineSize(m_context.runtimeSub()); + m_context << Instruction::DUP1; + m_context.pushSubroutineOffset(m_context.runtimeSub()); + m_context << u256(0) << Instruction::CODECOPY; m_context << u256(0) << Instruction::RETURN; return m_context.runtimeSub(); @@ -893,7 +892,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::Assembly ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -911,5 +910,5 @@ eth::Assembly ContractCompiler::cloneRuntime() a.appendJumpI(a.errorTag()); //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; - return a; + return make_shared(a); } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 9e24523c..2244a3be 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -114,7 +114,7 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::Assembly cloneRuntime(); + static eth::AssemblyPointer cloneRuntime(); bool const m_optimise; /// Pointer to the runtime compiler in case this is a creation compiler. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 83c3a2c4..7a328528 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -528,8 +528,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // copy the contract's code into memory eth::Assembly const& assembly = m_context.compiledContract(contract); utils().fetchFreeMemoryPointer(); + // TODO we create a copy here, which is actually what we want. + // This should be revisited at the point where we fix + // https://github.com/ethereum/solidity/issues/1092 // pushes size - eth::AssemblyItem subroutine = m_context.addSubroutine(assembly); + auto subroutine = m_context.addSubroutine(make_shared(assembly)); m_context << Instruction::DUP1 << subroutine; m_context << Instruction::DUP4 << Instruction::CODECOPY; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8f49b9c4..c01d11d2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7757,7 +7757,7 @@ BOOST_AUTO_TEST_CASE(store_function_in_constructor) { char const* sourceCode = R"( contract C { - uint result_in_constructor; + uint public result_in_constructor; function (uint) internal returns (uint) x; function C () { x = double; diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 562b7859..976976b2 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -1238,6 +1238,24 @@ BOOST_AUTO_TEST_CASE(inconsistency) compareVersions("trigger()"); } +BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies) +{ + // This tests that a runtime-function that is stored in storage in the constructor + // is not removed as part of dead code elimination. + char const* sourceCode = R"( + contract DCE { + function () internal returns (uint) stored; + function DCE() { + stored = f; + } + function f() internal returns (uint) { return 7; } + function test() returns (uint) { return stored(); } + } + )"; + compileBothVersions(sourceCode); + compareVersions("test()"); +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit From f3d0433ec3aca99f6d9212da944c6fc51cb9292a Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 11 Nov 2016 12:02:32 +0100 Subject: test: add a test about external function type taking/returning internal functions --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 5916ed10..865eb7ce 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4215,6 +4215,26 @@ BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(external_function_type_returning_internal) +{ + char const* text = R"( + contract C { + function() external returns (function () internal) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(external_function_type_taking_internal) +{ + char const* text = R"( + contract C { + function(function () internal) external x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) { char const* text = R"( -- cgit From 22b4d1b29a17c6a6360d6d6e42860bd621a7bfd3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 12:07:30 +0100 Subject: Check that no internals are used in any external function type. --- libsolidity/analysis/ReferencesResolver.cpp | 9 ++++++++- libsolidity/analysis/TypeChecker.cpp | 8 ++++++++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/Types.cpp | 24 ++++++++++++++++++++++++ libsolidity/ast/Types.h | 4 ++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index c49af013..a0768a34 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -96,7 +96,14 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) } if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) - fatalTypeError(_typeName.location(), "Only external function types can be payable."); + fatalTypeError(_typeName.location(), "Only external function types can be payable."); + if (_typeName.visibility() == VariableDeclaration::Visibility::External) + for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + if (!t->annotation().type->canBeUsedExternally(false)) + fatalTypeError(t->location(), "Internal type cannot be used for external function type."); + } _typeName.annotation().type = make_shared(_typeName); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index f934b2c8..8b6d45e2 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -574,6 +574,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return false; } +void TypeChecker::endVisit(FunctionTypeName const& _funType) +{ + FunctionType const& fun = dynamic_cast(*_funType.annotation().type); + if (fun.location() == FunctionType::Location::External) + if (!fun.canBeUsedExternally(false)) + typeError(_funType.location(), "External function type uses internal types."); +} + bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { // Inline assembly does not have its own type-checking phase, so we just run the diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index be1e02be..5874bb50 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -87,6 +87,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector const& _bases); virtual bool visit(EventDefinition const& _eventDef) override; + virtual void endVisit(FunctionTypeName const& _funType) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e18735d9..c1ae183e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1265,6 +1265,7 @@ TypePointer ArrayType::decodingType() const TypePointer ArrayType::interfaceType(bool _inLibrary) const { + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) return shared_from_this(); @@ -1282,6 +1283,21 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const return make_shared(DataLocation::Memory, baseExt, m_length); } +bool ArrayType::canBeUsedExternally(bool _inLibrary) const +{ + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) + if (_inLibrary && location() == DataLocation::Storage) + return true; + else if (m_arrayKind != ArrayKind::Ordinary) + return true; + else if (!m_baseType->canBeUsedExternally(_inLibrary)) + return false; + else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) + return false; + else + return true; +} + u256 ArrayType::memorySize() const { solAssert(!isDynamicallySized(), ""); @@ -1815,11 +1831,19 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); + solAssert( + m_location != Location::External || t->annotation().type->canBeUsedExternally(false), + "Internal type used as parameter for external function." + ); m_parameterTypes.push_back(t->annotation().type); } for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); + solAssert( + m_location != Location::External || t->annotation().type->canBeUsedExternally(false), + "Internal type used as return parameter for external function." + ); m_returnParameterTypes.push_back(t->annotation().type); } } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 9831bc42..84e56663 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -254,6 +254,9 @@ public: /// @param _inLibrary if set, returns types as used in a library, e.g. struct and contract types /// are returned without modification. virtual TypePointer interfaceType(bool /*_inLibrary*/) const { return TypePointer(); } + /// @returns true iff this type can be passed on via calls (to libraries if _inLibrary is true), + /// should be have identical to !!interfaceType(_inLibrary) but might do optimizations. + virtual bool canBeUsedExternally(bool _inLibrary) const { return !!interfaceType(_inLibrary); } private: /// @returns a member list containing all members added to this type by `using for` directives. @@ -580,6 +583,7 @@ public: virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; + virtual bool canBeUsedExternally(bool _inLibrary) const override; /// @returns true if this is a byte array or a string bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } -- cgit From 0335ed4cb476ece63224a96c8ab660116ff08c3a Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 14:11:07 +0100 Subject: Simple peephole optimizer that is activated even if not requested. --- libevmasm/Assembly.cpp | 33 +++++--- libevmasm/Assembly.h | 3 +- libevmasm/PeepholeOptimiser.cpp | 146 ++++++++++++++++++++++++++++++++++ libevmasm/PeepholeOptimiser.h | 53 ++++++++++++ libsolidity/codegen/Compiler.cpp | 6 +- libsolidity/codegen/CompilerContext.h | 2 +- 6 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 libevmasm/PeepholeOptimiser.cpp create mode 100644 libevmasm/PeepholeOptimiser.h diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0ee3f421..a813a3a7 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -20,13 +20,17 @@ */ #include "Assembly.h" -#include + #include #include +#include #include #include #include + +#include #include + using namespace std; using namespace dev; using namespace dev::eth; @@ -314,16 +318,15 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - if (_enable) - optimiseInternal(_isCreation, _runs); + optimiseInternal(_enable, _isCreation, _runs); return *this; } -map Assembly::optimiseInternal(bool _isCreation, size_t _runs) +map Assembly::optimiseInternal(bool _enable, bool _isCreation, size_t _runs) { for (size_t subId = 0; subId < m_subs.size(); ++subId) { - map subTagReplacements = m_subs[subId]->optimiseInternal(false, _runs); + map subTagReplacements = m_subs[subId]->optimiseInternal(_enable, false, _runs); BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } @@ -333,6 +336,13 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) { count = 0; + PeepholeOptimiser peepOpt(m_items); + if (peepOpt.optimise()) + count++; + + if (!_enable) + continue; + // This only modifies PushTags, we have to run again to actually remove code. BlockDeduplicator dedup(m_items); if (dedup.deduplicate()) @@ -399,12 +409,13 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) } } - total += ConstantOptimisationMethod::optimiseConstants( - _isCreation, - _isCreation ? 1 : _runs, - *this, - m_items - ); + if (_enable) + total += ConstantOptimisationMethod::optimiseConstants( + _isCreation, + _isCreation ? 1 : _runs, + *this, + m_items + ); return tagReplacements; } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 3ce82cce..f3c56610 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -101,6 +101,7 @@ public: /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. + /// If @a _enable is not set, will perform some simple peephole optimizations. Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); Json::Value stream( std::ostream& _out, @@ -112,7 +113,7 @@ public: protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. - std::map optimiseInternal(bool _isCreation, size_t _runs); + std::map optimiseInternal(bool _enable, bool _isCreation, size_t _runs); std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp new file mode 100644 index 00000000..91b0ece1 --- /dev/null +++ b/libevmasm/PeepholeOptimiser.cpp @@ -0,0 +1,146 @@ +/* + 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 . +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ + +#include "PeepholeOptimiser.h" + +#include +#include + +using namespace std; +using namespace dev::eth; +using namespace dev; + +// TODO: Extend this to use the tools from ExpressionClasses.cpp + +struct Identity +{ + static size_t windowSize() { return 1; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + *_out = *_in; + return true; + } +}; + +struct PushPop +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator) + { + auto t = _in[0].type(); + if (_in[1] == Instruction::POP && ( + SemanticInformation::isDupInstruction(_in[0]) || + t == Push || t == PushString || t == PushTag || t == PushSub || + t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress + )) + return true; + else + return false; + } +}; + +struct DoubleSwap +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator) + { + if (_in[0] == _in[1] && SemanticInformation::isSwapInstruction(_in[0])) + return true; + else + return false; + } +}; + +struct JumpToNext +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + if ( + _in[0].type() == PushTag && + (_in[1] == Instruction::JUMP || _in[1] == Instruction::JUMPI) && + _in[2].type() == Tag && + _in[0].data() == _in[2].data() + ) + { + *_out = _in[2]; + return true; + } + else + return false; + } +}; + +struct TagConjunctions +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + if ( + _in[0].type() == PushTag && + _in[2] == Instruction::AND && + _in[1].type() == Push && + (_in[1].data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF) + ) + { + *_out = _in[0]; + return true; + } + else + return false; + } +}; + +struct OptimiserState +{ + AssemblyItems const& items; + size_t i; + std::back_insert_iterator out; +}; + +void applyMethods(OptimiserState&) +{ + assertThrow(false, OptimizerException, "Peephole optimizer failed to apply identity."); +} + +template +void applyMethods(OptimiserState& _state, Method, OtherMethods... _other) +{ + if (_state.i + Method::windowSize() <= _state.items.size() && Method::apply(_state.items.begin() + _state.i, _state.out)) + _state.i += Method::windowSize(); + else + applyMethods(_state, _other...); +} + +bool PeepholeOptimiser::optimise() +{ + OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; + while (state.i < m_items.size()) + applyMethods(state, PushPop(), DoubleSwap(), JumpToNext(), TagConjunctions(), Identity()); + if (m_optimisedItems.size() < m_items.size()) + { + m_items = std::move(m_optimisedItems); + return true; + } + else + return false; + +} diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h new file mode 100644 index 00000000..dfc9a03b --- /dev/null +++ b/libevmasm/PeepholeOptimiser.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 . +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ +#pragma once + +#include +#include + +namespace dev +{ +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector; + +class PeepholeOptimisationMethod +{ +public: + virtual size_t windowSize() const; + virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out); +}; + +class PeepholeOptimiser +{ +public: + explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} + + bool optimise(); + +private: + AssemblyItems& m_items; + AssemblyItems m_optimisedItems; +}; + +} +} diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index eefa50c5..54639515 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -41,8 +41,7 @@ void Compiler::compileContract( ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); if (_contract.isLibrary()) { @@ -60,8 +59,7 @@ void Compiler::compileClone( ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); } eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index ee3fb068..8ccbddfd 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -155,7 +155,7 @@ public: /// Prepends "PUSH POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm->optimise(true, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } -- cgit From 7a292c9a05eb38f10f6e619db0805105433fda30 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:23:50 +0100 Subject: Fix parser for function type disambiguity. --- libsolidity/parsing/Parser.cpp | 13 ++++++++++++- test/libsolidity/SolidityParser.cpp | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e844861b..02b7d5e0 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -315,7 +315,18 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN m_scanner->next(); } else if (_allowModifiers && token == Token::Identifier) - result.modifiers.push_back(parseModifierInvocation()); + { + // This can either be a modifier (function declaration) or the name of the + // variable (function type name plus variable). + if ( + m_scanner->peekNextToken() == Token::Semicolon || + m_scanner->peekNextToken() == Token::Assign + ) + // Variable declaration, break here. + break; + else + result.modifiers.push_back(parseModifierInvocation()); + } else if (Token::isVisibilitySpecifier(token)) { if (result.visibility != Declaration::Visibility::Default) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 796da782..914dbc30 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1338,6 +1338,17 @@ BOOST_AUTO_TEST_CASE(mapping_and_array_of_functions) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(function_type_state_variable) +{ + char const* text = R"( + contract test { + function() x; + function() y = x; + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() -- cgit From 739dabff8ba8b4a91100c1612a0ac2faf0ef78b5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:24:08 +0100 Subject: Fix tests. --- test/libsolidity/Assembly.cpp | 6 +++--- test/libsolidity/SolidityOptimizer.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index eddba5e1..e5ce691b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -116,10 +116,10 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr n = make_shared(""); AssemblyItems items = compileContract(sourceCode); vector locations = - vector(18, SourceLocation(2, 75, n)) + - vector(31, SourceLocation(20, 72, n)) + + vector(16, SourceLocation(2, 75, n)) + + vector(27, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + - vector(4, SourceLocation(58, 67, n)) + + vector(2, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 976976b2..4698e88c 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(successor_not_found_bug) // This bug was caused because MSVC chose to use the u256->bool conversion // instead of u256->unsigned char const* sourceCode = R"( - contract greeter { function greeter() {} } + contract greeter { function greeter() { uint x = 2; x ++; } } )"; compileBothVersions(sourceCode); } -- cgit From 390ba085b63e3968b555eeab0ddf8ef429e7c0ee Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:38:56 +0100 Subject: fixup! Simple peephole optimizer that is activated even if not requested. --- libevmasm/PeepholeOptimiser.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 91b0ece1..f42dba48 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -81,6 +81,8 @@ struct JumpToNext _in[0].data() == _in[2].data() ) { + if (_in[1] == Instruction::JUMPI) + *_out = AssemblyItem(Instruction::POP, _in[1].location()); *_out = _in[2]; return true; } -- cgit From cb000a55328d0d1980f2a645e06d4f125ef89e47 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 16:35:13 +0100 Subject: Fix setting the tag. --- libevmasm/AssemblyItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index bc8c87aa..7bd93eaf 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -46,7 +46,7 @@ pair AssemblyItem::splitForeignPushTag() const void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - setData(_tag + ((u256(_subId) + 1) << 64)); + setData(_tag + (u256(_subId + 1) << 64)); } unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const -- cgit From a8e7ed37a10f9dd43bfc57db7733412503c1a24e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 18:43:34 +0100 Subject: Disable broken tests that are not useful. --- test/libsolidity/SolidityExpressionCompiler.cpp | 37 ++----------------------- test/libsolidity/SolidityOptimizer.cpp | 10 ------- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index e9a05745..91edfefd 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -325,12 +325,12 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::ADD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::MOD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::DIV), byte(Instruction::MUL)}); @@ -425,39 +425,6 @@ BOOST_AUTO_TEST_CASE(assignment) BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(function_call) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" - " function g(uint a, uint b) returns (uint c) {}\n" - "}\n"; - bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, - {{"test", "f", "a"}, {"test", "f", "b"}}); - - // Stack: a, b - bytes expectation({byte(Instruction::PUSH1), 0x02, - byte(Instruction::PUSH1), 0x0c, - byte(Instruction::PUSH1), 0x01, - byte(Instruction::DUP5), - byte(Instruction::ADD), - // Stack here: a b 2 (a+1) - byte(Instruction::DUP4), - byte(Instruction::PUSH1), 0x13, - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), - // Stack here: a b 2 g(a+1, b) - byte(Instruction::MUL), - // Stack here: a b g(a+1, b)*2 - byte(Instruction::DUP3), - byte(Instruction::ADD), - // Stack here: a b a+g(a+1, b)*2 - byte(Instruction::SWAP2), - byte(Instruction::POP), - byte(Instruction::DUP2), - byte(Instruction::JUMPDEST)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - BOOST_AUTO_TEST_CASE(negative_literals_8bits) { char const* sourceCode = "contract test {\n" diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 4698e88c..4991cf24 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -365,16 +365,6 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions) // BOOST_CHECK_EQUAL(2, numSHA3s); } -BOOST_AUTO_TEST_CASE(successor_not_found_bug) -{ - // This bug was caused because MSVC chose to use the u256->bool conversion - // instead of u256->unsigned - char const* sourceCode = R"( - contract greeter { function greeter() { uint x = 2; x ++; } } - )"; - compileBothVersions(sourceCode); -} - BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug) { // This bug appeared because a sha3 operation with too low sequence number was used, -- cgit From ec31d08775021de0f3279dbeb115b3e688c5997e Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 13:13:37 +0100 Subject: Change encoding to address-funid and add "function" as ABI type. --- libevmasm/PeepholeOptimiser.h | 1 + libsolidity/ast/ASTJsonConverter.cpp | 2 +- libsolidity/ast/Types.cpp | 9 +++++++-- libsolidity/ast/Types.h | 1 + libsolidity/codegen/CompilerUtils.cpp | 31 ++++++++++++++++++------------- libsolidity/codegen/CompilerUtils.h | 2 +- libsolidity/interface/CompilerStack.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 6 +++--- 8 files changed, 33 insertions(+), 21 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h index dfc9a03b..372e49c5 100644 --- a/libevmasm/PeepholeOptimiser.h +++ b/libevmasm/PeepholeOptimiser.h @@ -22,6 +22,7 @@ #include #include +#include namespace dev { diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index d6aca175..37dfd3c6 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -236,7 +236,7 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node) make_pair("payable", _node.isPayable()), make_pair("visibility", visibility), make_pair("constant", _node.isDeclaredConst()) - }); + }, true); return true; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c1ae183e..3a8fc075 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1930,6 +1930,12 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const return TypePointer(); } +string FunctionType::canonicalName(bool) const +{ + solAssert(m_location == Location::External, ""); + return "function"; +} + string FunctionType::toString(bool _short) const { string name = "function ("; @@ -2102,7 +2108,6 @@ TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. if (m_location == Location::External) - // This looks like bytes24, but bytes24 is stored differently on the stack. return shared_from_this(); else return TypePointer(); @@ -2111,7 +2116,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location == Location::External) - return make_shared(storageBytes()); + return shared_from_this(); else return TypePointer(); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 84e56663..34fcfc82 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -897,6 +897,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 1e21c020..1e819ed4 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -317,27 +317,32 @@ void CompilerUtils::memoryCopy() void CompilerUtils::splitExternalFunctionType(bool _leftAligned) { - // We have to split the left-aligned
into two stack slots: + // We have to split the left-aligned
into two stack slots: // address (right aligned), function identifier (right aligned) if (_leftAligned) - m_context << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; - m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; - m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - if (!_leftAligned) - m_context << u256(0xffffffffUL) << Instruction::AND; + { + m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV; + //
+ m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; + } + else + { + m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + } + m_context << u256(0xffffffffUL) << Instruction::AND; } void CompilerUtils::combineExternalFunctionType(bool _leftAligned) { - if (_leftAligned) - m_context << (u256(1) << 224); - else - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160); - m_context << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND; + //
+ m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; + if (!_leftAligned) + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << (u256(1) << 32) << Instruction::MUL; + m_context << Instruction::OR; if (_leftAligned) m_context << (u256(1) << 64) << Instruction::MUL; - m_context << Instruction::OR; } void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 2ebec81a..8e4033d6 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -115,7 +115,7 @@ public: void memoryCopy(); /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) - /// external function type
into two stack slots: + /// external function type
into two stack slots: /// address (right aligned), function identifier (right aligned) void splitExternalFunctionType(bool _rightAligned); /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c86de43f..f1eb2614 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -597,7 +597,7 @@ void CompilerStack::compileContract( cloneCompiler.compileClone(_contract, _compiledContracts); compiledContract.cloneObject = cloneCompiler.assembledObject(); } - catch (eth::AssemblyException const& _e) + catch (eth::AssemblyException const&) { // In some cases (if the constructor requests a runtime function), it is not // possible to compile the clone. diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c01d11d2..ed95d687 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7703,8 +7703,8 @@ BOOST_AUTO_TEST_CASE(receive_external_function_type) compileAndRun(sourceCode, 0, "C"); BOOST_CHECK(callContractFunction( - "f(bytes24)", - FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + "f(function)", + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) ) == encodeArgs(u256(7))); } @@ -7722,7 +7722,7 @@ BOOST_AUTO_TEST_CASE(return_external_function_type) compileAndRun(sourceCode, 0, "C"); BOOST_CHECK( callContractFunction("f()") == - FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) ); } -- cgit From 830f14c3a3c7bc991ad34c1b0299a31368853d97 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 21:51:41 +0100 Subject: Fix documentation formatting. --- docs/types.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 7f4570ed..c9e2e6fa 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -311,7 +311,7 @@ on it. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. -Example that shows how to use internal function types: +Example that shows how to use internal function types:: library ArrayUtils { // internal functions can be used in internal library functions because @@ -356,7 +356,7 @@ Example that shows how to use internal function types: } } -Another example that uses external function types: +Another example that uses external function types:: contract Oracle { struct Request { -- cgit From e1fec9b2877492658b079d3278a702310213fe2a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:37:19 +0100 Subject: JSON tests. --- test/libsolidity/ASTJSON.cpp | 14 ++++++++++++++ test/libsolidity/SolidityABIJSON.cpp | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 6c062ee8..0180c4ac 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -197,6 +197,20 @@ BOOST_AUTO_TEST_CASE(non_utf8) BOOST_CHECK(literal["attributes"]["type"].asString().find("invalid") != string::npos); } +BOOST_AUTO_TEST_CASE(function_type) +{ + CompilerStack c; + c.addSource("a", "contract C { function f(function() external payable constant returns (uint) x) {} }"); + c.parse(); + map sourceIndices; + sourceIndices["a"] = 1; + Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); + Json::Value event = astJson["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(event["name"], "EventDefinition"); + BOOST_CHECK_EQUAL(event["attributes"]["name"], "E"); + BOOST_CHECK_EQUAL(event["src"], "13:10:1"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index c01ff11b..f77ad5fc 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -703,6 +703,29 @@ BOOST_AUTO_TEST_CASE(payable_fallback_function) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* sourceCode = R"( + contract test { + function g(function(uint) external returns (uint)) {} + } + )"; + + char const* interface = R"( + [ + { + "constant" : false, + "payable": true, + "inputs": ["function"], + "name": "g", + "outputs": [], + "type" : "function" + } + ] + )"; + checkInterface(sourceCode, interface); +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit From b3eeb5fcf9a8efe1fc2a715fbd1a03c421824d72 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:52:07 +0100 Subject: Some more tests. --- test/libsolidity/ASTJSON.cpp | 14 ++++++++++---- test/libsolidity/SolidityABIJSON.cpp | 9 ++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 0180c4ac..fd7c21b8 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -205,10 +205,16 @@ BOOST_AUTO_TEST_CASE(function_type) map sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); - Json::Value event = astJson["children"][0]["children"][0]; - BOOST_CHECK_EQUAL(event["name"], "EventDefinition"); - BOOST_CHECK_EQUAL(event["attributes"]["name"], "E"); - BOOST_CHECK_EQUAL(event["src"], "13:10:1"); + Json::Value fun = astJson["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(fun["name"], "FunctionDefinition"); + Json::Value argument = fun["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(argument["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(argument["attributes"]["name"], "x"); + BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () constant payable external returns (uint256)"); + Json::Value funType = argument["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index f77ad5fc..5cabb7fa 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -707,7 +707,7 @@ BOOST_AUTO_TEST_CASE(function_type) { char const* sourceCode = R"( contract test { - function g(function(uint) external returns (uint)) {} + function g(function(uint) external returns (uint) x) {} } )"; @@ -715,8 +715,11 @@ BOOST_AUTO_TEST_CASE(function_type) [ { "constant" : false, - "payable": true, - "inputs": ["function"], + "payable": false, + "inputs": [{ + "name": "x", + "type": "function" + }], "name": "g", "outputs": [], "type" : "function" -- cgit From eeae91c2a2897c6fd155890d654485fc8cf4dfc6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:54:44 +0100 Subject: Update documentation. --- docs/types.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index c9e2e6fa..ae27ee31 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -309,7 +309,8 @@ in an exception. The same happens if you call a function after using ``delete`` on it. If external function types are used outside of the context of Solidity, -they are converted into the ``bytes24`` type. +they are treated as the ``function`` type, which encodes the address +followed by the function identifier together in a single ``bytes24`` type. Example that shows how to use internal function types:: -- cgit From 2defe4dcefb6f0e17d7f87231233ff637dad55a8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 15 Nov 2016 11:43:46 +0100 Subject: Documentation: Style update --- docs/types.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index ae27ee31..b22ad7d4 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -274,10 +274,10 @@ check the value ranges at runtime and a failure causes an exception. Enums need Function Types -------------- -Function types can be used to assign functions to variables and passing them -to or return them from function calls. Such variables and parameters have -to have function types. These types come in two flavours: *internal* and *external* -functions. +Function types are the types of functions. Variables of function type +can be assigned from functions and function parameters of function type +can be used to pass functions to and return functions from function calls. +Function types come in two flavours - *internal* and *external* functions: Internal functions can only be used inside the current contract (more specifically, inside the current code unit, which also includes internal library functions @@ -289,7 +289,7 @@ contract internally. External functions consist of an address and a function signature and they can be passed via and returned from external function calls. -Function types are notated as follows: +Function types are notated as follows:: function () {internal|external} [constant] [payable] [returns ()] -- cgit From 2c14a96820233809db4360b39f5f02039be5730a Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 16 Nov 2016 15:09:01 +0100 Subject: Some more assertions and style changes. --- libevmasm/Assembly.cpp | 2 +- libsolidity/ast/Types.cpp | 41 +++++++++++++++++++++-------------- libsolidity/codegen/CompilerUtils.cpp | 2 +- test/libsolidity/ASTJSON.cpp | 17 ++++++++++++--- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index a813a3a7..a3037456 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -81,7 +81,7 @@ string Assembly::out() const unsigned Assembly::bytesRequired(unsigned subTagSize) const { - for (unsigned tagSize = subTagSize;; ++tagSize) + for (unsigned tagSize = subTagSize; true; ++tagSize) { unsigned ret = 1; for (auto const& i: m_data) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3a8fc075..4488398f 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1827,23 +1827,28 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): m_isPayable(_typeName.isPayable()) { if (_typeName.isPayable()) + { solAssert(m_location == Location::External, "Internal payable function type used."); + solAssert(!m_isConstant, "Payable constant function"); + } for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); - solAssert( - m_location != Location::External || t->annotation().type->canBeUsedExternally(false), - "Internal type used as parameter for external function." - ); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as parameter for external function." + ); m_parameterTypes.push_back(t->annotation().type); } for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); - solAssert( - m_location != Location::External || t->annotation().type->canBeUsedExternally(false), - "Internal type used as return parameter for external function." - ); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as return parameter for external function." + ); m_returnParameterTypes.push_back(t->annotation().type); } } @@ -1941,17 +1946,21 @@ string FunctionType::toString(bool _short) const string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); - name += ") "; + name += ")"; if (m_isConstant) - name += "constant "; + name += " constant"; if (m_isPayable) - name += "payable "; + name += " payable"; if (m_location == Location::External) - name += "external "; - name += "returns ("; - for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) - name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); - return name + ")"; + name += " external"; + if (!m_returnParameterTypes.empty()) + { + name += " returns ("; + for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) + name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); + name += ")"; + } + return name; } unsigned FunctionType::calldataEncodedSize(bool _padded) const diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 1e819ed4..bfe5386b 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -138,7 +138,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound dynamic_cast(_type).location() == FunctionType::Location::External ) { - solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); combineExternalFunctionType(true); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index fd7c21b8..b88218e7 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -200,7 +200,10 @@ BOOST_AUTO_TEST_CASE(non_utf8) BOOST_AUTO_TEST_CASE(function_type) { CompilerStack c; - c.addSource("a", "contract C { function f(function() external payable constant returns (uint) x) {} }"); + c.addSource("a", + "contract C { function f(function() external payable returns (uint) x) " + "returns (function() external constant returns (uint)) {} }" + ); c.parse(); map sourceIndices; sourceIndices["a"] = 1; @@ -210,11 +213,19 @@ BOOST_AUTO_TEST_CASE(function_type) Json::Value argument = fun["children"][0]["children"][0]; BOOST_CHECK_EQUAL(argument["name"], "VariableDeclaration"); BOOST_CHECK_EQUAL(argument["attributes"]["name"], "x"); - BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () constant payable external returns (uint256)"); + BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () payable external returns (uint256)"); Json::Value funType = argument["children"][0]; - BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], false); BOOST_CHECK_EQUAL(funType["attributes"]["payable"], true); BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); + Json::Value retval = fun["children"][1]["children"][0]; + BOOST_CHECK_EQUAL(retval["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(retval["attributes"]["name"], ""); + BOOST_CHECK_EQUAL(retval["attributes"]["type"], "function () constant external returns (uint256)"); + funType = retval["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], false); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); } BOOST_AUTO_TEST_SUITE_END() -- cgit