Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax for defining custom errors. #10987

Merged
merged 1 commit into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions docs/grammar/Solidity.g4
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ sourceUnit: (
| constantVariableDeclaration
| structDefinition
| enumDefinition
| errorDefinition
)* EOF;

//@doc: inline
Expand Down Expand Up @@ -90,6 +91,7 @@ contractBodyElement:
| enumDefinition
| stateVariableDeclaration
| eventDefinition
| errorDefinition
| usingDirective;
//@doc:inline
namedArgument: name=identifier Colon value=expression;
Expand Down Expand Up @@ -289,6 +291,18 @@ eventDefinition:
Anonymous?
Semicolon;

/**
* Parameter of an error.
*/
errorParameter: type=typeName name=identifier?;
/**
* Definition of an error.
*/
errorDefinition:
Error name=identifier
LParen (parameters+=errorParameter (Comma parameters+=errorParameter)*)? RParen
Semicolon;

/**
* Using directive to bind library functions to types.
* Can occur within contracts and libraries.
Expand Down Expand Up @@ -365,9 +379,9 @@ tupleExpression: LParen (expression? ( Comma expression?)* ) RParen;
inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;

/**
* Besides regular non-keyword Identifiers, the 'from' keyword can also occur as identifier outside of import statements.
* Besides regular non-keyword Identifiers, some keywords like 'from' and 'error' can also be used as identifiers.
*/
identifier: Identifier | From;
identifier: Identifier | From | Error;

literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
booleanLiteral: True | False;
Expand Down
3 changes: 2 additions & 1 deletion docs/grammar/SolidityLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ Do: 'do';
Else: 'else';
Emit: 'emit';
Enum: 'enum';
Error: 'error'; // not a real keyword
Event: 'event';
External: 'external';
Fallback: 'fallback';
False: 'false';
Fixed: 'fixed' | ('fixed' [1-9][0-9]* 'x' [1-9][0-9]*);
From: 'from';
From: 'from'; // not a real keyword
/**
* Bytes types of fixed length.
*/
Expand Down
20 changes: 19 additions & 1 deletion libsolidity/analysis/ContractLevelChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolutil/FunctionSelector.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>


using namespace std;
using namespace solidity;
using namespace solidity::langutil;
Expand Down Expand Up @@ -433,6 +433,24 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra
);
hashes.insert(hash);
}

map<uint32_t, SourceLocation> errorHashes;
for (ErrorDefinition const* error: _contract.interfaceErrors())
{
if (!error->functionType(true)->interfaceFunctionType())
// This will result in an error later on, so we can ignore it here.
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: "s, errorHashes[hash]),
"Error signature hash collision for " + error->functionType(true)->externalSignature()
);
else
errorHashes[hash] = error->location();
}
}

void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract)
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/analysis/DeclarationContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/analysis/DeclarationTypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions libsolidity/analysis/DocStringAnalyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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,
Expand Down
1 change: 1 addition & 0 deletions libsolidity/analysis/DocStringAnalyser.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class DocStringAnalyser: private ASTConstVisitor
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<CallableDeclaration const*> const& _baseFunctions,
Expand Down
10 changes: 10 additions & 0 deletions libsolidity/analysis/DocStringTagParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,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,
Expand Down Expand Up @@ -134,11 +141,14 @@ void DocStringTagParser::handleCallable(
)
{
static set<string> const validEventTags = set<string>{"dev", "notice", "return", "param"};
static set<string> const validErrorTags = set<string>{"dev", "notice", "param"};
cameel marked this conversation as resolved.
Show resolved Hide resolved
static set<string> const validModifierTags = set<string>{"dev", "notice", "param", "inheritdoc"};
static set<string> const validTags = set<string>{"dev", "notice", "return", "param", "inheritdoc"};

if (dynamic_cast<EventDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validEventTags, "events");
else if (dynamic_cast<ErrorDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validErrorTags, "errors");
else if (dynamic_cast<ModifierDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validModifierTags, "modifiers");
else
Expand Down
1 change: 1 addition & 0 deletions libsolidity/analysis/DocStringTagParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class DocStringTagParser: private ASTConstVisitor
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,
Expand Down
34 changes: 34 additions & 0 deletions libsolidity/analysis/PostTypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/FunctionSelector.h>

#include <boost/range/adaptor/map.hpp>
#include <memory>
Expand Down Expand Up @@ -70,6 +71,11 @@ void PostTypeChecker::endVisit(VariableDeclaration const& _variable)
callEndVisit(_variable);
}

