Merge pull request #7328 from ethereum/tryCatch

Try and Catch for external calls.
This commit is contained in:
chriseth 2019-09-23 18:03:08 +02:00 committed by GitHub
commit 63eb097c3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1535 additions and 60 deletions

View File

@ -50,6 +50,12 @@ This section gives detailed instructions on how to update prior code for every b
* Change ``uint length = array.push(value)`` to ``array.push(value);``. The new length can be
accessed via ``array.length``.
New Features
============
* The :ref:`try/catch statement <try-catch>` allows you to react on failed external calls.
Deprecated Elements
===================

View File

@ -15,6 +15,10 @@ Most of the control structures known from curly-braces languages are available i
There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with
the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of ``try``/``catch``-statements,
but only for :ref:`external function calls <external-function-calls>` and
contract creation calls.
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
around single-statement bodies.
@ -383,7 +387,7 @@ of an exception instead of "bubbling up".
.. warning::
The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed.
It is not yet possible to catch exceptions with Solidity.
Exceptions can be caught with the ``try``/``catch`` statement.
``assert`` and ``require``
--------------------------
@ -488,3 +492,94 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll
.. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which
was deprecated in version 0.4.13 and removed in version 0.5.0.
.. _try-catch:
``try``/``catch``
-----------------
A failure in an external call can be caught using a try/catch statement, as follows:
::
pragma solidity >=0.5.0 <0.7.0;
interface DataFeed { function getData(address token) external returns (uint value); }
contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used
// or there was a failing assertion, division
// by zero, etc. inside getData.
errorCount++;
return (0, false);
}
}
}
The ``try`` keyword has to be followed by an expression representing an external function call
or a contract creation (``new ContractName()``).
Errors inside the expression are not caught (for example if it is a complex expression
that also involves internal function calls), only a revert happening inside the external
call itself. The ``returns`` part (which is optional) that follows declares return variables
matching the types returned by the external call. In case there was no error,
these variables are assigned and the contract's execution continues inside the
first success block. If the end of the success block is reached, execution continues after the ``catch`` blocks.
Currently, Solidity supports different kinds of catch blocks depending on the
type of error. If the error was caused by ``revert("reasonString")`` or
``require(false, "reasonString")`` (or an internal error that causes such an
exception), then the catch clause
of the type ``catch Error(string memory reason)`` will be executed.
It is planned to support other types of error data in the future.
The string ``Error`` is currently parsed as is and is not treated as an identifier.
The clause ``catch (bytes memory lowLevelData)`` is executed if the error signature
does not match any other clause, there was an error during decoding of the error
message, if there was a failing assertion in the external
call (for example due to a division by zero or a failing ``assert()``) or
if no error data was provided with the exception.
The declared variable provides access to the low-level error data in that case.
If you are not interested in the error data, you can just use
``catch { ... }`` (even as the only catch clause).
In order to catch all error cases, you have to have at least the clause
``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``.
The variables declared in the ``returns`` and the ``catch`` clause are only
in scope in the block that follows.
.. note::
If an error happens during the decoding of the return data
inside a try/catch-statement, this causes an exception in the currently
executing contract and because of that, it is not caught in the catch clause.
If there is an error during decoding of ``catch Error(string memory reason)``
and there is a low-level catch clause, this error is caught there.
.. note::
If execution reaches a catch-block, then the state-changing effects of
the external call have been reverted. If execution reaches
the success block, the effects were not reverted.
If the effects have been reverted, then execution either continues
in a catch block or the execution of the try/catch statement itself
reverts (for example due to decoding failures as noted above or
due to not providing a low-level catch clause).

View File

@ -62,12 +62,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' Identifier? ParameterList Block
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement

View File

@ -85,6 +85,18 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional)
return false;
}
bool ControlFlowBuilder::visit(TryStatement const& _tryStatement)
{
appendControlFlow(_tryStatement.externalCall());
auto nodes = splitFlow(_tryStatement.clauses().size());
for (size_t i = 0; i < _tryStatement.clauses().size(); ++i)
nodes[i] = createFlow(nodes[i], _tryStatement.clauses()[i]->block());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{
solAssert(!!m_currentNode, "");
@ -384,7 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
_variableDeclaration.value().get()
);
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter())
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,

View File

