diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 784c03d07..1b3cdb0e3 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -25,10 +25,10 @@ #include #include #include +#include #include #include - using namespace std; using namespace solidity; using namespace solidity::langutil; @@ -433,6 +433,25 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra ); hashes.insert(hash); } + + map errorHashes; + // TODO all used errors? + for (ErrorDefinition const* error: _contract.errors()) + { + if (!error->functionType(true)->interfaceFunctionType()) + // Will create an error later on. + continue; + uint32_t hash = selectorFromSignature32(error->functionType(true)->externalSignature()); + if (errorHashes.count(hash)) + m_errorReporter.typeError( + 4883_error, + _contract.location(), + SecondarySourceLocation{}.append("This error has the same selector: ", errorHashes[hash]), + string("Error signature hash collision for ") + error->functionType(true)->externalSignature() + ); + else + errorHashes[hash] = error->location(); + } } void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 25bd37b9f..499c26ff1 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -124,7 +124,7 @@ bool DeclarationContainer::registerDeclaration( // Do not warn about shadowing for structs and enums because their members are // not accessible without prefixes. Also do not warn about event parameters // because they do not participate in any proper scope. - bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventParameter()); + bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventOrErrorParameter()); if (m_enclosingContainer && !special) m_homonymCandidates.emplace_back(*_name, _location ? _location : &_declaration.location()); } diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index 939c3d202..da96709aa 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -368,7 +368,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable) } // Find correct data location. - if (_variable.isEventParameter()) + if (_variable.isEventOrErrorParameter()) { solAssert(varLoc == Location::Unspecified, ""); typeLoc = DataLocation::Memory; diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 9de1d8979..09e95c490 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -157,6 +157,13 @@ bool DocStringAnalyser::visit(EventDefinition const& _event) return true; } +bool DocStringAnalyser::visit(ErrorDefinition const& _error) +{ + handleCallable(_error, _error, _error.annotation()); + + return true; +} + void DocStringAnalyser::handleCallable( CallableDeclaration const& _callable, StructurallyDocumented const& _node, diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index e13cab898..1fa076359 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -43,6 +43,7 @@ private: bool visit(VariableDeclaration const& _variable) override; bool visit(ModifierDefinition const& _modifier) override; bool visit(EventDefinition const& _event) override; + bool visit(ErrorDefinition const& _error) override; CallableDeclaration const* resolveInheritDoc( std::set const& _baseFunctions, diff --git a/libsolidity/analysis/DocStringTagParser.cpp b/libsolidity/analysis/DocStringTagParser.cpp index 02c22e9b8..7f61c0f03 100644 --- a/libsolidity/analysis/DocStringTagParser.cpp +++ b/libsolidity/analysis/DocStringTagParser.cpp @@ -85,6 +85,13 @@ bool DocStringTagParser::visit(EventDefinition const& _event) return true; } +bool DocStringTagParser::visit(ErrorDefinition const& _error) +{ + handleCallable(_error, _error, _error.annotation()); + + return true; +} + void DocStringTagParser::checkParameters( CallableDeclaration const& _callable, StructurallyDocumented const& _node, @@ -127,11 +134,14 @@ void DocStringTagParser::handleCallable( ) { static set const validEventTags = set{"dev", "notice", "return", "param"}; + static set const validErrorTags = set{"dev", "notice", "param"}; static set const validModifierTags = set{"dev", "notice", "param", "inheritdoc"}; static set const validTags = set{"dev", "notice", "return", "param", "inheritdoc"}; if (dynamic_cast(&_callable)) parseDocStrings(_node, _annotation, validEventTags, "events"); + else if (dynamic_cast(&_callable)) + parseDocStrings(_node, _annotation, validErrorTags, "errors"); else if (dynamic_cast(&_callable)) parseDocStrings(_node, _annotation, validModifierTags, "modifiers"); else diff --git a/libsolidity/analysis/DocStringTagParser.h b/libsolidity/analysis/DocStringTagParser.h index 41dcca5df..548de1d42 100644 --- a/libsolidity/analysis/DocStringTagParser.h +++ b/libsolidity/analysis/DocStringTagParser.h @@ -44,6 +44,7 @@ private: bool visit(VariableDeclaration const& _variable) override; bool visit(ModifierDefinition const& _modifier) override; bool visit(EventDefinition const& _event) override; + bool visit(ErrorDefinition const& _error) override; void checkParameters( CallableDeclaration const& _callable, diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 17d5b8c3e..b5aa2ceab 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -83,10 +83,8 @@ inline vector> constructMagicVariable magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)), magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), magicVarDecl("now", TypeProvider::uint256()), - magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), - magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), - magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), - magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("require", TypeProvider::function(strings{}, strings{}, FunctionType::Kind::Require, true, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, true, StateMutability::Pure)), magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index cf2a39746..2a24e274f 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -85,6 +85,11 @@ bool PostTypeChecker::visit(FunctionCall const& _functionCall) return callVisit(_functionCall); } +void PostTypeChecker::endVisit(FunctionCall const& _functionCall) +{ + callEndVisit(_functionCall); +} + bool PostTypeChecker::visit(Identifier const& _identifier) { return callVisit(_identifier); @@ -313,6 +318,67 @@ private: bool m_insideEmitStatement = false; }; +struct ErrorOutsideRequireRevertChecker: public PostTypeChecker::Checker +{ + ErrorOutsideRequireRevertChecker(ErrorReporter& _errorReporter): + Checker(_errorReporter) {} + + bool visit(FunctionCall const& _functionCall) override + { + if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + return true; + auto const* functionType = dynamic_cast(_functionCall.expression().annotation().type); + solAssert(functionType, ""); + switch (functionType->kind()) + { + case FunctionType::Kind::Require: + case FunctionType::Kind::Revert: + { + solAssert(!m_insideRequireRevert, ""); + m_insideRequireRevert = true; + break; + } + case FunctionType::Kind::Error: + { + // This will not catch situations like + // revert(Error1(Error2())), but as long as we + // do not exit the expression context inside "require" and "revert", + // this will be caught by the type checker since errors + // do not have return values. + if (!m_insideRequireRevert) + m_errorReporter.typeError( + 7757_error, + _functionCall.location(), + "Errors can only be created directly inside require or revert calls." + ); + break; + } + default: + break; + } + return true; + } + + void endVisit(FunctionCall const& _functionCall) override + { + if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + return; + auto const* functionType = dynamic_cast(_functionCall.expression().annotation().type); + solAssert(functionType, ""); + if ( + functionType->kind() == FunctionType::Kind::Require || + functionType->kind() == FunctionType::Kind::Revert + ) + { + solAssert(m_insideRequireRevert, ""); + m_insideRequireRevert = false; + } + } + +private: + bool m_insideRequireRevert = false; +}; + struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker { NoVariablesInInterfaceChecker(ErrorReporter& _errorReporter): @@ -370,5 +436,6 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err m_checkers.push_back(make_shared(_errorReporter)); m_checkers.push_back(make_shared(_errorReporter)); m_checkers.push_back(make_shared(_errorReporter)); + m_checkers.push_back(make_shared(_errorReporter)); m_checkers.push_back(make_shared(_errorReporter)); } diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index fd94785e1..332acdbbd 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -81,6 +81,7 @@ private: void endVisit(EmitStatement const& _emit) override; bool visit(FunctionCall const& _functionCall) override; + void endVisit(FunctionCall const& _functionCall) override; bool visit(Identifier const& _identifier) override; bool visit(MemberAccess const& _identifier) override; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index c85791449..e79ce6ac7 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -192,6 +192,17 @@ void SyntaxChecker::endVisit(ModifierDefinition const& _modifier) m_placeholderFound = false; } +bool SyntaxChecker::visit(ErrorDefinition const& _error) +{ + if (_error.name() == "Error" || _error.name() == "Panic") + m_errorReporter.syntaxError( + 1855_error, + _error.location(), + "The built-in errors \"Error\" and \"Panic\" cannot be re-defined." + ); + return true; +} + void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _statement) { auto varDecl = dynamic_cast(&_statement); diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index ca36ed992..c522c8a10 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -40,6 +40,7 @@ namespace solidity::frontend * - issues deprecation warning for throw * - whether the msize instruction is used and the Yul optimizer is enabled at the same time. * - selection of the ABI coder through pragmas. + * - whether user-defined errors are called Error or Panic. */ class SyntaxChecker: private ASTConstVisitor { @@ -61,6 +62,8 @@ private: bool visit(ModifierDefinition const& _modifier) override; void endVisit(ModifierDefinition const& _modifier) override; + bool visit(ErrorDefinition const& _error) override; + /// Reports an error if _statement is a VariableDeclarationStatement. /// Used by if/while/for to check for single statement variable declarations /// without a block. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 23cd3c1de..529f8ece8 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include @@ -681,30 +682,12 @@ void TypeChecker::visitManually( bool TypeChecker::visit(EventDefinition const& _eventDef) { solAssert(_eventDef.visibility() > Visibility::Internal, ""); - unsigned numIndexed = 0; - for (ASTPointer const& var: _eventDef.parameters()) - { - if (var->isIndexed()) - numIndexed++; - if (type(*var)->containsNestedMapping()) - m_errorReporter.typeError( - 3448_error, - var->location(), - "Type containing a (nested) mapping is not allowed as event parameter type." - ); - if (!type(*var)->interfaceType(false)) - m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type."); - if ( - !useABICoderV2() && - !typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */) - ) - m_errorReporter.typeError( - 3061_error, - var->location(), - "This type is only supported in ABI coder v2. " - "Use \"pragma abicoder v2;\" to enable the feature." - ); - } + checkErrorAndEventParameters(_eventDef); + + auto numIndexed = ranges::count_if( + _eventDef.parameters(), + [](ASTPointer const& var) { return var->isIndexed(); } + ); if (_eventDef.isAnonymous() && numIndexed > 4) m_errorReporter.typeError(8598_error, _eventDef.location(), "More than 4 indexed arguments for anonymous event."); else if (!_eventDef.isAnonymous() && numIndexed > 3) @@ -712,6 +695,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return true; } +bool TypeChecker::visit(ErrorDefinition const& _errorDef) +{ + solAssert(_errorDef.visibility() > Visibility::Internal, ""); + checkErrorAndEventParameters(_errorDef); + return true; +} + void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast(*_funType.annotation().type); @@ -2029,6 +2019,85 @@ void TypeChecker::typeCheckABIEncodeFunctions( } } +void TypeChecker::typeCheckRequireRevert(FunctionCall const& _functionCall, FunctionType::Kind _kind) +{ + solAssert(_kind == FunctionType::Kind::Require || _kind == FunctionType::Kind::Revert, ""); + // Check for named arguments + if (!_functionCall.names().empty()) + { + m_errorReporter.typeError( + 1886_error, + _functionCall.location(), + "Named arguments cannot be used for this function call." + ); + return; + } + + bool isRequire = _kind == FunctionType::Kind::Require; + size_t argsExpected = isRequire ? 1 : 0; + string name = isRequire ? "require" : "revert"; + if ( + _functionCall.arguments().size() != argsExpected && + _functionCall.arguments().size() != argsExpected + 1 + ) + { + m_errorReporter.typeError( + 7445_error, + _functionCall.location(), + "Function \"" + + name + + "\" needs " + + to_string(argsExpected) + + " or " + + to_string(argsExpected + 1) + + " arguments, but provided " + + to_string(_functionCall.arguments().size()) + + "." + ); + return; + } + if (isRequire) + { + BoolResult result = type(*_functionCall.arguments().front())->isImplicitlyConvertibleTo(*TypeProvider::boolean()); + if (!result) + m_errorReporter.typeError( + 2956_error, + _functionCall.arguments().front()->location(), + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*_functionCall.arguments().front())->toString() + + " to " + + TypeProvider::boolean()->toString(false) + + " requested." + + (result.message().empty() ? "" : " " + result.message()) + ); + } + + // Event is omitted, nothing more to check. + if (_functionCall.arguments().size() == argsExpected) + return; + + if (type(*_functionCall.arguments().back())->isImplicitlyConvertibleTo(*TypeProvider::stringMemory())) + return; + + Declaration const* declaration = nullptr; + if (auto const* errorCall = dynamic_cast(_functionCall.arguments().back().get())) + if (*errorCall->annotation().kind == FunctionCallKind::FunctionCall) + declaration = referencedDeclaration(errorCall->expression()); + if (!dynamic_cast(declaration)) + { + m_errorReporter.typeError( + 4423_error, + _functionCall.arguments().back()->location(), + "Expected error instance or string." + string( + dynamic_cast(_functionCall.arguments().back().get()) ? + " Did you forget the \"()\" after the error?" : + "" + ) + ); + } +} + void TypeChecker::typeCheckFunctionGeneralChecks( FunctionCall const& _functionCall, FunctionTypePointer _functionType @@ -2248,7 +2317,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks( _functionType->kind() == FunctionType::Kind::DelegateCall || _functionType->kind() == FunctionType::Kind::External || _functionType->kind() == FunctionType::Kind::Creation || - _functionType->kind() == FunctionType::Kind::Event; + _functionType->kind() == FunctionType::Kind::Event || + _functionType->kind() == FunctionType::Kind::Error; if (callRequiresABIEncoding && !useABICoderV2()) { @@ -2431,6 +2501,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) case FunctionType::Kind::MetaType: returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall); break; + case FunctionType::Kind::Require: + case FunctionType::Kind::Revert: + typeCheckRequireRevert(_functionCall, functionType->kind()); + break; default: { typeCheckFunctionCall(_functionCall, functionType); @@ -3392,6 +3466,32 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) ); } +void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable) +{ + string kind = dynamic_cast(&_callable) ? "event" : "error"; + for (ASTPointer const& var: _callable.parameters()) + { + if (type(*var)->containsNestedMapping()) + m_errorReporter.typeError( + 3448_error, + var->location(), + "Type containing a (nested) mapping is not allowed as " + kind + " parameter type." + ); + if (!type(*var)->interfaceType(false)) + m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as " + kind + " parameter type."); + if ( + !useABICoderV2() && + !typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */) + ) + m_errorReporter.typeError( + 3061_error, + var->location(), + "This type is only supported in ABI coder v2. " + "Use \"pragma abicoder v2;\" to enable the feature." + ); + } +} + bool TypeChecker::contractDependenciesAreCyclic( ContractDefinition const& _contract, std::set const& _seenContracts diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index d75bb8a57..500ee1625 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -111,6 +111,11 @@ private: FunctionTypePointer _functionType ); + void typeCheckRequireRevert( + FunctionCall const& _functionCall, + FunctionType::Kind _kind + ); + void endVisit(InheritanceSpecifier const& _inheritance) override; void endVisit(ModifierDefinition const& _modifier) override; bool visit(FunctionDefinition const& _function) override; @@ -119,6 +124,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector const& _bases); bool visit(EventDefinition const& _eventDef) override; + bool visit(ErrorDefinition const& _errorDef) override; void endVisit(FunctionTypeName const& _funType) override; bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(IfStatement const& _ifStatement) override; @@ -147,6 +153,8 @@ private: void endVisit(Literal const& _literal) override; void endVisit(UsingForDirective const& _usingForDirective) override; + void checkErrorAndEventParameters(CallableDeclaration const& _callable); + bool contractDependenciesAreCyclic( ContractDefinition const& _contract, std::set const& _seenContracts = std::set() diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 61f038abd..106214d36 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -450,6 +450,24 @@ EventDefinitionAnnotation& EventDefinition::annotation() const return initAnnotation(); } +TypePointer ErrorDefinition::type() const +{ + return TypeProvider::function(*this); +} + +FunctionTypePointer ErrorDefinition::functionType(bool _internal) const +{ + if (_internal) + return TypeProvider::function(*this); + else + return nullptr; +} + +ErrorDefinitionAnnotation& ErrorDefinition::annotation() const +{ + return initAnnotation(); +} + SourceUnit const& Scopable::sourceUnit() const { ASTNode const* s = scope(); @@ -492,10 +510,10 @@ bool Declaration::isStructMember() const return dynamic_cast(scope()); } -bool Declaration::isEventParameter() const +bool Declaration::isEventOrErrorParameter() const { solAssert(scope(), ""); - return dynamic_cast(scope()); + return dynamic_cast(scope()) || dynamic_cast(scope()); } DeclarationAnnotation& Declaration::annotation() const @@ -641,7 +659,7 @@ set VariableDeclaration::allowedDataLocations() c { using Location = VariableDeclaration::Location; - if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter()) + if (!hasReferenceOrMappingType() || isStateVariable() || isEventOrErrorParameter()) return set{ Location::Unspecified }; else if (isCallableOrCatchParameter()) { diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index ab5041049..d19666b43 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -268,7 +268,7 @@ public: /// @returns true if this is a declaration of a struct member. bool isStructMember() const; /// @returns true if this is a declaration of a parameter of an event. - bool isEventParameter() const; + bool isEventOrErrorParameter() const; /// @returns the type of expressions referencing this declaration. /// This can only be called once types of variable declarations have already been resolved. @@ -509,6 +509,7 @@ public: std::vector definedFunctions() const { return filteredNodes(m_subNodes); } std::vector events() const { return filteredNodes(m_subNodes); } std::vector const& interfaceEvents() const; + std::vector errors() const { return filteredNodes(m_subNodes); } bool isInterface() const { return m_contractKind == ContractKind::Interface; } bool isLibrary() const { return m_contractKind == ContractKind::Library; } @@ -736,7 +737,7 @@ private: /** * Base class for all nodes that define function-like objects, i.e. FunctionDefinition, - * EventDefinition and ModifierDefinition. + * EventDefinition, ErrorDefinition and ModifierDefinition. */ class CallableDeclaration: public Declaration, public VariableScope { @@ -1155,6 +1156,47 @@ private: bool m_anonymous = false; }; +/** + * Definition of an error type usable in ``revert(MyError(x))``, ``require(condition, MyError(x))`` + * and ``catch MyError(_x)``. + */ +class ErrorDefinition: public CallableDeclaration, public StructurallyDocumented, public ScopeOpener +{ +public: + ErrorDefinition( + int64_t _id, + SourceLocation const& _location, + ASTPointer const& _name, + SourceLocation _nameLocation, + ASTPointer const& _documentation, + ASTPointer const& _parameters + ): + CallableDeclaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default, _parameters), + StructurallyDocumented(_documentation) + { + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + TypePointer type() const override; + + FunctionTypePointer functionType(bool _internal) const override; + + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return true; } + + ErrorDefinitionAnnotation& annotation() const override; + + CallableDeclaration const& resolveVirtual( + ContractDefinition const&, + ContractDefinition const* + ) const override + { + return *this; + } +}; + /** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global * functions when such an identifier is encountered. Will never have a valid location in the source code diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index e6ef18c3a..bfa610c8e 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -178,6 +178,11 @@ struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDoc { }; +struct ErrorDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation +{ +}; + + struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation { }; diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 80d37d399..d413c9e71 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -57,6 +57,7 @@ class VariableDeclaration; class ModifierDefinition; class ModifierInvocation; class EventDefinition; +class ErrorDefinition; class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index ce9ec4eee..05101e086 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -473,6 +473,18 @@ bool ASTJsonConverter::visit(EventDefinition const& _node) return false; } +bool ASTJsonConverter::visit(ErrorDefinition const& _node) +{ + m_inEvent = true; + setJsonNode(_node, "ErrorDefinition", { + make_pair("name", _node.name()), + make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + make_pair("parameters", toJson(_node.parameterList())) + }); + return false; +} + bool ASTJsonConverter::visit(ElementaryTypeName const& _node) { std::vector> attributes = { diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 699113ea8..cdd674da6 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -88,6 +88,7 @@ public: bool visit(ModifierDefinition const& _node) override; bool visit(ModifierInvocation const& _node) override; bool visit(EventDefinition const& _node) override; + bool visit(ErrorDefinition const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(FunctionTypeName const& _node) override; diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 6bf5910f2..6e7ac1f24 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -151,6 +151,8 @@ ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js return createModifierInvocation(_json); if (nodeType == "EventDefinition") return createEventDefinition(_json); + if (nodeType == "ErrorDefinition") + return createErrorDefinition(_json); if (nodeType == "ElementaryTypeName") return createElementaryTypeName(_json); if (nodeType == "UserDefinedTypeName") @@ -536,6 +538,17 @@ ASTPointer ASTJsonImporter::createEventDefinition(Json::Value c ); } +ASTPointer ASTJsonImporter::createErrorDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + createNameSourceLocation(_node), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), + createParameterList(member(_node, "parameters")) + ); +} + ASTPointer ASTJsonImporter::createElementaryTypeName(Json::Value const& _node) { unsigned short firstNum; diff --git a/libsolidity/ast/ASTJsonImporter.h b/libsolidity/ast/ASTJsonImporter.h index c4f8c6cc9..3ef6db384 100644 --- a/libsolidity/ast/ASTJsonImporter.h +++ b/libsolidity/ast/ASTJsonImporter.h @@ -88,6 +88,7 @@ private: ASTPointer createModifierDefinition(Json::Value const& _node); ASTPointer createModifierInvocation(Json::Value const& _node); ASTPointer createEventDefinition(Json::Value const& _node); + ASTPointer createErrorDefinition(Json::Value const& _node); ASTPointer createElementaryTypeName(Json::Value const& _node); ASTPointer createUserDefinedTypeName(Json::Value const& _node); ASTPointer createFunctionTypeName(Json::Value const& _node); diff --git a/libsolidity/ast/ASTUtils.cpp b/libsolidity/ast/ASTUtils.cpp index 2f7138249..0e700fb6a 100644 --- a/libsolidity/ast/ASTUtils.cpp +++ b/libsolidity/ast/ASTUtils.cpp @@ -67,4 +67,14 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons return rootDecl; } +Declaration const* referencedDeclaration(Expression const& _expression) +{ + if (auto const* memberAccess = dynamic_cast(&_expression)) + return memberAccess->annotation().referencedDeclaration; + else if (auto const* identifier = dynamic_cast(&_expression)) + return identifier->annotation().referencedDeclaration; + else + return nullptr; +} + } diff --git a/libsolidity/ast/ASTUtils.h b/libsolidity/ast/ASTUtils.h index 13626a0cf..c5f550a44 100644 --- a/libsolidity/ast/ASTUtils.h +++ b/libsolidity/ast/ASTUtils.h @@ -22,6 +22,8 @@ namespace solidity::frontend { class VariableDeclaration; +class Declaration; +class Expression; /// Find the topmost referenced constant variable declaration when the given variable /// declaration value is an identifier. Works only for constant variable declarations. @@ -31,4 +33,8 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons /// Returns true if the constant variable declaration is recursive. bool isConstantVariableRecursive(VariableDeclaration const& _varDecl); +/// @returns the declaration referenced from the expression which has to be MemberAccess +/// or Identifier. Returns nullptr otherwise. +Declaration const* referencedDeclaration(Expression const& _expression); + } diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 9ad652b06..4acb47273 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -71,6 +71,7 @@ public: virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); } virtual bool visit(ModifierInvocation& _node) { return visitNode(_node); } virtual bool visit(EventDefinition& _node) { return visitNode(_node); } + virtual bool visit(ErrorDefinition& _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); } @@ -124,6 +125,7 @@ public: virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); } virtual void endVisit(ModifierInvocation& _node) { endVisitNode(_node); } virtual void endVisit(EventDefinition& _node) { endVisitNode(_node); } + virtual void endVisit(ErrorDefinition& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); } @@ -199,6 +201,7 @@ public: virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); } virtual bool visit(ModifierInvocation const& _node) { return visitNode(_node); } virtual bool visit(EventDefinition const& _node) { return visitNode(_node); } + virtual bool visit(ErrorDefinition 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); } @@ -252,6 +255,7 @@ public: virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(ModifierInvocation const& _node) { endVisitNode(_node); } virtual void endVisit(EventDefinition const& _node) { endVisitNode(_node); } + virtual void endVisit(ErrorDefinition 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); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index afd9838a1..678a9ef7e 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -366,6 +366,28 @@ void EventDefinition::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void ErrorDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (m_documentation) + m_documentation->accept(_visitor); + m_parameters->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ErrorDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (m_documentation) + m_documentation->accept(_visitor); + m_parameters->accept(_visitor); + } + _visitor.endVisit(*this); +} + void ElementaryTypeName::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index cf81574a7..08900e6a1 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -431,6 +431,11 @@ FunctionType const* TypeProvider::function(EventDefinition const& _def) return createAndGet(_def); } +FunctionType const* TypeProvider::function(ErrorDefinition const& _def) +{ + return createAndGet(_def); +} + FunctionType const* TypeProvider::function(FunctionTypeName const& _typeName) { return createAndGet(_typeName); diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index 911eb7b31..510b16324 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -139,6 +139,8 @@ public: /// @returns the function type of an event. static FunctionType const* function(EventDefinition const& _event); + static FunctionType const* function(ErrorDefinition const& _error); + /// @returns the type of a function type name. static FunctionType const* function(FunctionTypeName const& _typeName); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 182b2b72b..a5470127e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2729,13 +2729,34 @@ FunctionType::FunctionType(EventDefinition const& _event): } solAssert( - m_parameterNames.size() == m_parameterTypes.size(), - "Parameter names list must match parameter types list!" - ); + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); solAssert( - m_returnParameterNames.size() == m_returnParameterTypes.size(), - "Return parameter names list must match return parameter types list!" - ); + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); +} + +FunctionType::FunctionType(ErrorDefinition const& _error): + m_kind(Kind::Error), + m_stateMutability(StateMutability::Pure), + m_declaration(&_error) +{ + for (ASTPointer const& var: _error.parameters()) + { + m_parameterNames.push_back(var->name()); + m_parameterTypes.push_back(var->annotation().type); + } + + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + solAssert( + m_returnParameterNames.empty() && m_returnParameterTypes.empty(), + "Return parameter names list must match return parameter types list!" + ); } FunctionType::FunctionType(FunctionTypeName const& _typeName): @@ -2863,6 +2884,7 @@ string FunctionType::richIdentifier() const case Kind::RIPEMD160: id += "ripemd160"; break; case Kind::GasLeft: id += "gasleft"; break; case Kind::Event: id += "event"; break; + case Kind::Error: id += "error"; break; case Kind::SetGas: id += "setgas"; break; case Kind::SetValue: id += "setvalue"; break; case Kind::BlockHash: id += "blockhash"; break; @@ -3100,7 +3122,7 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const // Note that m_declaration might also be a state variable! solAssert(m_declaration, "Declaration needed to determine interface function type."); bool isLibraryFunction = false; - if (kind() != Kind::Event) + if (kind() != Kind::Event && kind() != Kind::Error) if (auto const* contract = dynamic_cast(m_declaration->scope())) isLibraryFunction = contract->isLibrary(); @@ -3389,15 +3411,16 @@ string FunctionType::externalSignature() const case Kind::External: case Kind::DelegateCall: case Kind::Event: + case Kind::Error: case Kind::Declaration: break; default: solAssert(false, "Invalid function type for requesting external signature."); } - // "inLibrary" is only relevant if this is not an event. + // "inLibrary" is only relevant if this is neither an event nor an error. bool inLibrary = false; - if (kind() != Kind::Event) + if (kind() != Kind::Event && kind() != Kind::Error) if (auto const* contract = dynamic_cast(m_declaration->scope())) inLibrary = contract->isLibrary(); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 0414ea783..0e3025266 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1149,6 +1149,7 @@ public: SHA256, ///< CALL to special contract for sha256 RIPEMD160, ///< CALL to special contract for ripemd160 Event, ///< syntactic sugar for LOG* + Error, ///< creating an error instance in revert or require SetGas, ///< modify the default gas value for the function call SetValue, ///< modify the default value transfer for the function call BlockHash, ///< BLOCKHASH @@ -1180,6 +1181,7 @@ public: explicit FunctionType(VariableDeclaration const& _varDecl); /// Creates the function type of an event. explicit FunctionType(EventDefinition const& _event); + explicit FunctionType(ErrorDefinition const& _error); /// 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). diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 4a3b7c7b4..383455d25 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -792,7 +792,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { // function-sel(Error(string)) + encoding solAssert(arguments.size() == 1, ""); - solAssert(function.parameterTypes().size() == 1, ""); + solUnimplementedAssert(arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), ""); if (m_context.revertStrings() == RevertStrings::Strip) { if (!*arguments.front()->annotation().isPure) @@ -908,6 +908,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << logInstruction(numIndexed); break; } + case FunctionType::Kind::Error: + { + solAssert(false, ""); + } case FunctionType::Kind::BlockHash: { acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true); @@ -1076,7 +1080,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Assert: case FunctionType::Kind::Require: { - acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); + acceptAndConvert(*arguments.front(), *TypeProvider::boolean(), false); bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip; @@ -1087,6 +1091,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function call. solAssert(arguments.size() == 2, ""); solAssert(function.kind() == FunctionType::Kind::Require, ""); + solUnimplementedAssert(arguments.back()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), ""); if (m_context.revertStrings() == RevertStrings::Strip) { if (!*arguments.at(1)->annotation().isPure) @@ -1381,6 +1386,11 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "event not found"); // no-op, because the parent node will do the job break; + case FunctionType::Kind::Error: + if (!dynamic_cast(_memberAccess.annotation().referencedDeclaration)) + solAssert(false, "error not found"); + // no-op, because the parent node will do the job + break; case FunctionType::Kind::DelegateCall: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); @@ -2052,6 +2062,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) { // no-op } + else if (dynamic_cast(declaration)) + { + // no-op + } else if (dynamic_cast(declaration)) { // no-op diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index be8942f65..cec8617ec 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1041,11 +1041,20 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) m_code << templ.render(); break; } + case FunctionType::Kind::Error: + { + solAssert(false, ""); + } case FunctionType::Kind::Assert: case FunctionType::Kind::Require: { solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert"); solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert"); + if (arguments.size() == 2) + solAssert(functionType->kind() == FunctionType::Kind::Require, ""); + + if (arguments.size() == 2) + solUnimplementedAssert(arguments.back()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), ""); Type const* messageArgumentType = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip ? @@ -1192,12 +1201,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } case FunctionType::Kind::Revert: { - solAssert(arguments.size() == parameterTypes.size(), ""); if (arguments.empty()) m_code << "revert(0, 0)\n"; else { solAssert(arguments.size() == 1, ""); + solUnimplementedAssert(arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), ""); if (m_context.revertStrings() == RevertStrings::Strip) m_code << "revert(0, 0)\n"; @@ -1945,7 +1954,14 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) dynamic_cast(_memberAccess.annotation().referencedDeclaration), "Event not found" ); - // the call will do the resolving + // the call will do the resolving + break; + case FunctionType::Kind::Error: + solAssert( + dynamic_cast(_memberAccess.annotation().referencedDeclaration), + "Error not found" + ); + // the call will do the resolving break; case FunctionType::Kind::DelegateCall: define(IRVariable(_memberAccess).part("address"), _memberAccess.expression()); @@ -2275,6 +2291,11 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) { // no-op } + else if (dynamic_cast(declaration)) + { + // TODO should this rather be an assertion? Can we get here? + // no-op + } else if (dynamic_cast(declaration)) { // no-op diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index e0c4c2cc1..749914e41 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -725,6 +725,8 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) arrayPop(_funCall); break; case FunctionType::Kind::Event: + case FunctionType::Kind::Error: + // TODO but are side-effects of arguments taken into account? // This can be safely ignored. break; case FunctionType::Kind::ObjectCreation: diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 623060f0b..c3293118c 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -112,8 +112,16 @@ ASTPointer Parser::parse(shared_ptr const& _scanner) nodes.push_back(parseFunctionDefinition(true)); break; default: + if ( + // Workaround because `error` is not a keyword. + m_scanner->currentToken() == Token::Identifier && + currentLiteral() == "error" && + m_scanner->peekNextToken() == Token::Identifier && + m_scanner->peekNextNextToken() == Token::LParen + ) + nodes.push_back(parseErrorDefinition()); // Constant variable. - if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS) + else if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS) { VarDeclParserOptions options; options.kind = VarDeclKind::FileLevel; @@ -351,6 +359,14 @@ ASTPointer Parser::parseContractDefinition() subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) subNodes.push_back(parseEnumDefinition()); + else if ( + // Workaround because `error` is not a keyword. + currentTokenValue == Token::Identifier && + currentLiteral() == "error" && + m_scanner->peekNextToken() == Token::Identifier && + m_scanner->peekNextNextToken() == Token::LParen + ) + subNodes.push_back(parseErrorDefinition()); else if (variableDeclarationStart()) { VarDeclParserOptions options; @@ -917,6 +933,21 @@ ASTPointer Parser::parseEventDefinition() return nodeFactory.createNode(name, nameLocation, documentation, parameters, anonymous); } +ASTPointer Parser::parseErrorDefinition() +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + ASTPointer documentation = parseStructuredDocumentation(); + + solAssert(*expectIdentifierToken() == "error", ""); + auto&& [name, nameLocation] = expectIdentifierWithLocation(); + + ASTPointer parameters = parseParameterList({}); + nodeFactory.markEndPosition(); + expectToken(Token::Semicolon); + return nodeFactory.createNode(name, documentation, parameters); +} + ASTPointer Parser::parseUsingDirective() { RecursionGuard recursionGuard(*this); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 77152d38f..109c2b614 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -102,6 +102,7 @@ private: ); ASTPointer parseModifierDefinition(); ASTPointer parseEventDefinition(); + ASTPointer parseErrorDefinition(); ASTPointer parseUsingDirective(); ASTPointer parseModifierInvocation(); ASTPointer parseIdentifier(); diff --git a/test/libsolidity/syntaxTests/errors/basic.sol b/test/libsolidity/syntaxTests/errors/basic.sol new file mode 100644 index 000000000..75ac3cf5e --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/basic.sol @@ -0,0 +1,6 @@ +contract C { + error MyError(); + error MyError2(uint x); + error MyError3(uint x, bytes); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/basic_usage.sol b/test/libsolidity/syntaxTests/errors/basic_usage.sol new file mode 100644 index 000000000..3f8cd5aa1 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/basic_usage.sol @@ -0,0 +1,9 @@ +error E(); +function f() pure { + revert(E()); +} +function g() pure { + bool x; + require(x, E()); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/basic_usage_params.sol b/test/libsolidity/syntaxTests/errors/basic_usage_params.sol new file mode 100644 index 000000000..a8585e2fa --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/basic_usage_params.sol @@ -0,0 +1,9 @@ +error E(uint,string); +function f() pure { + revert(E(2, "abc")); +} +function g(string storage s) pure { + bool x; + require(x, E(7, s)); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/basic_usage_parethesized.sol b/test/libsolidity/syntaxTests/errors/basic_usage_parethesized.sol new file mode 100644 index 000000000..f8bd7e698 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/basic_usage_parethesized.sol @@ -0,0 +1,13 @@ +error E(); +function f() pure { + revert((E())); +} +function g() pure { + bool x; + require(x, (E())); +} +// ---- +// TypeError 6473: (43-46): Tuple component cannot be empty. +// TypeError 4423: (42-47): Expected error or string. +// TypeError 6473: (100-103): Tuple component cannot be empty. +// TypeError 4423: (99-104): Expected error or string. diff --git a/test/libsolidity/syntaxTests/errors/clash_function_error.sol b/test/libsolidity/syntaxTests/errors/clash_function_error.sol new file mode 100644 index 000000000..0e3a97f7f --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/clash_function_error.sol @@ -0,0 +1,4 @@ +function Err() pure {} +error Err(); +// ---- +// DeclarationError 2333: (23-35): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/errors/clash_function_error_inheritance.sol b/test/libsolidity/syntaxTests/errors/clash_function_error_inheritance.sol new file mode 100644 index 000000000..3b71fb4d6 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/clash_function_error_inheritance.sol @@ -0,0 +1,4 @@ +contract A { function Err() public pure {} } +contract B is A { error Err(); } +// ---- +// DeclarationError 9097: (63-75): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/errors/error_reserved_name.sol b/test/libsolidity/syntaxTests/errors/error_reserved_name.sol new file mode 100644 index 000000000..8d69460d7 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/error_reserved_name.sol @@ -0,0 +1,3 @@ +error Error(uint); +// ---- +// SyntaxError 1855: (44-62): The built-in errors "Error" and "Panic" cannot be re-defined. diff --git a/test/libsolidity/syntaxTests/errors/file_level.sol b/test/libsolidity/syntaxTests/errors/file_level.sol new file mode 100644 index 000000000..07afb90cc --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/file_level.sol @@ -0,0 +1,6 @@ +error MyError(); +error MyError2(uint x); +contract C { + error MyError3(uint x, bytes); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/name_parenthesized.sol b/test/libsolidity/syntaxTests/errors/name_parenthesized.sol new file mode 100644 index 000000000..58034e7f6 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/name_parenthesized.sol @@ -0,0 +1,6 @@ +error E(); +function f() pure { + revert((E)()); +} +// ---- +// TypeError 4423: (42-47): Expected error or string. diff --git a/test/libsolidity/syntaxTests/errors/named_error.sol b/test/libsolidity/syntaxTests/errors/named_error.sol new file mode 100644 index 000000000..750705386 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/named_error.sol @@ -0,0 +1,5 @@ +error E(uint a); +function f() pure { + revert(E({a: 2})); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/named_require.sol b/test/libsolidity/syntaxTests/errors/named_require.sol new file mode 100644 index 000000000..94ae41d6d --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/named_require.sol @@ -0,0 +1,6 @@ +error E(); +function f() pure { + require({error: E()}); +} +// ---- +// TypeError 1886: (35-56): Named arguments cannot be used for this function call. diff --git a/test/libsolidity/syntaxTests/errors/named_revert.sol b/test/libsolidity/syntaxTests/errors/named_revert.sol new file mode 100644 index 000000000..51dff3bc9 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/named_revert.sol @@ -0,0 +1,6 @@ +error E(); +function f() pure { + revert({error: E()}); +} +// ---- +// TypeError 1886: (35-55): Named arguments cannot be used for this function call. diff --git a/test/libsolidity/syntaxTests/errors/nested_invocation.sol b/test/libsolidity/syntaxTests/errors/nested_invocation.sol new file mode 100644 index 000000000..3d3c83e1f --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/nested_invocation.sol @@ -0,0 +1,7 @@ +error E1(); +error E2(); +function f() pure { + revert(E1(E2)); +} +// ---- +// TypeError 6160: (55-61): Wrong argument count for function call: 1 arguments given but expected 0. diff --git a/test/libsolidity/syntaxTests/errors/nested_invocation2.sol b/test/libsolidity/syntaxTests/errors/nested_invocation2.sol new file mode 100644 index 000000000..d966bf1ce --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/nested_invocation2.sol @@ -0,0 +1,7 @@ +error E1(uint); +error E2(); +function f() pure { + revert(E1(E2)); +} +// ---- +// TypeError 9553: (62-64): Invalid type for argument in function call. Invalid implicit conversion from function () pure to uint256 requested. diff --git a/test/libsolidity/syntaxTests/errors/nested_invocation3.sol b/test/libsolidity/syntaxTests/errors/nested_invocation3.sol new file mode 100644 index 000000000..d966bf1ce --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/nested_invocation3.sol @@ -0,0 +1,7 @@ +error E1(uint); +error E2(); +function f() pure { + revert(E1(E2)); +} +// ---- +// TypeError 9553: (62-64): Invalid type for argument in function call. Invalid implicit conversion from function () pure to uint256 requested. diff --git a/test/libsolidity/syntaxTests/errors/nested_invocation4.sol b/test/libsolidity/syntaxTests/errors/nested_invocation4.sol new file mode 100644 index 000000000..cff38aac9 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/nested_invocation4.sol @@ -0,0 +1,7 @@ +error E1(uint); +error E2(); +function f() pure { + revert(E1([E2()])); +} +// ---- +// TypeError 5604: (63-67): Array component cannot be empty. diff --git a/test/libsolidity/syntaxTests/errors/no_mappings.sol b/test/libsolidity/syntaxTests/errors/no_mappings.sol new file mode 100644 index 000000000..9a8c9e509 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/no_mappings.sol @@ -0,0 +1,9 @@ +error MyError(mapping(uint => uint)); +contract C { + error MyError2(mapping(uint => uint)); +} +// ---- +// TypeError 3448: (14-35): Type containing a (nested) mapping is not allowed as error parameter type. +// TypeError 3417: (14-35): Internal or recursive type is not allowed as error parameter type. +// TypeError 3448: (70-91): Type containing a (nested) mapping is not allowed as error parameter type. +// TypeError 3417: (70-91): Internal or recursive type is not allowed as error parameter type. diff --git a/test/libsolidity/syntaxTests/errors/no_overloading.sol b/test/libsolidity/syntaxTests/errors/no_overloading.sol new file mode 100644 index 000000000..53ba01743 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/no_overloading.sol @@ -0,0 +1,4 @@ +error Err(uint); +error Err(bytes32); +// ---- +// DeclarationError 2333: (17-36): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/errors/no_overloading_inheritance.sol b/test/libsolidity/syntaxTests/errors/no_overloading_inheritance.sol new file mode 100644 index 000000000..fac0da4bc --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/no_overloading_inheritance.sol @@ -0,0 +1,8 @@ +contract A { + error Err(uint); +} +contract B is A { + error Err(bytes32); +} +// ---- +// DeclarationError 9097: (58-77): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/errors/no_structs_in_abiv1.sol b/test/libsolidity/syntaxTests/errors/no_structs_in_abiv1.sol new file mode 100644 index 000000000..8727a2852 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/no_structs_in_abiv1.sol @@ -0,0 +1,7 @@ +pragma abicoder v1; +struct S {uint a;} +contract C { + error MyError(S); +} +// ---- +// TypeError 3061: (70-71): This type is only supported in ABI coder v2. Use "pragma abicoder v2;" to enable the feature. diff --git a/test/libsolidity/syntaxTests/errors/panic_reserved_name.sol b/test/libsolidity/syntaxTests/errors/panic_reserved_name.sol new file mode 100644 index 000000000..f269c931a --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/panic_reserved_name.sol @@ -0,0 +1,5 @@ +// TODO: What if an error is imported and alias as Panic? +// TODO I Think the best way would be to have Error in the global scope. +error Panic(bytes2); +// ---- +// SyntaxError 1855: (131-151): The built-in errors "Error" and "Panic" cannot be re-defined. diff --git a/test/libsolidity/syntaxTests/errors/struct_named_error.sol b/test/libsolidity/syntaxTests/errors/struct_named_error.sol new file mode 100644 index 000000000..0ee82ead6 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/struct_named_error.sol @@ -0,0 +1,6 @@ +// Test that the parser workaround is not breaking. +struct error {uint a;} +contract C { + error x; +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/usage_standalone.sol b/test/libsolidity/syntaxTests/errors/usage_standalone.sol new file mode 100644 index 000000000..76794a478 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/usage_standalone.sol @@ -0,0 +1,6 @@ +error E(); +function f() pure { + E(); +} +// ---- +// TypeError 7757: (35-38): Errors can only be created directly inside require or revert calls. diff --git a/test/libsolidity/syntaxTests/errors/usage_standalone_no_call.sol b/test/libsolidity/syntaxTests/errors/usage_standalone_no_call.sol new file mode 100644 index 000000000..78f4616a4 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/usage_standalone_no_call.sol @@ -0,0 +1,7 @@ +error E(); +function f() pure { + // TODO is it a problem that we do not get an error here? + // we should at least get "statement has no effect" + E; +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/usage_with_options.sol b/test/libsolidity/syntaxTests/errors/usage_with_options.sol new file mode 100644 index 000000000..8a6e02311 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/usage_with_options.sol @@ -0,0 +1,6 @@ +error E1(); +function f() pure { + revert(E1{gas: 10}()); +} +// ---- +// TypeError 2193: (43-54): Function call options can only be set on external function calls or contract creations. diff --git a/test/libsolidity/syntaxTests/errors/use_without_parentheses.sol b/test/libsolidity/syntaxTests/errors/use_without_parentheses.sol new file mode 100644 index 000000000..787257cde --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/use_without_parentheses.sol @@ -0,0 +1,7 @@ +error E1(); +error E2(); +function f() pure { + revert(E1); +} +// ---- +// TypeError 4423: (55-57): Expected error or string. diff --git a/test/libsolidity/syntaxTests/errors/using_structs.sol b/test/libsolidity/syntaxTests/errors/using_structs.sol new file mode 100644 index 000000000..282fce35d --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/using_structs.sol @@ -0,0 +1,6 @@ +struct S {uint a;} +contract C { + error MyError(S); + error MyError2(S t); +} +// ---- diff --git a/test/libsolidity/syntaxTests/errors/wrong_arguments.sol b/test/libsolidity/syntaxTests/errors/wrong_arguments.sol new file mode 100644 index 000000000..45cd56168 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/wrong_arguments.sol @@ -0,0 +1,6 @@ +error E(uint,string); +function f() pure { + revert(E(2, 3)); +} +// ---- +// TypeError 9553: (58-59): Invalid type for argument in function call. Invalid implicit conversion from int_const 3 to string memory requested. diff --git a/test/libsolidity/syntaxTests/errors/wrong_number_of_arguments.sol b/test/libsolidity/syntaxTests/errors/wrong_number_of_arguments.sol new file mode 100644 index 000000000..7fccbbf24 --- /dev/null +++ b/test/libsolidity/syntaxTests/errors/wrong_number_of_arguments.sol @@ -0,0 +1,10 @@ +error E(); +function f() pure { + require(); + require(true, E(), 8); + revert(E(), 3); +} +// ---- +// TypeError 7445: (35-44): Function "require" needs 1 or 2 arguments, but provided 0. +// TypeError 7445: (50-71): Function "require" needs 1 or 2 arguments, but provided 3. +// TypeError 7445: (77-91): Function "revert" needs 0 or 1 arguments, but provided 2. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/428_bare_revert.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/428_bare_revert.sol index 9cd58e3da..c9d95d21f 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/428_bare_revert.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/428_bare_revert.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// TypeError 2144: (81-87): No matching declaration found after variable lookup. +// Warning 6133: (81-87): Statement has no effect. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/432_bare_require.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/432_bare_require.sol index a67f5180f..7be86bba7 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/432_bare_require.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/432_bare_require.sol @@ -3,4 +3,4 @@ contract C { function f() pure public { require; } } // ---- -// TypeError 2144: (101-108): No matching declaration found after variable lookup. +// Warning 6133: (101-108): Statement has no effect. diff --git a/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol index fac7ec57a..08af132c5 100644 --- a/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol +++ b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol @@ -26,4 +26,4 @@ contract C { // TypeError 5547: (401-404): Empty tuple on the left hand side. // TypeError 4289: (399-466): Compound assignment is not allowed for tuple types. // TypeError 7407: (410-466): Type bytes32 is not implicitly convertible to expected type tuple(). -// TypeError 9322: (389-396): No matching declaration found after argument-dependent lookup. +// TypeError 2956: (399-466): Invalid type for argument in function call. Invalid implicit conversion from tuple() to bool requested.