diff --git a/CMakeLists.txt b/CMakeLists.txt index b29bc4143..60a8158de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.14") +set(PROJECT_VERSION "0.4.15") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index af301fddc..99466fd19 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,16 @@ +### 0.4.15 (2017-08-08) + +Features: + * Type Checker: Show unimplemented function if trying to instantiate an abstract class. + +Bugfixes: + * Code Generator: ``.delegatecall()`` should always return execution outcome. + * Code Generator: Provide "new account gas" for low-level ``callcode`` and ``delegatecall``. + * Type Checker: Constructors must be implemented if declared. + * Type Checker: Disallow the ``.gas()`` modifier on ``ecrecover``, ``sha256`` and ``ripemd160``. + * Type Checker: Do not mark overloaded functions as shadowing other functions. + * Type Checker: Internal library functions must be implemented if declared. + ### 0.4.14 (2017-07-31) Features: diff --git a/docs/bugs.json b/docs/bugs.json index 4fd73492d..ac322a486 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,14 +1,22 @@ [ + { + "name": "DelegateCallReturnValue", + "summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.", + "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successuful.", + "introduced": "0.3.0", + "fixed": "0.4.15", + "severity": "low" + }, { "name": "ECRecoverMalformedInput", - "summary": "The ecrecover() builtin can return garbage for malformed input.", + "summary": "The ecrecover() builtin can return garbage for malformed input.", "description": "The ecrecover precompile does not properly signal failure for malformed input (especially in the 'v' argument) and thus the Solidity function can return data that was previously present in the return area in memory.", "fixed": "0.4.14", "severity": "medium" }, { "name": "SkipEmptyStringLiteral", - "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.", + "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.", "description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.", "fixed": "0.4.12", "severity": "low" diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 1a5b4f5f2..33f7bae98 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -182,6 +182,7 @@ }, "0.3.0": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -198,6 +199,7 @@ }, "0.3.1": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -213,6 +215,7 @@ }, "0.3.2": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -228,6 +231,7 @@ }, "0.3.3": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -242,6 +246,7 @@ }, "0.3.4": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -256,6 +261,7 @@ }, "0.3.5": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -270,6 +276,7 @@ }, "0.3.6": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -282,6 +289,7 @@ }, "0.4.0": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -294,6 +302,7 @@ }, "0.4.1": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -306,6 +315,7 @@ }, "0.4.10": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" @@ -314,6 +324,7 @@ }, "0.4.11": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral" ], @@ -321,22 +332,31 @@ }, "0.4.12": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput" ], "released": "2017-07-03" }, "0.4.13": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput" ], "released": "2017-07-06" }, "0.4.14": { - "bugs": [], + "bugs": [ + "DelegateCallReturnValue" + ], "released": "2017-07-31" }, + "0.4.15": { + "bugs": [], + "released": "2017-08-08" + }, "0.4.2": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -348,6 +368,7 @@ }, "0.4.3": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -358,6 +379,7 @@ }, "0.4.4": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -367,6 +389,7 @@ }, "0.4.5": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -377,6 +400,7 @@ }, "0.4.6": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", @@ -386,6 +410,7 @@ }, "0.4.7": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" @@ -394,6 +419,7 @@ }, "0.4.8": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" @@ -402,6 +428,7 @@ }, "0.4.9": { "bugs": [ + "DelegateCallReturnValue", "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" diff --git a/docs/control-structures.rst b/docs/control-structures.rst index a7af69f5f..796e92389 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -393,6 +393,9 @@ When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are reth and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case of an exception instead of "bubbling up". +.. warning:: + The low-level ``call``, ``delegatecall`` and ``callcode`` will return success if the calling account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. + Catching exceptions is not yet possible. In the following example, you can see how ``require`` can be used to easily check conditions on inputs diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index e07561c54..35eb2d93a 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -56,7 +56,7 @@ repository contains potentially unstable changes in the develop branch. docker run ethereum/solc:stable solc --version -Currenty, the docker image only contains the compiler executable, +Currently, the docker image only contains the compiler executable, so you have to do some additional work to link in the source and output directories. @@ -83,7 +83,15 @@ If you want to use the cutting edge developer version: sudo apt-get update sudo apt-get install solc -We are also releasing a `snap package `_, which is installable in all the `supported Linux distros `_. To help testing the unstable solc with the most recent changes from the development branch: +We are also releasing a `snap package `_, which is installable in all the `supported Linux distros `_. To install the latest stable version of solc: + +.. code:: bash + + sudo snap install solc + +Or if you want to help testing the unstable solc with the most recent changes from the development branch: + +.. code:: bash sudo snap install solc --edge diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 71d271929..dde4495b5 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -277,9 +277,9 @@ activate themselves. if (highestBidder != 0) { // Sending back the money by simply using // highestBidder.send(highestBid) is a security risk - // because it can be prevented by the caller by e.g. - // raising the call stack to 1023. It is always safer - // to let the recipients withdraw their money themselves. + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index 9c7c89e73..301998b71 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -53,6 +53,7 @@ public: bool registerDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr, bool _invisible = false, bool _update = false); std::vector resolveName(ASTString const& _name, bool _recursive = false) const; ASTNode const* enclosingNode() const { return m_enclosingNode; } + DeclarationContainer const* enclosingContainer() const { return m_enclosingContainer; } std::map> const& declarations() const { return m_declarations; } /// @returns whether declaration is valid, and if not also returns previous declaration. Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index df83f382b..523e71769 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -452,13 +452,9 @@ bool DeclarationRegistrationHelper::registerDeclaration( _errorLocation = &_declaration.location(); Declaration const* shadowedDeclaration = nullptr; - if (_warnOnShadow && !_declaration.name().empty()) - for (auto const* decl: _container.resolveName(_declaration.name(), true)) - if (decl != &_declaration) - { - shadowedDeclaration = decl; - break; - } + if (_warnOnShadow && !_declaration.name().empty() && _container.enclosingContainer()) + for (auto const* decl: _container.enclosingContainer()->resolveName(_declaration.name(), true)) + shadowedDeclaration = decl; if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract())) { diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index a498c7ba6..59bd3b1f4 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -169,6 +169,8 @@ private: void closeCurrentScope(); void registerDeclaration(Declaration& _declaration, bool _opensScope); + static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2); + /// @returns the canonical name of the current scope. std::string currentCanonicalName() const; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5419db2d8..6852c13da 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -112,8 +112,6 @@ bool TypeChecker::visit(ContractDefinition const& _contract) m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values."); } } - if (!function->isImplemented()) - _contract.annotation().isFullyImplemented = false; } for (auto const& n: _contract.subNodes()) @@ -188,20 +186,13 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont using FunTypeAndFlag = std::pair; map> functions; - bool allBaseConstructorsImplemented = true; // Search from base to derived for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (FunctionDefinition const* function: contract->definedFunctions()) { // Take constructors out of overload hierarchy if (function->isConstructor()) - { - if (!function->isImplemented()) - // Base contract's constructor is not fully implemented, no way to get - // out of this. - allBaseConstructorsImplemented = false; continue; - } auto& overloads = functions[function->name()]; FunctionTypePointer funType = make_shared(*function); auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) @@ -219,16 +210,15 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont it->second = true; } - if (!allBaseConstructorsImplemented) - _contract.annotation().isFullyImplemented = false; - // Set to not fully implemented if at least one flag is false. for (auto const& it: functions) for (auto const& funAndFlag: it.second) if (!funAndFlag.second) { - _contract.annotation().isFullyImplemented = false; - return; + FunctionDefinition const* function = dynamic_cast(&funAndFlag.first->declaration()); + solAssert(function, ""); + _contract.annotation().unimplementedFunctions.push_back(function); + break; } } @@ -266,7 +256,8 @@ void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _c } } if (!argumentsNeeded.empty()) - _contract.annotation().isFullyImplemented = false; + for (ContractDefinition const* contract: argumentsNeeded) + _contract.annotation().unimplementedFunctions.push_back(contract->constructor()); } void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) @@ -525,6 +516,10 @@ bool TypeChecker::visit(FunctionDefinition const& _function) } if (_function.isImplemented()) _function.body().accept(*this); + else if (_function.isConstructor()) + m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared."); + else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal) + m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared."); return false; } @@ -1050,7 +1045,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) { auto kind = callType->kind(); if ( - kind == FunctionType::Kind::Bare || + kind == FunctionType::Kind::BareCall || kind == FunctionType::Kind::BareCallCode || kind == FunctionType::Kind::BareDelegateCall ) @@ -1523,8 +1518,15 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (!contract) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); - if (!contract->annotation().isFullyImplemented) - m_errorReporter.typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); + if (!contract->annotation().unimplementedFunctions.empty()) + m_errorReporter.typeError( + _newExpression.location(), + SecondarySourceLocation().append( + "Missing implementation:", + contract->annotation().unimplementedFunctions.front()->location() + ), + "Trying to create an instance of an abstract contract." + ); if (!contract->constructorIsPublic()) m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 45a6dd1a4..f757f03c0 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -79,8 +79,8 @@ struct TypeDeclarationAnnotation: ASTAnnotation struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation { - /// Whether all functions are implemented. - bool isFullyImplemented = true; + /// List of functions without a body. Can also contain functions from base classes. + std::vector unimplementedFunctions; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector linearizedBaseContracts; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index eda70b63e..e4a602cba 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -253,7 +253,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), - make_pair("fullyImplemented", _node.annotation().isFullyImplemented), + make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)), diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3f8da5018..56fdd508f 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -477,7 +477,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons if (isAddress()) return { {"balance", make_shared(256)}, - {"call", make_shared(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)}, + {"call", make_shared(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, false, true)}, {"callcode", make_shared(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)}, {"delegatecall", make_shared(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, {"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, @@ -2178,7 +2178,7 @@ string FunctionType::identifier() const case Kind::External: id += "external"; break; case Kind::CallCode: id += "callcode"; break; case Kind::DelegateCall: id += "delegatecall"; break; - case Kind::Bare: id += "bare"; break; + case Kind::BareCall: id += "barecall"; break; case Kind::BareCallCode: id += "barecallcode"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break; case Kind::Creation: id += "creation"; break; @@ -2346,7 +2346,7 @@ unsigned FunctionType::sizeOnStack() const unsigned size = 0; if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall) size = 2; - else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) + else if (kind == Kind::BareCall || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) size = 1; else if (kind == Kind::Internal) size = 1; @@ -2402,10 +2402,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { case Kind::External: case Kind::Creation: - case Kind::ECRecover: - case Kind::SHA256: - case Kind::RIPEMD160: - case Kind::Bare: + case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: { @@ -2509,7 +2506,7 @@ bool FunctionType::isBareCall() const { switch (m_kind) { - case Kind::Bare: + case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: case Kind::ECRecover: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 1db463554..5d2bdca06 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -838,7 +838,7 @@ public: External, ///< external call using CALL CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage - Bare, ///< CALL without function hash + BareCall, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash BareDelegateCall, ///< DELEGATECALL without function hash Creation, ///< external call using CREATE diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 02cc62be0..521d485f2 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -546,7 +546,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::External: case FunctionType::Kind::CallCode: case FunctionType::Kind::DelegateCall: - case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: _functionCall.expression().accept(*this); @@ -642,7 +642,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers{}, strings(), strings(), - FunctionType::Kind::Bare, + FunctionType::Kind::BareCall, false, nullptr, false, @@ -973,7 +973,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case FunctionType::Kind::DelegateCall: case FunctionType::Kind::CallCode: case FunctionType::Kind::Send: - case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::Transfer: @@ -1560,7 +1560,7 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); auto funKind = _functionType.kind(); - bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode; + bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; @@ -1579,7 +1579,7 @@ void ExpressionCompiler::appendExternalFunctionCall( TypePointers parameterTypes = _functionType.parameterTypes(); bool manualFunctionId = false; if ( - (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && + (funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && !_arguments.empty() ) { @@ -1712,7 +1712,7 @@ void ExpressionCompiler::appendExternalFunctionCall( u256 gasNeededByCaller = eth::GasCosts::callGas + 10; if (_functionType.valueSet()) gasNeededByCaller += eth::GasCosts::callValueTransferGas; - if (!isCallCode && !isDelegateCall && !existenceChecked) + if (!existenceChecked) gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 9689b700c..7c66a8430 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -639,7 +639,7 @@ void CompilerStack::compileContract( { if ( _compiledContracts.count(&_contract) || - !_contract.annotation().isFullyImplemented || + !_contract.annotation().unimplementedFunctions.empty() || !_contract.constructorIsPublic() ) return; diff --git a/libsolidity/interface/ErrorReporter.cpp b/libsolidity/interface/ErrorReporter.cpp index f9ef4ceb4..e6171756c 100644 --- a/libsolidity/interface/ErrorReporter.cpp +++ b/libsolidity/interface/ErrorReporter.cpp @@ -151,6 +151,16 @@ void ErrorReporter::syntaxError(SourceLocation const& _location, string const& _ ); } +void ErrorReporter::typeError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + error( + Error::Type::TypeError, + _location, + _secondaryLocation, + _description + ); +} + void ErrorReporter::typeError(SourceLocation const& _location, string const& _description) { error( diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index 8b066a3ee..42b0c8b62 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -73,6 +73,12 @@ public: void syntaxError(SourceLocation const& _location, std::string const& _description); + void typeError( + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description + ); + void typeError(SourceLocation const& _location, std::string const& _description); void fatalTypeError(SourceLocation const& _location, std::string const& _description); diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 7730a99a1..62d22999f 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -101,6 +101,8 @@ void SourceReferenceFormatter::printExceptionInformation( _stream << _name; if (string const* description = boost::get_error_info(_exception)) _stream << ": " << *description << endl; + else + _stream << endl; printSourceLocation(_stream, location, _scannerFromSourceName); @@ -108,9 +110,8 @@ void SourceReferenceFormatter::printExceptionInformation( { for (auto info: secondarylocation->infos) { - _stream << info.first << " "; printSourceName(_stream, &info.second, _scannerFromSourceName); - _stream << endl; + _stream << info.first << endl; printSourceLocation(_stream, &info.second, _scannerFromSourceName); } _stream << endl; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index db7f59ee5..d0c5285cf 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1950,6 +1950,87 @@ BOOST_AUTO_TEST_CASE(ripemd) testContractAgainstCpp("a(bytes32)", f, u256(-1)); } +BOOST_AUTO_TEST_CASE(packed_keccak256) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return keccak256(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> u256 + { + return dev::keccak256( + toCompactBigEndian(unsigned(8)) + + toBigEndian(_x) + + toCompactBigEndian(unsigned(65536)) + + toBigEndian(_x) + + toBigEndian(u256(256)) + ); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(packed_sha256) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return sha256(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> bytes + { + if (_x == u256(4)) + return fromHex("804e0d7003cfd70fc925dc103174d9f898ebb142ecc2a286da1abd22ac2ce3ac"); + if (_x == u256(5)) + return fromHex("e94921945f9068726c529a290a954f412bcac53184bb41224208a31edbf63cf0"); + if (_x == u256(-1)) + return fromHex("f14def4d07cd185ddd8b10a81b2238326196a38867e6e6adbcc956dc913488c7"); + return fromHex(""); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(packed_ripemd160) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return ripemd160(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> bytes + { + if (_x == u256(4)) + return fromHex("f93175303eba2a7b372174fc9330237f5ad202fc000000000000000000000000"); + if (_x == u256(5)) + return fromHex("04f4fc112e2bfbe0d38f896a46629e08e2fcfad5000000000000000000000000"); + if (_x == u256(-1)) + return fromHex("c0a2e4b1f3ff766a9a0089e7a410391730872495000000000000000000000000"); + return fromHex(""); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + BOOST_AUTO_TEST_CASE(ecrecover) { char const* sourceCode = R"( @@ -2318,21 +2399,6 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic) BOOST_REQUIRE(callContractFunction("checkState()") == encodeArgs(false, 20 - 5)); } -BOOST_AUTO_TEST_CASE(gas_for_builtin) -{ - char const* sourceCode = R"( - contract Contract { - function test(uint g) returns (bytes32 data, bool flag) { - data = ripemd160.gas(g)("abc"); - flag = true; - } - } - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test(uint256)", 500) == bytes()); - BOOST_CHECK(callContractFunction("test(uint256)", 800) == encodeArgs(u256("0x8eb208f7e05d987a9b044a8e98c6b087f15a0bfc000000000000000000000000"), true)); -} - BOOST_AUTO_TEST_CASE(value_complex) { char const* sourceCode = R"( @@ -9816,6 +9882,64 @@ BOOST_AUTO_TEST_CASE(inlineasm_empty_let) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0), u256(0))); } +BOOST_AUTO_TEST_CASE(bare_call_invalid_address) +{ + char const* sourceCode = R"( + contract C { + /// Calling into non-existant account is successful (creates the account) + function f() external constant returns (bool) { + return address(0x4242).call(); + } + function g() external constant returns (bool) { + return address(0x4242).callcode(); + } + function h() external constant returns (bool) { + return address(0x4242).delegatecall(); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("g()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("h()") == encodeArgs(u256(1))); +} + +BOOST_AUTO_TEST_CASE(delegatecall_return_value) +{ + char const* sourceCode = R"DELIMITER( + contract C { + uint value; + function set(uint _value) external { + value = _value; + } + function get() external constant returns (uint) { + return value; + } + function get_delegated() external constant returns (bool) { + return this.delegatecall(bytes4(sha3("get()"))); + } + function assert0() external constant { + assert(value == 0); + } + function assert0_delegated() external constant returns (bool) { + return this.delegatecall(bytes4(sha3("assert0()"))); + } + } + )DELIMITER"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("set(uint256)", u256(1)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("set(uint256)", u256(42)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(42))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index cd922cc8d..2fbc6ac8f 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -624,7 +624,7 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) std::vector> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast(nodes[1].get()); BOOST_REQUIRE(contract); - BOOST_CHECK(!contract->annotation().isFullyImplemented); + BOOST_CHECK(!contract->annotation().unimplementedFunctions.empty()); BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented()); } @@ -640,10 +640,10 @@ BOOST_AUTO_TEST_CASE(abstract_contract) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().isFullyImplemented); + BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); BOOST_CHECK(!base->definedFunctions()[0]->isImplemented()); BOOST_REQUIRE(derived); - BOOST_CHECK(derived->annotation().isFullyImplemented); + BOOST_CHECK(derived->annotation().unimplementedFunctions.empty()); BOOST_CHECK(derived->definedFunctions()[0]->isImplemented()); } @@ -659,9 +659,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().isFullyImplemented); + BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); + BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); } BOOST_AUTO_TEST_CASE(create_abstract_contract) @@ -677,44 +677,6 @@ BOOST_AUTO_TEST_CASE(create_abstract_contract) CHECK_ERROR(text, TypeError, ""); } -BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_optional) -{ - ASTPointer sourceUnit; - char const* text = R"( - contract BaseBase { function BaseBase(uint j); } - contract base is BaseBase { function foo(); } - contract derived is base { - function derived(uint i) BaseBase(i){} - function foo() {} - } - )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); - std::vector> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 4); - ContractDefinition* derived = dynamic_cast(nodes[3].get()); - BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); -} - -BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_not_provided) -{ - ASTPointer sourceUnit; - char const* text = R"( - contract BaseBase { function BaseBase(uint); } - contract base is BaseBase { function foo(); } - contract derived is base { - function derived(uint) {} - function foo() {} - } - )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); - std::vector> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 4); - ContractDefinition* derived = dynamic_cast(nodes[3].get()); - BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); -} - BOOST_AUTO_TEST_CASE(redeclare_implemented_abstract_function_as_abstract) { ASTPointer sourceUnit; @@ -738,7 +700,7 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); + BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); } BOOST_AUTO_TEST_CASE(function_canonical_signature) @@ -5714,7 +5676,7 @@ BOOST_AUTO_TEST_CASE(interface_constructor) function I(); } )"; - CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces"); + CHECK_ERROR_ALLOW_MULTI(text, TypeError, "Constructor cannot be defined in interfaces"); } BOOST_AUTO_TEST_CASE(interface_functions) @@ -6134,6 +6096,25 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_variables) CHECK_WARNING(text, "shadows a builtin symbol"); } +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_storage_variables) +{ + char const* text = R"( + contract C { + uint msg; + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtin_at_global_scope) +{ + char const* text = R"( + contract msg { + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + BOOST_AUTO_TEST_CASE(shadowing_builtins_with_parameters) { char const* text = R"( @@ -6190,6 +6171,28 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_constructor) CHECK_SUCCESS_NO_WARNINGS(text); } +BOOST_AUTO_TEST_CASE(function_overload_is_not_shadowing) +{ + char const* text = R"( + contract C { + function f() {} + function f(uint) {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_override_is_not_shadowing) +{ + char const* text = R"( + contract D { function f() {} } + contract C is D { + function f(uint) {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(callable_crash) { char const* text = R"( @@ -6437,7 +6440,7 @@ BOOST_AUTO_TEST_CASE(using_this_in_constructor) CHECK_WARNING(text, "\"this\" used in constructor"); } -BOOST_AUTO_TEST_CASE(do_not_crash_on_not_lalue) +BOOST_AUTO_TEST_CASE(do_not_crash_on_not_lvalue) { // This checks for a bug that caused a crash because of continued analysis. char const* text = R"( @@ -6451,6 +6454,110 @@ BOOST_AUTO_TEST_CASE(do_not_crash_on_not_lalue) CHECK_ERROR_ALLOW_MULTI(text, TypeError, "is not callable"); } +BOOST_AUTO_TEST_CASE(builtin_reject_gas) +{ + char const* text = R"( + contract C { + function f() { + keccak256.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + sha256.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ripemd160.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ecrecover.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); +} + +BOOST_AUTO_TEST_CASE(builtin_reject_value) +{ + char const* text = R"( + contract C { + function f() { + keccak256.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + sha256.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ripemd160.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ecrecover.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); +} + +BOOST_AUTO_TEST_CASE(constructor_without_implementation) +{ + char const* text = R"( + contract C { + function C(); + } + )"; + CHECK_ERROR(text, TypeError, "Constructor must be implemented if declared."); +} + +BOOST_AUTO_TEST_CASE(library_function_without_implementation) +{ + char const* text = R"( + library L { + function f(); + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + library L { + function f() internal; + } + )"; + CHECK_ERROR(text, TypeError, "Internal library function must be implemented if declared."); + text = R"( + library L { + function f() private; + } + )"; + CHECK_ERROR(text, TypeError, "Internal library function must be implemented if declared."); +} + BOOST_AUTO_TEST_SUITE_END() }