@ -45,6 +45,7 @@ private:
// Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override;
bool visit(Conditional const& _conditional) override;
bool visit(TryStatement const& _tryStatement) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
@ -98,12 +99,27 @@ private:
return result;
}
/// Splits the control flow starting at the current node into @a _n paths.
/// m_currentNode is set to nullptr and has to be set manually or
/// using mergeFlow later.
std::vector<CFGNode*> splitFlow(size_t n)
{
std::vector<CFGNode*> result(n);
for (auto& node: result)
{
node = m_nodeContainer.newNode();
connect(m_currentNode, node);
}
m_currentNode = nullptr;
return result;
}
/// Merges the control flow of @a _nodes to @a _endNode.
/// If @a _endNode is nullptr, a new node is creates and used as end node.
/// Sets the merge destination as current node.
/// Note: @a _endNode may be one of the nodes in @a _nodes.
template<size_t n>
void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr)
template<typename C>
void mergeFlow(C const& _nodes, CFGNode* _endNode = nullptr)
{
CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
for (auto& node: _nodes)

View File

@ -624,6 +624,18 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(TryCatchClause& _tryCatchClause)
{
_tryCatchClause.setScope(m_currentScope);
enterNewSubScope(_tryCatchClause);
return true;
}
void DeclarationRegistrationHelper::endVisit(TryCatchClause&)
{
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier)
{
registerDeclaration(_modifier, true);

View File

@ -177,6 +177,8 @@ private:
bool visit(EnumValue& _value) override;
bool visit(FunctionDefinition& _function) override;
void endVisit(FunctionDefinition& _function) override;
bool visit(TryCatchClause& _tryCatchClause) override;
void endVisit(TryCatchClause& _tryCatchClause) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
bool visit(FunctionTypeName& _funTypeName) override;

View File

@ -388,7 +388,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
", ",
" or "
);
if (_variable.isCallableParameter())
if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +

View File

@ -118,10 +118,12 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
{
if (var.first.second->isCallableParameter())
if (var.first.second->isCallableOrCatchParameter())
m_errorReporter.warning(
var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning."
"Unused " +
string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") +
" parameter. Remove or comment out the variable name to silence this warning."
);
else
m_errorReporter.warning(var.first.second->location(), "Unused local variable.");

View File

@ -433,7 +433,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// * or inside of a struct definition.
if (
m_scope->isInterface()
&& !_variable.isCallableParameter()
&& !_variable.isCallableOrCatchParameter()
&& !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@ -743,6 +743,133 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
return false;
}
void TypeChecker::endVisit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
FunctionType const& functionType = dynamic_cast<FunctionType const&>(*externalCall->expression().annotation().type);
if (
functionType.kind() != FunctionType::Kind::External &&
functionType.kind() != FunctionType::Kind::Creation &&
functionType.kind() != FunctionType::Kind::DelegateCall
)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
externalCall->annotation().tryCall = true;
solAssert(_tryStatement.clauses().size() >= 2, "");
solAssert(_tryStatement.clauses().front(), "");
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
TypePointers returnTypes =
m_evmVersion.supportsReturndata() ?
functionType.returnParameterTypes() :
functionType.returnParameterTypesWithoutDynamicTypes();
std::vector<ASTPointer<VariableDeclaration>> const& parameters =
successClause.parameters()->parameters();
if (returnTypes.size() != parameters.size())
m_errorReporter.typeError(
successClause.location(),
"Function returns " +
to_string(functionType.returnParameterTypes().size()) +
" values, but returns clause has " +
to_string(parameters.size()) +
" variables."
);
size_t len = min(returnTypes.size(), parameters.size());
for (size_t i = 0; i < len; ++i)
{
solAssert(returnTypes[i], "");
if (parameters[i] && *parameters[i]->annotation().type != *returnTypes[i])
m_errorReporter.typeError(
parameters[i]->location(),
"Invalid type, expected " +
returnTypes[i]->toString(false) +
" but got " +
parameters[i]->annotation().type->toString() +
"."
);
}
}
TryCatchClause const* errorClause = nullptr;
TryCatchClause const* lowLevelClause = nullptr;
for (size_t i = 1; i < _tryStatement.clauses().size(); ++i)
{
TryCatchClause const& clause = *_tryStatement.clauses()[i];
if (clause.errorName() == "")
{
if (lowLevelClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", lowLevelClause->location()),
"This try statement already has a low-level catch clause."
);
lowLevelClause = &clause;
if (clause.parameters() && !clause.parameters()->parameters().empty())
{
if (
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::bytesMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch (bytes memory ...) { ... }` or `catch { ... }`.");
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
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 { ... }`."
);
}
}
else if (clause.errorName() == "Error")
{
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
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 (errorClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
"This try statement already has an \"Error\" catch clause."
);
errorClause = &clause;
if (
!clause.parameters() ||
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
}
else
m_errorReporter.typeError(
clause.location(),
"Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`."
);
}
}
bool TypeChecker::visit(WhileStatement const& _whileStatement)
{
expectType(_whileStatement.condition(), *TypeProvider::boolean());

View File

@ -120,6 +120,7 @@ private:
void endVisit(FunctionTypeName const& _funType) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(IfStatement const& _ifStatement) override;
void endVisit(TryStatement const& _tryStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override;
void endVisit(Return const& _return) override;

View File

@ -448,12 +448,13 @@ bool VariableDeclaration::isLocalVariable() const
dynamic_cast<FunctionTypeName const*>(s) ||
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<TryCatchClause const*>(s) ||
dynamic_cast<ForStatement const*>(s);
}
bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isCallableOrCatchParameter() const
{
if (isReturnParameter())
if (isReturnParameter() || isTryCatchParameter())
return true;
vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
@ -472,7 +473,7 @@ bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isLocalOrReturn() const
{
return isReturnParameter() || (isLocalVariable() && !isCallableParameter());
return isReturnParameter() || (isLocalVariable() && !isCallableOrCatchParameter());
}
bool VariableDeclaration::isReturnParameter() const
@ -492,9 +493,14 @@ bool VariableDeclaration::isReturnParameter() const
return false;
}
bool VariableDeclaration::isTryCatchParameter() const
{
return dynamic_cast<TryCatchClause const*>(scope());
}
bool VariableDeclaration::isExternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
@ -506,7 +512,7 @@ bool VariableDeclaration::isExternalCallableParameter() const
bool VariableDeclaration::isInternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
@ -518,7 +524,7 @@ bool VariableDeclaration::isInternalCallableParameter() const
bool VariableDeclaration::isLibraryFunctionParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
@ -554,10 +560,10 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
locations.insert(Location::Storage);
return locations;
}
else if (isCallableParameter())
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter())
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
locations.insert(Location::Storage);
return locations;
}

View File

