Syntax changes for catching custom errors.

This commit is contained in:
chriseth 2021-02-02 16:14:44 +01:00
parent b3261d7b0f
commit edf3529db6
11 changed files with 179 additions and 70 deletions

View File

@ -994,9 +994,10 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
TryCatchClause const* panicClause = nullptr; TryCatchClause const* panicClause = nullptr;
TryCatchClause const* errorClause = nullptr; TryCatchClause const* errorClause = nullptr;
TryCatchClause const* lowLevelClause = nullptr; TryCatchClause const* lowLevelClause = nullptr;
map<ErrorDefinition const*, TryCatchClause const*> seenErrors;
for (auto const& clause: _tryStatement.clauses() | ranges::views::drop_exactly(1) | views::dereferenceChecked) for (auto const& clause: _tryStatement.clauses() | ranges::views::drop_exactly(1) | views::dereferenceChecked)
{ {
if (clause.errorName() == "") if (clause.kind() == TryCatchClause::Kind::Fallback)
{ {
if (lowLevelClause) if (lowLevelClause)
m_errorReporter.typeError( m_errorReporter.typeError(
@ -1022,59 +1023,85 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`." "). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
); );
} }
continue;
} }
else if (clause.errorName() == "Error" || clause.errorName() == "Panic")
{
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
1812_error,
clause.location(),
"This catch clause type cannot be used on the selected EVM version (" +
m_evmVersion.name() +
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
);
if (clause.errorName() == "Error") if (!m_evmVersion.supportsReturndata())
{ m_errorReporter.typeError(
if (errorClause) 1812_error,
m_errorReporter.typeError( clause.location(),
1036_error, "This catch clause type cannot be used on the selected EVM version (" +
clause.location(), m_evmVersion.name() +
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()), "). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
"This try statement already has an \"Error\" catch clause." );
);
errorClause = &clause; if (clause.kind() == TryCatchClause::Kind::Error)
if ( {
!clause.parameters() || if (errorClause)
clause.parameters()->parameters().size() != 1 || m_errorReporter.typeError(
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory() 1036_error,
) clause.location(),
m_errorReporter.typeError(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`."); SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
} "This try statement already has an \"Error\" catch clause."
else );
{ errorClause = &clause;
if (panicClause) if (
m_errorReporter.typeError( !clause.parameters() ||
6732_error, clause.parameters()->parameters().size() != 1 ||
clause.location(), *clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
SecondarySourceLocation{}.append("The first clause is here:", panicClause->location()), )
"This try statement already has a \"Panic\" catch clause." m_errorReporter.typeError(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
); }
panicClause = &clause; else if (clause.kind() == TryCatchClause::Kind::Panic)
if ( {
!clause.parameters() || if (panicClause)
clause.parameters()->parameters().size() != 1 || m_errorReporter.typeError(
*clause.parameters()->parameters().front()->type() != *TypeProvider::uint256() 6732_error,
) clause.location(),
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`."); SecondarySourceLocation{}.append("The first clause is here:", panicClause->location()),
} "This try statement already has a \"Panic\" catch clause."
);
panicClause = &clause;
if (
!clause.parameters() ||
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::uint256()
)
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
} }
else else
m_errorReporter.typeError( {
3542_error, solAssert(clause.kind() == TryCatchClause::Kind::UserDefined, "");
clause.location(), solAssert(*clause.errorName().annotation().requiredLookup == VirtualLookup::Static, "");
"Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`." ErrorDefinition const* error = dynamic_cast<ErrorDefinition const*>(clause.errorName().annotation().referencedDeclaration);
); if (!error)
{
m_errorReporter.typeError(1178_error, clause.location(), "Expected the name of an error.");
continue;
}
if (!seenErrors.emplace(error, &clause).second)
m_errorReporter.typeError(
6853_error,
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", seenErrors[error]->location()),
"This try statement already has a \"" + error->name() + "\" catch clause."
);
if (
!clause.parameters() ||
clause.parameters()->parameters().size() != error->parameters().size()
)
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
else
for (auto&& [varDecl, parameter]: ranges::views::zip(clause.parameters()->parameters(), error->parameters()))
if (*varDecl->type() != *parameter->type())
m_errorReporter.typeError(
63958_error,
varDecl->location(),
("Expected a parameter of type \"" + parameter->type()->toString(true) + "\" ") +
("but got \"" + varDecl->type()->toString(true) + "\"")
);
}
} }
} }

View File

@ -856,13 +856,14 @@ string Literal::getChecksummedAddress() const
TryCatchClause const* TryStatement::successClause() const TryCatchClause const* TryStatement::successClause() const
{ {
solAssert(m_clauses.size() > 0, ""); solAssert(m_clauses.size() > 0, "");
solAssert(m_clauses.front()->kind() == TryCatchClause::Kind::Success, "");
return m_clauses[0].get(); return m_clauses[0].get();
} }
TryCatchClause const* TryStatement::panicClause() const TryCatchClause const* TryStatement::panicClause() const
{ {
for (size_t i = 1; i < m_clauses.size(); ++i) for (size_t i = 1; i < m_clauses.size(); ++i)
if (m_clauses[i]->errorName() == "Panic") if (m_clauses[i]->kind() == TryCatchClause::Kind::Panic)
return m_clauses[i].get(); return m_clauses[i].get();
return nullptr; return nullptr;
} }
@ -870,7 +871,7 @@ TryCatchClause const* TryStatement::panicClause() const
TryCatchClause const* TryStatement::errorClause() const TryCatchClause const* TryStatement::errorClause() const
{ {
for (size_t i = 1; i < m_clauses.size(); ++i) for (size_t i = 1; i < m_clauses.size(); ++i)
if (m_clauses[i]->errorName() == "Error") if (m_clauses[i]->kind() == TryCatchClause::Kind::Error)
return m_clauses[i].get(); return m_clauses[i].get();
return nullptr; return nullptr;
} }
@ -878,7 +879,7 @@ TryCatchClause const* TryStatement::errorClause() const
TryCatchClause const* TryStatement::fallbackClause() const TryCatchClause const* TryStatement::fallbackClause() const
{ {
for (size_t i = 1; i < m_clauses.size(); ++i) for (size_t i = 1; i < m_clauses.size(); ++i)
if (m_clauses[i]->errorName().empty()) if (m_clauses[i]->kind() == TryCatchClause::Kind::Fallback)
return m_clauses[i].get(); return m_clauses[i].get();
return nullptr; return nullptr;
} }

View File

@ -1503,29 +1503,46 @@ private:
class TryCatchClause: public ASTNode, public Scopable, public ScopeOpener class TryCatchClause: public ASTNode, public Scopable, public ScopeOpener
{ {
public: public:
enum Kind
{
Success,
Panic,
Error,
Fallback,
UserDefined
};
TryCatchClause( TryCatchClause(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> _errorName, Kind _kind,
ASTPointer<IdentifierPath> _errorName,
ASTPointer<ParameterList> _parameters, ASTPointer<ParameterList> _parameters,
ASTPointer<Block> _block ASTPointer<Block> _block
): ):
ASTNode(_id, _location), ASTNode(_id, _location),
m_kind(_kind),
m_errorName(std::move(_errorName)), m_errorName(std::move(_errorName)),
m_parameters(std::move(_parameters)), m_parameters(std::move(_parameters)),
m_block(std::move(_block)) m_block(std::move(_block))
{} {
solAssert(!!m_errorName == (m_kind == Kind::UserDefined), "");
}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
ASTString const& errorName() const { return *m_errorName; }
Kind kind() const { return m_kind; }
/// @returns the name of the error. Should only be called if catch kind is UserDefined.
IdentifierPath const& errorName() const { return *m_errorName; }
ParameterList const* parameters() const { return m_parameters.get(); } ParameterList const* parameters() const { return m_parameters.get(); }
Block const& block() const { return *m_block; } Block const& block() const { return *m_block; }
TryCatchClauseAnnotation& annotation() const override; TryCatchClauseAnnotation& annotation() const override;
private: private:
ASTPointer<ASTString> m_errorName; Kind m_kind;
ASTPointer<IdentifierPath> m_errorName;
ASTPointer<ParameterList> m_parameters; ASTPointer<ParameterList> m_parameters;
ASTPointer<Block> m_block; ASTPointer<Block> m_block;
}; };
@ -1535,6 +1552,8 @@ private:
* Syntax: * Syntax:
* try <call> returns (uint x, uint y) { * try <call> returns (uint x, uint y) {
* // success code * // success code
* } catch Custom(uint data) {
* // custom error handler
* } catch Panic(uint errorCode) { * } catch Panic(uint errorCode) {
* // panic * // panic
* } catch Error(string memory cause) { * } catch Error(string memory cause) {

View File

@ -578,7 +578,10 @@ bool ASTJsonConverter::visit(IfStatement const& _node)
bool ASTJsonConverter::visit(TryCatchClause const& _node) bool ASTJsonConverter::visit(TryCatchClause const& _node)
{ {
setJsonNode(_node, "TryCatchClause", { setJsonNode(_node, "TryCatchClause", {
make_pair("errorName", _node.errorName()), make_pair("kind", catchClauseKind(_node.kind())),
make_pair("errorName", toJsonOrNull(
_node.kind() == TryCatchClause::Kind::UserDefined ? &_node.errorName() : nullptr
)),
make_pair("parameters", toJsonOrNull(_node.parameters())), make_pair("parameters", toJsonOrNull(_node.parameters())),
make_pair("block", toJson(_node.block())) make_pair("block", toJson(_node.block()))
}); });
@ -931,6 +934,19 @@ string ASTJsonConverter::functionCallKind(FunctionCallKind _kind)
} }
} }
string ASTJsonConverter::catchClauseKind(TryCatchClause::Kind _kind)
{
switch (_kind)
{
case TryCatchClause::Kind::Success: return "success";
case TryCatchClause::Kind::Panic: return "panic";
case TryCatchClause::Kind::Error: return "error";
case TryCatchClause::Kind::Fallback: return "fallback";
case TryCatchClause::Kind::UserDefined: return "userDefined";
}
solAssert(false, "Unknown kind of catch clause.");
}
string ASTJsonConverter::literalTokenKind(Token _token) string ASTJsonConverter::literalTokenKind(Token _token)
{ {
switch (_token) switch (_token)

View File

@ -153,6 +153,7 @@ private:
static std::string location(VariableDeclaration::Location _location); static std::string location(VariableDeclaration::Location _location);
static std::string contractKind(ContractKind _kind); static std::string contractKind(ContractKind _kind);
static std::string functionCallKind(FunctionCallKind _kind); static std::string functionCallKind(FunctionCallKind _kind);
static std::string catchClauseKind(TryCatchClause::Kind _kind);
static std::string literalTokenKind(Token _token); static std::string literalTokenKind(Token _token);
static std::string type(Expression const& _expression); static std::string type(Expression const& _expression);
static std::string type(VariableDeclaration const& _varDecl); static std::string type(VariableDeclaration const& _varDecl);

View File

@ -642,7 +642,8 @@ ASTPointer<TryCatchClause> ASTJsonImporter::createTryCatchClause(Json::Value con
{ {
return createASTNode<TryCatchClause>( return createASTNode<TryCatchClause>(
_node, _node,
memberAsASTString(_node, "errorName"), tryCatchClauseKind(_node),
nullOrCast<IdentifierPath>(member(_node, "errorName")),
nullOrCast<ParameterList>(member(_node, "parameters")), nullOrCast<ParameterList>(member(_node, "parameters")),
convertJsonToASTNode<Block>(member(_node, "block")) convertJsonToASTNode<Block>(member(_node, "block"))
); );
@ -959,6 +960,24 @@ bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name
// =========== JSON to definition helpers ======================= // =========== JSON to definition helpers =======================
TryCatchClause::Kind ASTJsonImporter::tryCatchClauseKind(Json::Value const& _node)
{
astAssert(!member(_node, "kind").isNull(), "");
if (_node["kind"].asString() == "success")
return TryCatchClause::Kind::Success;
else if (_node["kind"].asString() == "error")
return TryCatchClause::Kind::Error;
else if (_node["kind"].asString() == "panic")
return TryCatchClause::Kind::Panic;
else if (_node["kind"].asString() == "fallback")
return TryCatchClause::Kind::Fallback;
else if (_node["kind"].asString() == "userDefined")
return TryCatchClause::Kind::UserDefined;
else
astAssert(false, "Unknown try catch clause kind");
return {};
}
ContractKind ASTJsonImporter::contractKind(Json::Value const& _node) ContractKind ASTJsonImporter::contractKind(Json::Value const& _node)
{ {
ContractKind kind; ContractKind kind;

View File

@ -144,6 +144,7 @@ private:
Visibility visibility(Json::Value const& _node); Visibility visibility(Json::Value const& _node);
StateMutability stateMutability(Json::Value const& _node); StateMutability stateMutability(Json::Value const& _node);
VariableDeclaration::Location location(Json::Value const& _node); VariableDeclaration::Location location(Json::Value const& _node);
TryCatchClause::Kind tryCatchClauseKind(Json::Value const& _node);
ContractKind contractKind(Json::Value const& _node); ContractKind contractKind(Json::Value const& _node);
Token literalTokenKind(Json::Value const& _node); Token literalTokenKind(Json::Value const& _node);
Literal::SubDenomination subdenomination(Json::Value const& _node); Literal::SubDenomination subdenomination(Json::Value const& _node);

View File

@ -542,6 +542,8 @@ void TryCatchClause::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_errorName)
m_errorName->accept(_visitor);
if (m_parameters) if (m_parameters)
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
m_block->accept(_visitor); m_block->accept(_visitor);
@ -553,6 +555,8 @@ void TryCatchClause::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_errorName)
m_errorName->accept(_visitor);
if (m_parameters) if (m_parameters)
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
m_block->accept(_visitor); m_block->accept(_visitor);

View File

@ -975,14 +975,14 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
ASTPointer<TryCatchClause> panic{}; ASTPointer<TryCatchClause> panic{};
ASTPointer<TryCatchClause> fallback{}; ASTPointer<TryCatchClause> fallback{};
for (size_t i = 1; i < _catchClauses.size(); ++i) for (size_t i = 1; i < _catchClauses.size(); ++i)
if (_catchClauses[i]->errorName() == "Error") if (_catchClauses[i]->kind() == TryCatchClause::Kind::Error)
error = _catchClauses[i]; error = _catchClauses[i];
else if (_catchClauses[i]->errorName() == "Panic") else if (_catchClauses[i]->kind() == TryCatchClause::Kind::Panic)
panic = _catchClauses[i]; panic = _catchClauses[i];
else if (_catchClauses[i]->errorName().empty()) else if (_catchClauses[i]->kind() == TryCatchClause::Kind::Fallback)
fallback = _catchClauses[i]; fallback = _catchClauses[i];
else else
solAssert(false, ""); solUnimplementedAssert(false, "");
solAssert(_catchClauses.size() == 1ul + (error ? 1 : 0) + (panic ? 1 : 0) + (fallback ? 1 : 0), ""); solAssert(_catchClauses.size() == 1ul + (error ? 1 : 0) + (panic ? 1 : 0) + (fallback ? 1 : 0), "");

View File

@ -1323,7 +1323,7 @@ ASTPointer<TryStatement> Parser::parseTryStatement(ASTPointer<ASTString> const&
ASTPointer<Block> successBlock = parseBlock(); ASTPointer<Block> successBlock = parseBlock();
successClauseFactory.setEndPositionFromNode(successBlock); successClauseFactory.setEndPositionFromNode(successBlock);
clauses.emplace_back(successClauseFactory.createNode<TryCatchClause>( clauses.emplace_back(successClauseFactory.createNode<TryCatchClause>(
make_shared<ASTString>(), returnsParameters, successBlock TryCatchClause::Kind::Success, ASTPointer<IdentifierPath>{}, returnsParameters, successBlock
)); ));
do do
@ -1342,20 +1342,42 @@ ASTPointer<TryCatchClause> Parser::parseCatchClause()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
expectToken(Token::Catch); expectToken(Token::Catch);
ASTPointer<ASTString> errorName = make_shared<string>(); TryCatchClause::Kind kind;
ASTPointer<IdentifierPath> errorName;
ASTPointer<ParameterList> errorParameters; ASTPointer<ParameterList> errorParameters;
if (m_scanner->currentToken() != Token::LBrace) if (m_scanner->currentToken() != Token::LBrace)
{ {
if (m_scanner->currentToken() == Token::Identifier) if (m_scanner->currentToken() == Token::Identifier)
errorName = expectIdentifierToken(); {
string name = m_scanner->currentLiteral();
if (name == "Error")
{
kind = TryCatchClause::Kind::Error;
m_scanner->next();
}
else if (name == "Panic")
{
kind = TryCatchClause::Kind::Panic;
m_scanner->next();
}
else
{
errorName = parseIdentifierPath();
kind = TryCatchClause::Kind::UserDefined;
}
}
else
kind = TryCatchClause::Kind::Fallback;
VarDeclParserOptions options; VarDeclParserOptions options;
options.allowEmptyName = true; options.allowEmptyName = true;
options.allowLocationSpecifier = true; options.allowLocationSpecifier = true;
errorParameters = parseParameterList(options, !errorName->empty()); errorParameters = parseParameterList(options);
} }
else
kind = TryCatchClause::Kind::Fallback;
ASTPointer<Block> block = parseBlock(); ASTPointer<Block> block = parseBlock();
nodeFactory.setEndPositionFromNode(block); nodeFactory.setEndPositionFromNode(block);
return nodeFactory.createNode<TryCatchClause>(errorName, errorParameters, block); return nodeFactory.createNode<TryCatchClause>(kind, errorName, errorParameters, block);
} }
ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString) ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString)

View File

@ -7,5 +7,4 @@ contract C {
} }
} }
// ---- // ----
// TypeError 3542: (93-119): Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`. // DeclarationError 7920: (99-105): Identifier not found or not unique.
// TypeError 3542: (120-143): Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`.