Skip to content

Commit

Permalink
Syntax for custom errors.
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseth committed Feb 23, 2021
1 parent e75e3fc commit 19ad0b3
Show file tree
Hide file tree
Showing 65 changed files with 562 additions and 55 deletions.
16 changes: 15 additions & 1 deletion 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 @@ -367,7 +381,7 @@ inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;
/**
* Besides regular non-keyword Identifiers, the 'from' keyword can also occur as identifier outside of import statements.
*/
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())
// 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)
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 @@ -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,
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 @@ -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,
Expand Down Expand Up @@ -127,11 +134,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"};
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 " + to_string(selector) + " 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
1 change: 1 addition & 0 deletions libsolidity/analysis/SyntaxChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <liblangutil/SemVerHandler.h>

#include <libsolutil/UTF8.h>
#include <libsolutil/FunctionSelector.h>

#include <boost/algorithm/string.hpp>

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 @@ -681,37 +682,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 @@ -2248,7 +2238,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 @@ -3392,6 +3383,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 @@ -119,6 +119,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 @@ -147,6 +148,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

0 comments on commit 19ad0b3

Please sign in to comment.