@ -553,7 +553,7 @@ public:
};
/**
* Parameter list, used as function parameter list and return list.
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively
* inside structs).
*/
@ -738,9 +738,12 @@ public:
/// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
bool isCallableOrCatchParameter() const;
/// @returns true if this variable is a return parameter of a function.
bool isReturnParameter() const;
/// @returns true if this variable is a parameter of the success or failure clausse
/// of a try/catch statement.
bool isTryCatchParameter() const;
/// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
@ -1174,6 +1177,76 @@ private:
ASTPointer<Statement> m_falseBody; ///< "else" part, optional
};
/**
* Clause of a try-catch block. Includes both the successful case and the
* unsuccessful cases.
* Names are only allowed for the unsuccessful cases.
*/
class TryCatchClause: public ASTNode, public Scopable
{
public:
TryCatchClause(
SourceLocation const& _location,
ASTPointer<ASTString> const& _errorName,
ASTPointer<ParameterList> const& _parameters,
ASTPointer<Block> const& _block
):
ASTNode(_location),
m_errorName(_errorName),
m_parameters(_parameters),
m_block(_block)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ASTString const& errorName() const { return *m_errorName; }
ParameterList const* parameters() const { return m_parameters.get(); }
Block const& block() const { return *m_block; }
private:
ASTPointer<ASTString> m_errorName;
ASTPointer<ParameterList> m_parameters;
ASTPointer<Block> m_block;
};
/**
* Try-statement with a variable number of catch statements.
* Syntax:
* try <call> returns (uint x, uint y) {
* // success code
* } catch Error(string memory cause) {
* // error code, reason provided
* } catch (bytes memory lowLevelData) {
* // error code, no reason provided or non-matching error signature.
* }
*
* The last statement given above can also be specified as
* } catch () {
*/
class TryStatement: public Statement
{
public:
TryStatement(
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _externalCall,
std::vector<ASTPointer<TryCatchClause>> const& _clauses
):
Statement(_location, _docString),
m_externalCall(_externalCall),
m_clauses(_clauses)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& externalCall() const { return *m_externalCall; }
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
private:
ASTPointer<Expression> m_externalCall;
std::vector<ASTPointer<TryCatchClause>> m_clauses;
};
/**
* Statement in which a break statement is legal (abstract class).
*/

View File

@ -218,6 +218,8 @@ enum class FunctionCallKind
struct FunctionCallAnnotation: ExpressionAnnotation
{
FunctionCallKind kind = FunctionCallKind::Unset;
/// If true, this is the external call of a try statement.
bool tryCall = false;
};
}

View File

@ -64,6 +64,8 @@ class Statement;
class Block;
class PlaceholderStatement;
class IfStatement;
class TryCatchClause;
class TryStatement;
class BreakableStatement;
class WhileStatement;
class ForStatement;

View File

