mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7328 from ethereum/tryCatch
Try and Catch for external calls.
This commit is contained in:
commit
63eb097c3e
@ -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
|
||||
===================
|
||||
|
||||
|
@ -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).
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -388,7 +388,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
||||
", ",
|
||||
" or "
|
||||
);
|
||||
if (_variable.isCallableParameter())
|
||||
if (_variable.isCallableOrCatchParameter())
|
||||
errorString +=
|
||||
" for " +
|
||||
string(_variable.isReturnParameter() ? "return " : "") +
|
||||
|
@ -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.");
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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).
|
||||
*/
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ class Statement;
|
||||
class Block;
|
||||
class PlaceholderStatement;
|
||||
class IfStatement;
|
||||
class TryCatchClause;
|
||||
class TryStatement;
|
||||
class BreakableStatement;
|
||||
class WhileStatement;
|
||||
class ForStatement;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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))
|
||||
|
@ -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), "");
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
32
test/libsolidity/semanticTests/tryCatch/create.sol
Normal file
32
test/libsolidity/semanticTests/tryCatch/create.sol
Normal 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"
|
@ -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
|
18
test/libsolidity/semanticTests/tryCatch/lowLevel.sol
Normal file
18
test/libsolidity/semanticTests/tryCatch/lowLevel.sol
Normal 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
|
33
test/libsolidity/semanticTests/tryCatch/nested.sol
Normal file
33
test/libsolidity/semanticTests/tryCatch/nested.sol
Normal 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"
|
17
test/libsolidity/semanticTests/tryCatch/return_function.sol
Normal file
17
test/libsolidity/semanticTests/tryCatch/return_function.sol
Normal 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
|
18
test/libsolidity/semanticTests/tryCatch/simple.sol
Normal file
18
test/libsolidity/semanticTests/tryCatch/simple.sol
Normal 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
|
19
test/libsolidity/semanticTests/tryCatch/structured.sol
Normal file
19
test/libsolidity/semanticTests/tryCatch/structured.sol
Normal 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"
|
@ -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"
|
16
test/libsolidity/semanticTests/tryCatch/trivial.sol
Normal file
16
test/libsolidity/semanticTests/tryCatch/trivial.sol
Normal 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
|
@ -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.
|
@ -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
|
11
test/libsolidity/syntaxTests/tryCatch/catch_error.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/catch_error.sol
Normal file
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public returns (uint, uint) {
|
||||
try this.f() {
|
||||
|
||||
} catch Error(string memory) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=byzantium
|
11
test/libsolidity/syntaxTests/tryCatch/catch_error_named.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/catch_error_named.sol
Normal file
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public returns (uint, uint) {
|
||||
try this.f() {
|
||||
|
||||
} catch Error(string memory x) {
|
||||
x;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=byzantium
|
11
test/libsolidity/syntaxTests/tryCatch/catch_low_level.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/catch_low_level.sol
Normal file
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public returns (uint, uint) {
|
||||
try this.f() {
|
||||
|
||||
} catch (bytes memory x) {
|
||||
x;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=byzantium
|
11
test/libsolidity/syntaxTests/tryCatch/empty_catch.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/empty_catch.sol
Normal file
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public returns (uint, uint) {
|
||||
try this.f() {
|
||||
|
||||
} catch () {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// ParserError: (101-102): Expected type name
|
11
test/libsolidity/syntaxTests/tryCatch/empty_returns.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/empty_returns.sol
Normal file
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public {
|
||||
try this.f() returns () {
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// ParserError: (69-70): Expected type name
|
@ -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 ...) { ... }`.
|
11
test/libsolidity/syntaxTests/tryCatch/invalid_error_name.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/invalid_error_name.sol
Normal 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(...)`.
|
13
test/libsolidity/syntaxTests/tryCatch/invalid_returns.sol
Normal file
13
test/libsolidity/syntaxTests/tryCatch/invalid_returns.sol
Normal 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.
|
40
test/libsolidity/syntaxTests/tryCatch/library_call.sol
Normal file
40
test/libsolidity/syntaxTests/tryCatch/library_call.sol
Normal 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
|
||||
// ----
|
@ -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 { ... }`.
|
@ -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 { ... }`.
|
8
test/libsolidity/syntaxTests/tryCatch/no_catch.sol
Normal file
8
test/libsolidity/syntaxTests/tryCatch/no_catch.sol
Normal 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 '}'
|
@ -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.
|
9
test/libsolidity/syntaxTests/tryCatch/no_returns.sol
Normal file
9
test/libsolidity/syntaxTests/tryCatch/no_returns.sol
Normal file
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f() public returns (uint, uint) {
|
||||
try this.f() {
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
10
test/libsolidity/syntaxTests/tryCatch/returns.sol
Normal file
10
test/libsolidity/syntaxTests/tryCatch/returns.sol
Normal 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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
11
test/libsolidity/syntaxTests/tryCatch/returns_memory.sol
Normal file
11
test/libsolidity/syntaxTests/tryCatch/returns_memory.sol
Normal 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
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public returns (uint[] memory, uint) {
|
||||
try this.f() returns (uint[] memory, uint) {
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=byzantium
|
9
test/libsolidity/syntaxTests/tryCatch/simple_catch.sol
Normal file
9
test/libsolidity/syntaxTests/tryCatch/simple_catch.sol
Normal file
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f() public {
|
||||
try this.f() {
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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 { ... }`.
|
14
test/libsolidity/syntaxTests/tryCatch/two_catch_clauses.sol
Normal file
14
test/libsolidity/syntaxTests/tryCatch/two_catch_clauses.sol
Normal 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
|
@ -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.
|
@ -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.
|
18
test/libsolidity/syntaxTests/unusedVariables/try_catch.sol
Normal file
18
test/libsolidity/syntaxTests/unusedVariables/try_catch.sol
Normal 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.
|
Loading…
Reference in New Issue
Block a user