void PostTypeChecker::endVisit(ErrorDefinition const& _error)
{
callEndVisit(_error);
}

bool PostTypeChecker::visit(EmitStatement const& _emit)
{
return callVisit(_emit);
Expand Down Expand Up @@ -362,6 +368,33 @@ struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
/// Flag indicating whether we are currently inside a StructDefinition.
int m_insideStruct = 0;
};

struct ReservedErrorSelector: public PostTypeChecker::Checker
{
ReservedErrorSelector(ErrorReporter& _errorReporter):
Checker(_errorReporter)
{}

void endVisit(ErrorDefinition const& _error) override
{
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."
);
else
{
uint32_t selector = selectorFromSignature32(_error.functionType(true)->externalSignature());
if (selector == 0 || ~selector == 0)
m_errorReporter.syntaxError(
2855_error,
_error.location(),
"The selector 0x" + toHex(toCompactBigEndian(selector, 4)) + " is reserved. Please rename the error to avoid the collision."
);
}
}
};
}

PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter)
Expand All @@ -371,4 +404,5 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
m_checkers.push_back(make_shared<ReservedErrorSelector>(_errorReporter));
}
3 changes: 3 additions & 0 deletions libsolidity/analysis/PostTypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace solidity::frontend
* - whether a modifier is in a function header
* - whether an event is used outside of an emit statement
* - whether a variable is declared in a interface
* - whether an error uses a reserved signature
*
* When adding a new checker, make sure a visitor that forwards calls that your
* checker uses exists in PostTypeChecker. Add missing ones.
Expand Down Expand Up @@ -77,6 +78,8 @@ class PostTypeChecker: private ASTConstVisitor
bool visit(VariableDeclaration const& _variable) override;
void endVisit(VariableDeclaration const& _variable) override;

void endVisit(ErrorDefinition const& _error) override;

bool visit(EmitStatement const& _emit) override;
void endVisit(EmitStatement const& _emit) override;

Expand Down
67 changes: 42 additions & 25 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

#include <range/v3/view/zip.hpp>
#include <range/v3/view/drop_exactly.hpp>
#include <range/v3/algorithm/count_if.hpp>

#include <memory>
#include <vector>
Expand Down Expand Up @@ -686,37 +687,26 @@ void TypeChecker::visitManually(
bool TypeChecker::visit(EventDefinition const& _eventDef)
{
solAssert(_eventDef.visibility() > Visibility::Internal, "");
unsigned numIndexed = 0;
for (ASTPointer<VariableDeclaration> 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<VariableDeclaration> 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)
m_errorReporter.typeError(7249_error, _eventDef.location(), "More than 3 indexed arguments for event.");
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<FunctionType const&>(*_funType.annotation().type);
Expand Down Expand Up @@ -2279,7 +2269,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())
{
Expand Down Expand Up @@ -3426,6 +3417,32 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
);
}

void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)
{
string kind = dynamic_cast<EventDefinition const*>(&_callable) ? "event" : "error";
for (ASTPointer<VariableDeclaration> 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<ContractDefinition const*> const& _seenContracts
Expand Down
3 changes: 3 additions & 0 deletions libsolidity/analysis/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class TypeChecker: private ASTConstVisitor
/// case this is a base constructor call.
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> 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;
Expand Down Expand Up @@ -153,6 +154,8 @@ class TypeChecker: private ASTConstVisitor
void endVisit(Literal const& _literal) override;
void endVisit(UsingForDirective const& _usingForDirective) override;

void checkErrorAndEventParameters(CallableDeclaration const& _callable);

bool contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
Expand Down
Loading