@ -499,6 +499,25 @@ bool ASTJsonConverter::visit(IfStatement const& _node)
return false;
}
bool ASTJsonConverter::visit(TryCatchClause const& _node)
{
setJsonNode(_node, "TryCatchClause", {
make_pair("errorName", _node.errorName()),
make_pair("parameters", toJsonOrNull(_node.parameters())),
make_pair("block", toJson(_node.block()))
});
return false;
}
bool ASTJsonConverter::visit(TryStatement const& _node)
{
setJsonNode(_node, "TryStatement", {
make_pair("externalCall", toJson(_node.externalCall())),
make_pair("clauses", toJson(_node.clauses()))
});
return false;
}
bool ASTJsonConverter::visit(WhileStatement const& _node)
{
setJsonNode(
@ -647,7 +666,8 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("expression", toJson(_node.expression())),
make_pair("names", std::move(names)),
make_pair("arguments", toJson(_node.arguments()))
make_pair("arguments", toJson(_node.arguments())),
make_pair("tryCall", _node.annotation().tryCall)
};
if (m_legacy)
{

View File

@ -93,6 +93,8 @@ public:
bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(TryCatchClause const& _node) override;
bool visit(TryStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;

View File

@ -69,6 +69,8 @@ public:
virtual bool visit(Block& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); }
virtual bool visit(IfStatement& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause& _node) { return visitNode(_node); }
virtual bool visit(TryStatement& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement& _node) { return visitNode(_node); }
virtual bool visit(ForStatement& _node) { return visitNode(_node); }
virtual bool visit(Continue& _node) { return visitNode(_node); }
@ -117,6 +119,8 @@ public:
virtual void endVisit(Block& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Continue& _node) { endVisitNode(_node); }
@ -177,6 +181,8 @@ public:
virtual bool visit(Block const& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement const& _node) { return visitNode(_node); }
virtual bool visit(IfStatement const& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause const& _node) { return visitNode(_node); }
virtual bool visit(TryStatement const& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement const& _node) { return visitNode(_node); }
virtual bool visit(ForStatement const& _node) { return visitNode(_node); }
virtual bool visit(Continue const& _node) { return visitNode(_node); }
@ -225,6 +231,8 @@ public:
virtual void endVisit(Block const& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause const& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Continue const& _node) { endVisitNode(_node); }

View File

@ -461,6 +461,48 @@ void IfStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void WhileStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

View File

@ -95,6 +95,26 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
m_context << Instruction::REVERT;
}
void CompilerUtils::returnDataToArray()
{
if (m_context.evmVersion().supportsReturndata())
{
m_context << Instruction::RETURNDATASIZE;
m_context.appendInlineAssembly(R"({
switch v case 0 {
v := 0x60
} default {
v := mload(0x40)
mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
mstore(v, returndatasize())
returndatacopy(add(v, 0x20), 0, returndatasize())
}
})", {"v"});
}
else
pushZeroPointer();
}
void CompilerUtils::accessCalldataTail(Type const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");

View File

@ -67,6 +67,10 @@ public:
/// Stack post:
void revertWithStringData(Type const& _argumentType);
/// Allocates a new array and copies the return data to it.
/// If the EVM does not support return data, creates an empty array.
void returnDataToArray();
/// Computes the absolute calldata offset of a tail given a base reference and the (absolute)
/// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also
/// returns the array length on the stack.

View File

@ -34,6 +34,8 @@
#include <liblangutil/ErrorReporter.h>
#include <libdevcore/Whiskers.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
@ -761,6 +763,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
return false;
}
bool ContractCompiler::visit(TryStatement const& _tryStatement)
{
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _tryStatement);
compileExpression(_tryStatement.externalCall());
unsigned returnSize = _tryStatement.externalCall().annotation().type->sizeOnStack();
// Stack: [ return values] <success flag>
eth::AssemblyItem successTag = m_context.appendConditionalJump();
// Catch case.
m_context.adjustStackOffset(-returnSize);
handleCatch(_tryStatement.clauses());
eth::AssemblyItem endTag = m_context.appendJumpToNew();
m_context << successTag;
m_context.adjustStackOffset(returnSize);
{
// Success case.
// Stack: return values
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
vector<TypePointer> exprTypes{_tryStatement.externalCall().annotation().type};
if (auto tupleType = dynamic_cast<TupleType const*>(exprTypes.front()))
exprTypes = tupleType->components();
vector<ASTPointer<VariableDeclaration>> const& params = successClause.parameters()->parameters();
solAssert(exprTypes.size() == params.size(), "");
for (size_t i = 0; i < exprTypes.size(); ++i)
solAssert(params[i] && exprTypes[i] && *params[i]->annotation().type == *exprTypes[i], "");
}
else
CompilerUtils(m_context).popStackSlots(returnSize);
_tryStatement.clauses().front()->accept(*this);
}
m_context << endTag;
checker.check();
return false;
}
void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _catchClauses)
{
// Stack is empty.
ASTPointer<TryCatchClause> structured{};
ASTPointer<TryCatchClause> fallback{};
for (size_t i = 1; i < _catchClauses.size(); ++i)
if (_catchClauses[i]->errorName() == "Error")
structured = _catchClauses[i];
else if (_catchClauses[i]->errorName().empty())
fallback = _catchClauses[i];
else
solAssert(false, "");
solAssert(_catchClauses.size() == size_t(1 + (structured ? 1 : 0) + (fallback ? 1 : 0)), "");
eth::AssemblyItem endTag = m_context.newTag();
eth::AssemblyItem fallbackTag = m_context.newTag();
if (structured)
{
solAssert(
structured->parameters() &&
structured->parameters()->parameters().size() == 1 &&
structured->parameters()->parameters().front() &&
*structured->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
""
);
solAssert(m_context.evmVersion().supportsReturndata(), "");
string errorHash = FixedHash<4>(dev::keccak256("Error(string)")).hex();
// Try to decode the error message.
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
m_context << u256(0);
m_context.appendInlineAssembly(
Whiskers(R"({
data := mload(0x40)
mstore(data, 0)
for {} 1 {} {
if lt(returndatasize(), 0x44) { data := 0 break }
returndatacopy(0, 0, 4)
let sig := <getSig>
if iszero(eq(sig, 0x<ErrorSignature>)) { data := 0 break }
returndatacopy(data, 4, sub(returndatasize(), 4))
let offset := mload(data)
if or(
gt(offset, 0xffffffffffffffff),
gt(add(offset, 0x24), returndatasize())
) {
data := 0
break
}
let msg := add(data, offset)
let length := mload(msg)
if gt(length, 0xffffffffffffffff) { data := 0 break }
let end := add(add(msg, 0x20), length)
if gt(end, add(data, returndatasize())) { data := 0 break }
mstore(0x40, and(add(end, 0x1f), not(0x1f)))
data := msg
break
}
})")
("ErrorSignature", errorHash)
("getSig",
m_context.evmVersion().hasBitwiseShifting() ?
"shr(224, mload(0))" :
"div(mload(0), " + (u256(1) << 224).str() + ")"
).render(),
{"data"}
);
m_context << Instruction::DUP1;
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
m_context << Instruction::POP;
m_context.appendJumpTo(fallbackTag);
m_context.adjustStackOffset(1);
m_context << decodeSuccessTag;
structured->accept(*this);
m_context.appendJumpTo(endTag);
}
m_context << fallbackTag;
if (fallback)
{
if (fallback->parameters())
{
solAssert(m_context.evmVersion().supportsReturndata(), "");
solAssert(
fallback->parameters()->parameters().size() == 1 &&
fallback->parameters()->parameters().front() &&
*fallback->parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(),
""
);
CompilerUtils(m_context).returnDataToArray();
}
fallback->accept(*this);
}
else
{
// re-throw
if (m_context.evmVersion().supportsReturndata())
m_context.appendInlineAssembly(R"({
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
})");
else
m_context.appendRevert();
}
m_context << endTag;
}
bool ContractCompiler::visit(TryCatchClause const& _clause)
{
CompilerContext::LocationSetter locationSetter(m_context, _clause);
unsigned varSize = 0;
if (_clause.parameters())
for (ASTPointer<VariableDeclaration> const& varDecl: _clause.parameters()->parameters() | boost::adaptors::reversed)
{
solAssert(varDecl, "");
varSize += varDecl->annotation().type->sizeOnStack();
m_context.addVariable(*varDecl, varSize);
}
_clause.block().accept(*this);
m_context.removeVariablesAboveStackHeight(m_context.stackHeight() - varSize);
CompilerUtils(m_context).popStackSlots(varSize);
return false;
}
bool ContractCompiler::visit(IfStatement const& _ifStatement)
{
StackHeightChecker checker(m_context);

View File

@ -104,6 +104,9 @@ private:
bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(TryStatement const& _tryStatement) override;
void handleCatch(std::vector<ASTPointer<TryCatchClause>> const& _catchClauses);
bool visit(TryCatchClause const& _clause) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override;

View File

@ -586,13 +586,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
break;
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
solAssert(!_functionCall.annotation().tryCall, "");
[[fallthrough]];
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
@ -620,12 +622,21 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else
m_context << u256(0);
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
// Check if zero (reverted)
m_context << Instruction::DUP1 << Instruction::ISZERO;
if (_functionCall.annotation().tryCall)
{
// If this is a try call, return "<address> 1" in the success case and
// "0" in the error case.
AssemblyItem errorCase = m_context.appendConditionalJump();
m_context << u256(1);
m_context << errorCase;
}
else
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
break;
}
case FunctionType::Kind::SetGas:
@ -675,7 +686,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true,
true
),
{}
{},
false
);
if (function.kind() == FunctionType::Kind::Transfer)
{
@ -835,7 +847,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << contractAddresses.at(function.kind());
for (unsigned i = function.sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i);
appendExternalFunctionCall(function, arguments);
solAssert(!_functionCall.annotation().tryCall, "");
appendExternalFunctionCall(function, arguments, false);
break;
}
case FunctionType::Kind::ByteArrayPush:
@ -1964,7 +1977,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
void ExpressionCompiler::appendExternalFunctionCall(
FunctionType const& _functionType,
vector<ASTPointer<Expression const>> const& _arguments
vector<ASTPointer<Expression const>> const& _arguments,
bool _tryCall
)
{
solAssert(
@ -2000,6 +2014,12 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
if (_tryCall)
{
solAssert(!returnSuccessConditionAndReturndata, "");
solAssert(!_functionType.isBareCall(), "");
}
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
bool dynamicReturnSize = false;
@ -2171,17 +2191,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
(_functionType.gasSet() ? 1 : 0) +
(!_functionType.isBareCall() ? 1 : 0);
if (returnSuccessConditionAndReturndata)
m_context << swapInstruction(remainsSize);
else
eth::AssemblyItem endTag = m_context.newTag();
if (!returnSuccessConditionAndReturndata && !_tryCall)
{
//Propagate error condition (if CALL pushes 0 on stack).
// Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(true);
}
else
m_context << swapInstruction(remainsSize);
utils().popStackSlots(remainsSize);
// Only success flag is remaining on stack.
if (_tryCall)
{
m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalJumpTo(endTag);
m_context << Instruction::POP;
}
if (returnSuccessConditionAndReturndata)
{
// success condition is already there
@ -2189,24 +2219,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// an internal helper function e.g. for ``send`` and ``transfer``. In that
// case we're only interested in the success condition, not the return data.
if (!_functionType.returnParameterTypes().empty())
{
if (haveReturndatacopy)
{
m_context << Instruction::RETURNDATASIZE;
m_context.appendInlineAssembly(R"({
switch v case 0 {
v := 0x60
} default {
v := mload(0x40)
mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
mstore(v, returndatasize())
returndatacopy(add(v, 0x20), 0, returndatasize())
}
})", {"v"});
}
else
utils().pushZeroPointer();
}
utils().returnDataToArray();
}
else if (funKind == FunctionType::Kind::RIPEMD160)
{
@ -2260,6 +2273,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().abiDecode(returnTypes, true);
}
if (_tryCall)
{
// Success branch will reach this, failure branch will directly jump to endTag.
m_context << u256(1);
m_context << endTag;
}
}
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)

