diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index bc6be6008..20c117c0f 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -56,7 +56,12 @@ void ContractDefinition::checkTypeRequirements() FunctionDefinition const* constructor = getConstructor(); if (constructor && !constructor->getReturnParameters().empty()) BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError( - "Non-empty \"returns\" directive for constructor.")); + "Non-empty \"returns\" directive for constructor.")); + + FunctionDefinition const* fallbackFunction = getFallbackFunction(); + if (fallbackFunction && fallbackFunction->getScope() == this && !fallbackFunction->getParameters().empty()) + BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError( + "Fallback function cannot take parameters.")); for (ASTPointer const& modifier: getFunctionModifiers()) modifier->checkTypeRequirements(); @@ -99,6 +104,15 @@ FunctionDefinition const* ContractDefinition::getConstructor() const return nullptr; } +FunctionDefinition const* ContractDefinition::getFallbackFunction() const +{ + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + for (ASTPointer const& f: contract->getDefinedFunctions()) + if (f->getName().empty()) + return f.get(); + return nullptr; +} + void ContractDefinition::checkIllegalOverrides() const { // TODO unify this at a later point. for this we need to put the constness and the access specifier @@ -147,7 +161,7 @@ vector, FunctionTypePointer>> const& ContractDefinition::getIn for (ContractDefinition const* contract: getLinearizedBaseContracts()) { for (ASTPointer const& f: contract->getDefinedFunctions()) - if (f->isPublic() && !f->isConstructor() && functionsSeen.count(f->getName()) == 0) + if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && functionsSeen.count(f->getName()) == 0) { functionsSeen.insert(f->getName()); FixedHash<4> hash(dev::sha3(f->getCanonicalSignature())); diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 6c207290c..3dc63d333 100755 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -235,8 +235,10 @@ public: std::vector const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } void setLinearizedBaseContracts(std::vector const& _bases) { m_linearizedBaseContracts = _bases; } - /// Returns the constructor or nullptr if no constructor was specified + /// Returns the constructor or nullptr if no constructor was specified. FunctionDefinition const* getConstructor() const; + /// Returns the fallback function or nullptr if no constructor was specified. + FunctionDefinition const* getFallbackFunction() const; private: void checkIllegalOverrides() const; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 1fa31ce7d..3c46d4552 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -146,8 +146,8 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) map, const eth::AssemblyItem> callDataUnpackerEntryPoints; // retrieve the function signature hash from the calldata - m_context << u256(1) << u256(0); - CompilerUtils(m_context).loadFromMemory(0, 4, false, true); + if (!interfaceFunctions.empty()) + CompilerUtils(m_context).loadFromMemory(0, 4, false, true); // stack now is: 1 0 for (auto const& it: interfaceFunctions) @@ -156,7 +156,15 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); } - m_context << eth::Instruction::STOP; // function not found + if (FunctionDefinition const* fallback = _contract.getFallbackFunction()) + { + eth::AssemblyItem returnTag = m_context.pushNewTag(); + fallback->accept(*this); + m_context << returnTag; + appendReturnValuePacker(FunctionType(*fallback).getReturnParameterTypes()); + } + else + m_context << eth::Instruction::STOP; // function not found for (auto const& it: interfaceFunctions) { diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 5cfc8f462..812b3263d 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -189,7 +189,11 @@ ASTPointer Parser::parseFunctionDefinition(bool _isPublic, A docstring = make_shared(m_scanner->getCurrentCommentLiteral()); expectToken(Token::FUNCTION); - ASTPointer name(expectIdentifierToken()); + ASTPointer name; + if (m_scanner->getCurrentToken() == Token::LPAREN) + name = make_shared(); // anonymous function + else + name = expectIdentifierToken(); ASTPointer parameters(parseParameterList()); bool isDeclaredConst = false; vector> modifiers; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index bebb4be17..afc1c1dba 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -494,7 +494,7 @@ MemberList const& ContractType::getMembers() const { for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) for (ASTPointer const& function: base->getDefinedFunctions()) - if (!function->isConstructor()) + if (!function->isConstructor() && !function->getName().empty()) members.insert(make_pair(function->getName(), make_shared(*function, true))); } else @@ -808,7 +808,7 @@ MemberList const& TypeType::getMembers() const // We are accessing the type of a base contract, so add all public and private // functions. Note that this does not add inherited functions on purpose. for (ASTPointer const& f: contract.getDefinedFunctions()) - if (!f->isConstructor()) + if (!f->isConstructor() && !f->getName().empty()) members[f->getName()] = make_shared(*f); } m_members.reset(new MemberList(members)); diff --git a/test/SolidityABIJSON.cpp b/test/SolidityABIJSON.cpp index 4a44ebb84..edafb1686 100644 --- a/test/SolidityABIJSON.cpp +++ b/test/SolidityABIJSON.cpp @@ -273,6 +273,15 @@ BOOST_AUTO_TEST_CASE(const_function) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(exclude_fallback_function) +{ + char const* sourceCode = "contract test { function() {} }"; + + char const* interface = "[]"; + + checkInterface(sourceCode, interface); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityCompiler.cpp b/test/SolidityCompiler.cpp index 98397af79..17d9a7c07 100644 --- a/test/SolidityCompiler.cpp +++ b/test/SolidityCompiler.cpp @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 73; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, // initialize local variable x byte(Instruction::PUSH1), 0x2, @@ -114,8 +114,8 @@ BOOST_AUTO_TEST_CASE(ifStatement) " function f() { bool x; if (x) 77; else if (!x) 78; else 79; }" "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 60; - unsigned boilerplateSize = 73; + unsigned shift = 56; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, byte(Instruction::DUP1), @@ -155,8 +155,8 @@ BOOST_AUTO_TEST_CASE(loops) " function f() { while(true){1;break;2;continue;3;return;4;} }" "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 60; - unsigned boilerplateSize = 73; + unsigned shift = 56; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x1, diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index f29767075..576e0f917 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -1955,6 +1955,37 @@ BOOST_AUTO_TEST_CASE(super_in_constructor) BOOST_CHECK(callContractFunction("f()") == encodeArgs(1 | 2 | 4 | 8)); } +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* sourceCode = R"( + contract A { + uint data; + function() returns (uint r) { data = 1; return 2; } + function getData() returns (uint r) { return data; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(0)); + BOOST_CHECK(callContractFunction("") == encodeArgs(2)); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(inherited_fallback_function) +{ + char const* sourceCode = R"( + contract A { + uint data; + function() returns (uint r) { data = 1; return 2; } + function getData() returns (uint r) { return data; } + } + contract B is A {} + )"; + compileAndRun(sourceCode, 0, "B"); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(0)); + BOOST_CHECK(callContractFunction("") == encodeArgs(2)); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(1)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index 07f2d638a..d081916cd 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -680,6 +680,54 @@ BOOST_AUTO_TEST_CASE(private_state_variable) BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of a private variable should not exist"); } +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* text = R"( + contract C { + uint x; + function() { x = 2; } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(fallback_function_with_arguments) +{ + char const* text = R"( + contract C { + uint x; + function(uint a) { x = 2; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(fallback_function_twice) +{ + char const* text = R"( + contract C { + uint x; + function() { x = 2; } + function() { x = 3; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(fallback_function_inheritance) +{ + char const* text = R"( + contract A { + uint x; + function() { x = 1; } + } + contract C is A { + function() { x = 2; } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityParser.cpp b/test/SolidityParser.cpp index 7bfb4c0c8..0059ccf70 100644 --- a/test/SolidityParser.cpp +++ b/test/SolidityParser.cpp @@ -586,6 +586,14 @@ BOOST_AUTO_TEST_CASE(modifier_invocation) BOOST_CHECK_NO_THROW(parseText(text)); } +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* text = "contract c {\n" + " function() { }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + BOOST_AUTO_TEST_SUITE_END() }