View File

@ -96,9 +96,12 @@ private:
/// @}
/// Appends code to call a function of the given type with the given arguments.
/// @param _tryCall if true, this is the external call of a try statement. In that case,
/// returns success flag on top of stack and does not revert on failure.
void appendExternalFunctionCall(
FunctionType const& _functionType,
std::vector<ASTPointer<Expression const>> const& _arguments
std::vector<ASTPointer<Expression const>> const& _arguments,
bool _tryCall
);
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
/// expected to be on the stack and is updated by this call.

View File

@ -1099,13 +1099,15 @@ ASTPointer<Statement> Parser::parseStatement()
}
statement = nodeFactory.createNode<Return>(docString, expression);
break;
}
}
case Token::Throw:
{
statement = ASTNodeFactory(*this).createNode<Throw>(docString);
m_scanner->next();
break;
}
{
statement = ASTNodeFactory(*this).createNode<Throw>(docString);
m_scanner->next();
break;
}
case Token::Try:
return parseTryStatement(docString);
case Token::Assembly:
return parseInlineAssembly(docString);
case Token::Emit:
@ -1187,6 +1189,62 @@ ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _d
return nodeFactory.createNode<IfStatement>(_docString, condition, trueBody, falseBody);
}
ASTPointer<TryStatement> Parser::parseTryStatement(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Try);
ASTPointer<Expression> externalCall = parseExpression();
vector<ASTPointer<TryCatchClause>> clauses;
ASTNodeFactory successClauseFactory(*this);
ASTPointer<ParameterList> returnsParameters;
if (m_scanner->currentToken() == Token::Returns)
{
m_scanner->next();
VarDeclParserOptions options;
options.allowEmptyName = true;
options.allowLocationSpecifier = true;
returnsParameters = parseParameterList(options, false);
}
ASTPointer<Block> successBlock = parseBlock();
successClauseFactory.setEndPositionFromNode(successBlock);
clauses.emplace_back(successClauseFactory.createNode<TryCatchClause>(
make_shared<ASTString>(), returnsParameters, successBlock
));
do
{
clauses.emplace_back(parseCatchClause());
}
while (m_scanner->currentToken() == Token::Catch);
nodeFactory.setEndPositionFromNode(clauses.back());
return nodeFactory.createNode<TryStatement>(
_docString, externalCall, clauses
);
}
ASTPointer<TryCatchClause> Parser::parseCatchClause()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Catch);
ASTPointer<ASTString> errorName = make_shared<string>();
ASTPointer<ParameterList> errorParameters;
if (m_scanner->currentToken() != Token::LBrace)
{
if (m_scanner->currentToken() == Token::Identifier)
errorName = expectIdentifierToken();
VarDeclParserOptions options;
options.allowEmptyName = true;
options.allowLocationSpecifier = true;
errorParameters = parseParameterList(options, !errorName->empty());
}
ASTPointer<Block> block = parseBlock();
nodeFactory.setEndPositionFromNode(block);
return nodeFactory.createNode<TryCatchClause>(errorName, errorParameters, block);
}
ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);

View File

@ -119,6 +119,8 @@ private:
ASTPointer<Statement> parseStatement();
ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {});
ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<TryCatchClause> parseCatchClause();
ASTPointer<WhileStatement> parseWhileStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<WhileStatement> parseDoWhileStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<ForStatement> parseForStatement(ASTPointer<ASTString> const& _docString);

View File

@ -321,6 +321,7 @@
"names" : [],
"nodeType" : "FunctionCall",
"src" : "209:13:1",
"tryCall" : false,
"typeDescriptions" :
{
"typeIdentifier" : "t_address",
@ -444,6 +445,7 @@
"names" : [],
"nodeType" : "FunctionCall",
"src" : "239:10:1",
"tryCall" : false,
"typeDescriptions" :
{
"typeIdentifier" : "t_address_payable",

View File

@ -396,6 +396,7 @@
[
null
],
"tryCall" : false,
"type" : "address",
"type_conversion" : true
},
@ -526,6 +527,7 @@
[
null
],
"tryCall" : false,
"type" : "address payable",
"type_conversion" : true
},

View File

@ -14784,6 +14784,59 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser)
);
}
BOOST_AUTO_TEST_CASE(try_catch_library_call)
{
char const* sourceCode = R"(
library L {
struct S { uint x; }
function integer(uint t, bool b) public view returns (uint) {
if (b) {
return t;
} else {
revert("failure");
}
}
function stru(S storage t, bool b) public view returns (uint) {
if (b) {
return t.x;
} else {
revert("failure");
}
}
}
contract C {
using L for L.S;
L.S t;
function f(bool b) public returns (uint, string memory) {
uint x = 8;
try L.integer(x, b) returns (uint _x) {
return (_x, "");
} catch Error(string memory message) {
return (18, message);
}
}
function g(bool b) public returns (uint, string memory) {
t.x = 9;
try t.stru(b) returns (uint x) {
return (x, "");
} catch Error(string memory message) {
return (19, message);
}
}
}
)";
if (dev::test::Options::get().evmVersion().supportsReturndata())
{
compileAndRun(sourceCode, 0, "L", bytes());
compileAndRun(sourceCode, 0, "C", bytes(), map<string, Address>{{"L", m_contractAddress}});
ABI_CHECK(callContractFunction("f(bool)", true), encodeArgs(8, 0x40, 0));
ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(18, 0x40, 7, "failure"));
ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(9, 0x40, 0));
ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs(19, 0x40, 7, "failure"));
}
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -0,0 +1,32 @@
contract Reverts {
constructor(uint) public { revert("test message."); }
}
contract Succeeds {
constructor(uint) public { }
}
contract C {
function f() public returns (Reverts x, uint, string memory txt) {
uint i = 3;
try new Reverts(i) returns (Reverts r) {
x = r;
txt = "success";
} catch Error(string memory s) {
txt = s;
}
}
function g() public returns (Succeeds x, uint, string memory txt) {
uint i = 8;
try new Succeeds(i) returns (Succeeds r) {
x = r;
txt = "success";
} catch Error(string memory s) {
txt = s;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f() -> 0, 0, 96, 13, "test message."
// g() -> 0xf01f7809444bd9a93a854361c6fae3f23d9e23db, 0, 96, 7, "success"

View File

@ -0,0 +1,168 @@
contract C {
function g(bytes memory revertMsg) public pure returns (uint, uint) {
assembly { revert(add(revertMsg, 0x20), mload(revertMsg)) }
}
function f1() public returns (uint x) {
// Invalid signature
try this.g(abi.encodeWithSelector(0x12345678, uint(0), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch (bytes memory) {
return 2;
}
}
function f1a() public returns (uint x) {
// Invalid signature
try this.g(abi.encodeWithSelector(0x12345678, uint(0), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch {
return 2;
}
}
function f1b() public returns (uint x) {
// Invalid signature
try this.g(abi.encodeWithSelector(0x12345678, uint(0), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
}
}
function f1c() public returns (uint x) {
// Invalid signature
try this.g(abi.encodeWithSelector(0x12345678, uint(0), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch {
return 2;
}
}
function f2() public returns (uint x) {
// Valid signature but illegal offset
try this.g(abi.encodeWithSignature("Error(string)", uint(0x100), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch (bytes memory) {
return 2;
}
}
function f2a() public returns (uint x) {
// Valid signature but illegal offset
try this.g(abi.encodeWithSignature("Error(string)", uint(0x100), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch {
return 2;
}
}
function f2b() public returns (uint x) {
// Valid signature but illegal offset
try this.g(abi.encodeWithSignature("Error(string)", uint(0x100), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
}
}
function f2c() public returns (uint x) {
// Valid signature but illegal offset
try this.g(abi.encodeWithSignature("Error(string)", uint(0x100), uint(0), uint(0))) returns (uint a, uint b) {
return 0;
} catch {
return 1;
}
}
function f3() public returns (uint x) {
// Valid up to length
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x30), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch (bytes memory) {
return 2;
}
}
function f3a() public returns (uint x) {
// Valid up to length
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x30), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch (bytes memory) {
return 2;
}
}
function f3b() public returns (uint x) {
// Valid up to length
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x30), uint(0))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
}
}
function f3c() public returns (uint x) {
// Valid up to length
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x30), uint(0))) returns (uint a, uint b) {
return 0;
} catch {
return 1;
}
}
function f4() public returns (uint x) {
// Fully valid
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x7), bytes7("abcdefg"))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch (bytes memory) {
return 2;
}
}
function f4a() public returns (uint x) {
// Fully valid
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x7), bytes7("abcdefg"))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
} catch {
return 2;
}
}
function f4b() public returns (uint x) {
// Fully valid
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x7), bytes7("abcdefg"))) returns (uint a, uint b) {
return 0;
} catch Error(string memory) {
return 1;
}
}
function f4c() public returns (uint x) {
// Fully valid
try this.g(abi.encodeWithSignature("Error(string)", uint(0x20), uint(0x7), bytes7("abcdefg"))) returns (uint a, uint b) {
return 0;
} catch {
return 1;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f1() -> 2
// f1a() -> 2
// f1b() -> FAILURE, hex"12345678", 0x0, 0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
// f1c() -> 2
// f2() -> 2
// f2a() -> 2
// f2b() -> FAILURE, hex"08c379a0", 0x100, 0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
// f2c() -> 1
// f3() -> 2
// f3a() -> 2
// f3b() -> FAILURE, hex"08c379a0", 0x20, 48, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
// f3c() -> 1
// f4() -> 1
// f4a() -> 1
// f4b() -> 1
// f4c() -> 1

View File

@ -0,0 +1,18 @@
contract C {
function g(bool b) public pure returns (uint, uint) {
require(b, "message");
return (1, 2);
}
function f(bool b) public returns (uint x, uint y, bytes memory txt) {
try this.g(b) returns (uint a, uint b) {
(x, y) = (a, b);
} catch (bytes memory s) {
txt = s;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f(bool): true -> 1, 2, 96, 0
// f(bool): false -> 0, 0, 96, 100, 0x8c379a000000000000000000000000000000000000000000000000000000000, 0x2000000000000000000000000000000000000000000000000000000000, 0x76d657373616765000000000000000000000000000000000000000000, 0

View File

@ -0,0 +1,33 @@
contract C {
function g(bool b) public pure returns (uint, uint) {
require(b, "failure");
return (1, 2);
}
function f(bool cond1, bool cond2) public returns (uint x, uint y, bytes memory txt) {
try this.g(cond1) returns (uint a, uint b) {
try this.g(cond2) returns (uint a2, uint b2) {
(x, y) = (a, b);
txt = "success";
} catch Error(string memory s) {
x = 12;
txt = bytes(s);
} catch (bytes memory s) {
x = 13;
txt = s;
}
} catch Error(string memory s) {
x = 99;
txt = bytes(s);
} catch (bytes memory s) {
x = 98;
txt = s;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f(bool,bool): true, true -> 1, 2, 96, 7, "success"
// f(bool,bool): true, false -> 12, 0, 96, 7, "failure"
// f(bool,bool): false, true -> 99, 0, 96, 7, "failure"
// f(bool,bool): false, false -> 99, 0, 96, 7, "failure"

View File

@ -0,0 +1,17 @@
contract C {
function g() public returns (uint a, function() external h, uint b) {
a = 1;
h = this.fun;
b = 9;
}
function f() public returns (uint, function() external, uint) {
// Note that the function type uses two stack slots.
try this.g() returns (uint a, function() external h, uint b) {
return (a, h, b);
} catch {
}
}
function fun() public pure {}
}
// ----
// f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9

View File

@ -0,0 +1,18 @@
contract C {
function g(bool b) public pure returns (uint, uint) {
require(b);
return (1, 2);
}
function f(bool b) public returns (uint x, uint y) {
try this.g(b) returns (uint a, uint b) {
(x, y) = (a, b);
} catch {
(x, y) = (9, 10);
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f(bool): true -> 1, 2
// f(bool): false -> 9, 10

View File

@ -0,0 +1,19 @@
contract C {
function g(bool b) public pure returns (uint, uint) {
require(b, "message");
return (1, 2);
}
function f(bool b) public returns (uint x, uint y, string memory txt) {
try this.g(b) returns (uint a, uint b) {
(x, y) = (a, b);
txt = "success";
} catch Error(string memory s) {
txt = s;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f(bool): true -> 1, 2, 0x60, 7, "success"
// f(bool): false -> 0, 0, 0x60, 7, "message"

View File

@ -0,0 +1,23 @@
contract C {
function g(bool b) public pure returns (uint, uint) {
require(b, "message longer than 32 bytes 32 bytes 32 bytes 32 bytes 32 bytes 32 bytes 32 bytes");
return (1, 2);
}
function f(bool cond) public returns (uint x, uint y, bytes memory txt) {
try this.g(cond) returns (uint a, uint b) {
(x, y) = (a, b);
txt = "success";
} catch Error(string memory s) {
x = 99;
txt = bytes(s);
} catch (bytes memory s) {
x = 98;
txt = s;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f(bool): true -> 1, 2, 96, 7, "success"
// f(bool): false -> 99, 0, 96, 82, "message longer than 32 bytes 32 ", "bytes 32 bytes 32 bytes 32 bytes", " 32 bytes 32 bytes"

View File

@ -0,0 +1,16 @@
contract C {
function g(bool x) public pure {
require(x);
}
function f(bool x) public returns (uint) {
// Set the gas to make this work on pre-byzantium VMs
try this.g.gas(8000)(x) {
return 1;
} catch {
return 2;
}
}
}
// ----
// f(bool): true -> 1
// f(bool): false -> 2

View File

@ -0,0 +1,34 @@
contract C {
struct S { bool f; }
S s;
function ext() external {}
function f() internal returns (S storage r)
{
try this.ext() { }
catch (bytes memory) { r = s; }
}
function g() internal returns (S storage r)
{
try this.ext() { r = s; }
catch (bytes memory) { }
}
function h() internal returns (S storage r)
{
try this.ext() {}
catch Error (string memory) { r = s; }
catch (bytes memory) { r = s; }
}
function i() internal returns (S storage r)
{
try this.ext() { r = s; }
catch (bytes memory) { return r; }
r = s;
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError: (113-124): This variable is of storage pointer type and can be returned without prior assignment.
// TypeError: (240-251): This variable is of storage pointer type and can be returned without prior assignment.
// TypeError: (367-378): This variable is of storage pointer type and can be returned without prior assignment.
// TypeError: (631-632): This variable is of storage pointer type and can be accessed without prior assignment.

View File

@ -0,0 +1,24 @@
contract C {
struct S { bool f; }
S s;
function ext() external { }
function f() internal returns (S storage r)
{
try this.ext() { r = s; }
catch (bytes memory) { r = s; }
}
function g() internal returns (S storage r)
{
try this.ext() { r = s; }
catch Error (string memory) { r = s; }
catch (bytes memory) { r = s; }
}
function h() internal returns (S storage r)
{
try this.ext() { }
catch (bytes memory) { }
r = s;
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error(string memory) {
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error(string memory x) {
x;
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch (bytes memory x) {
x;
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch () {
}
}
}
// ----
// ParserError: (101-102): Expected type name

View File

@ -0,0 +1,11 @@
contract C {
function f() public {
try this.f() returns () {
} catch {
}
}
}
// ----
// ParserError: (69-70): Expected type name

View File

@ -0,0 +1,12 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error(uint) {
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError: (94-123): Expected `catch Error(string memory ...) { ... }`.

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error2() {
} catch abc() {
}
}
}
// ----
// TypeError: (93-119): Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`.
// TypeError: (120-143): Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`.

View File

@ -0,0 +1,13 @@
contract C {
function f() public returns (uint8, uint) {
// Implicitly convertible, but not exactly the same type.
try this.f() returns (uint, int x) {
} catch {
}
}
}
// ----
// TypeError: (157-161): Invalid type, expected uint8 but got uint256.
// TypeError: (163-168): Invalid type, expected uint256 but got int256.

View File

@ -0,0 +1,40 @@
library L {
struct S { uint x; }
function integer(uint t, bool b) public pure returns (uint) {
if (b) {
return t;
} else {
revert("failure");
}
}
function stru(S storage t, bool b) public view returns (uint) {
if (b) {
return t.x;
} else {
revert("failure");
}
}
}
contract C {
using L for L.S;
L.S t;
function f(bool b) public pure returns (uint, string memory) {
uint x = 8;
try L.integer(x, b) returns (uint _x) {
return (_x, "");
} catch Error(string memory message) {
return (18, message);
}
}
function g(bool b) public returns (uint, string memory) {
t.x = 9;
try t.stru(b) returns (uint x) {
return (x, "");
} catch Error(string memory message) {
return (19, message);
}
}
}
// ====
// EVMVersion: >=byzantium
// ----

View File

@ -0,0 +1,13 @@
contract C {
function f() public {
try this.f() {
} catch (bytes memory) {
}
}
}
// ====
// EVMVersion: <byzantium
// ----
// TypeError: (73-106): This catch clause type cannot be used on the selected EVM version (homestead). You need at least a Byzantium-compatible EVM or use `catch { ... }`.

View File

@ -0,0 +1,12 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch (uint) {
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError: (94-118): Expected `catch (bytes memory ...) { ... }` or `catch { ... }`.

View File

@ -0,0 +1,8 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
}
}
}
// ----
// ParserError: (97-98): Expected reserved keyword 'catch' but got '}'

View File

@ -0,0 +1,9 @@
contract C {
function f() public returns (uint, uint) {
try f() {
} catch {
}
}
}
// ----
// TypeError: (72-75): Try can only be used with external function calls and contract creation calls.

View File

@ -0,0 +1,9 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch {
}
}
}

View File

@ -0,0 +1,10 @@
contract C {
function f() public returns (uint, uint) {
try this.f() returns (uint a, uint b) {
a = 1;
b = 2;
} catch {
}
}
}

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint[] memory, uint) {
try this.f() returns (uint[] memory x, uint y) {
return (x, y);
} catch {
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,11 @@
contract C {
function f() public returns (uint[] memory, uint) {
try this.f() returns (uint[] memory, uint) {
} catch {
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,9 @@
contract C {
function f() public {
try this.f() {
} catch {
}
}
}

View File

@ -0,0 +1,13 @@
contract C {
function f() public {
try this.f() {
} catch Error(string memory) {
}
}
}
// ====
// EVMVersion: <byzantium
// ----
// TypeError: (73-112): This catch clause type cannot be used on the selected EVM version (homestead). You need at least a Byzantium-compatible EVM or use `catch { ... }`.

View File

@ -0,0 +1,14 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error(string memory x) {
x;
} catch (bytes memory x) {
x;
}
}
}
// ====
// EVMVersion: >=byzantium

View File

@ -0,0 +1,15 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch Error(string memory x) {
x;
} catch Error(string memory y) {
y;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError: (150-205): This try statement already has an "Error" catch clause.

View File

@ -0,0 +1,14 @@
contract C {
function f() public returns (uint, uint) {
try this.f() {
} catch {
} catch (bytes memory y) {
y;
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError: (112-161): This try statement already has a low-level catch clause.

View File

@ -0,0 +1,18 @@
contract test {
function f() public returns (uint b) {
try this.f() returns (uint a) {
} catch Error(string memory message) {
} catch (bytes memory error) {
}
}
}
// ====
// EVMVersion: >=byzantium
// ----
// Warning: (49-55): Unused function parameter. Remove or comment out the variable name to silence this warning.
// Warning: (89-95): Unused try/catch parameter. Remove or comment out the variable name to silence this warning.
// Warning: (122-143): Unused try/catch parameter. Remove or comment out the variable name to silence this warning.
// Warning: (165-183): Unused try/catch parameter. Remove or comment out the variable name to silence this warning.