mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Catch panic.
This commit is contained in:
@@ -997,6 +997,7 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
||||
}
|
||||
}
|
||||
|
||||
TryCatchClause const* panicClause = nullptr;
|
||||
TryCatchClause const* errorClause = nullptr;
|
||||
TryCatchClause const* lowLevelClause = nullptr;
|
||||
for (size_t i = 1; i < _tryStatement.clauses().size(); ++i)
|
||||
@@ -1029,7 +1030,7 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (clause.errorName() == "Error")
|
||||
else if (clause.errorName() == "Error" || clause.errorName() == "Panic")
|
||||
{
|
||||
if (!m_evmVersion.supportsReturndata())
|
||||
m_errorReporter.typeError(
|
||||
@@ -1040,26 +1041,46 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
||||
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
|
||||
);
|
||||
|
||||
if (errorClause)
|
||||
m_errorReporter.typeError(
|
||||
1036_error,
|
||||
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(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
|
||||
if (clause.errorName() == "Error")
|
||||
{
|
||||
if (errorClause)
|
||||
m_errorReporter.typeError(
|
||||
1036_error,
|
||||
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(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (panicClause)
|
||||
m_errorReporter.typeError(
|
||||
6732_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", panicClause->location()),
|
||||
"This try statement already has a \"Panic\" catch clause."
|
||||
);
|
||||
panicClause = &clause;
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != 1 ||
|
||||
*clause.parameters()->parameters().front()->type() != *TypeProvider::uint256()
|
||||
)
|
||||
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
|
||||
}
|
||||
}
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
3542_error,
|
||||
clause.location(),
|
||||
"Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`."
|
||||
"Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,7 +841,15 @@ TryCatchClause const* TryStatement::successClause() const
|
||||
return m_clauses[0].get();
|
||||
}
|
||||
|
||||
TryCatchClause const* TryStatement::structuredClause() const
|
||||
TryCatchClause const* TryStatement::panicClause() const
|
||||
{
|
||||
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||
if (m_clauses[i]->errorName() == "Panic")
|
||||
return m_clauses[i].get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TryCatchClause const* TryStatement::errorClause() const
|
||||
{
|
||||
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||
if (m_clauses[i]->errorName() == "Error")
|
||||
|
||||
@@ -1494,6 +1494,8 @@ private:
|
||||
* Syntax:
|
||||
* try <call> returns (uint x, uint y) {
|
||||
* // success code
|
||||
* } catch Panic(uint errorCode) {
|
||||
* // panic
|
||||
* } catch Error(string memory cause) {
|
||||
* // error code, reason provided
|
||||
* } catch (bytes memory lowLevelData) {
|
||||
@@ -1524,7 +1526,8 @@ public:
|
||||
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
||||
|
||||
TryCatchClause const* successClause() const;
|
||||
TryCatchClause const* structuredClause() const;
|
||||
TryCatchClause const* panicClause() const;
|
||||
TryCatchClause const* errorClause() const;
|
||||
TryCatchClause const* fallbackClause() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -971,31 +971,46 @@ bool ContractCompiler::visit(TryStatement const& _tryStatement)
|
||||
void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _catchClauses)
|
||||
{
|
||||
// Stack is empty.
|
||||
ASTPointer<TryCatchClause> structured{};
|
||||
ASTPointer<TryCatchClause> error{};
|
||||
ASTPointer<TryCatchClause> panic{};
|
||||
ASTPointer<TryCatchClause> fallback{};
|
||||
for (size_t i = 1; i < _catchClauses.size(); ++i)
|
||||
if (_catchClauses[i]->errorName() == "Error")
|
||||
structured = _catchClauses[i];
|
||||
error = _catchClauses[i];
|
||||
else if (_catchClauses[i]->errorName() == "Panic")
|
||||
panic = _catchClauses[i];
|
||||
else if (_catchClauses[i]->errorName().empty())
|
||||
fallback = _catchClauses[i];
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(_catchClauses.size() == 1ul + (structured ? 1 : 0) + (fallback ? 1 : 0), "");
|
||||
solAssert(_catchClauses.size() == 1ul + (error ? 1 : 0) + (panic ? 1 : 0) + (fallback ? 1 : 0), "");
|
||||
|
||||
evmasm::AssemblyItem endTag = m_context.newTag();
|
||||
evmasm::AssemblyItem fallbackTag = m_context.newTag();
|
||||
if (structured)
|
||||
evmasm::AssemblyItem panicTag = m_context.newTag();
|
||||
if (error || panic)
|
||||
// Note that this function returns zero on failure, which is not a problem yet,
|
||||
// but will be a problem once we allow user-defined errors.
|
||||
m_context.callYulFunction(m_context.utilFunctions().returnDataSelectorFunction(), 0, 1);
|
||||
// stack: <selector>
|
||||
if (error)
|
||||
{
|
||||
solAssert(
|
||||
structured->parameters() &&
|
||||
structured->parameters()->parameters().size() == 1 &&
|
||||
structured->parameters()->parameters().front() &&
|
||||
*structured->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
|
||||
error->parameters() &&
|
||||
error->parameters()->parameters().size() == 1 &&
|
||||
error->parameters()->parameters().front() &&
|
||||
*error->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
|
||||
""
|
||||
);
|
||||
solAssert(m_context.evmVersion().supportsReturndata(), "");
|
||||
|
||||
// stack: <selector>
|
||||
m_context << Instruction::DUP1 << selectorFromSignature32("Error(string)") << Instruction::EQ;
|
||||
m_context << Instruction::ISZERO;
|
||||
m_context.appendConditionalJumpTo(panicTag);
|
||||
m_context << Instruction::POP; // remove selector
|
||||
|
||||
// Try to decode the error message.
|
||||
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||
m_context.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
|
||||
@@ -1006,9 +1021,42 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
||||
m_context.adjustStackOffset(1);
|
||||
|
||||
m_context << decodeSuccessTag;
|
||||
structured->accept(*this);
|
||||
error->accept(*this);
|
||||
m_context.appendJumpTo(endTag);
|
||||
m_context.adjustStackOffset(1);
|
||||
}
|
||||
m_context << panicTag;
|
||||
if (panic)
|
||||
{
|
||||
solAssert(
|
||||
panic->parameters() &&
|
||||
panic->parameters()->parameters().size() == 1 &&
|
||||
panic->parameters()->parameters().front() &&
|
||||
*panic->parameters()->parameters().front()->annotation().type == *TypeProvider::uint256(),
|
||||
""
|
||||
);
|
||||
solAssert(m_context.evmVersion().supportsReturndata(), "");
|
||||
|
||||
// stack: <selector>
|
||||
m_context << selectorFromSignature32("Panic(uint256)") << Instruction::EQ;
|
||||
m_context << Instruction::ISZERO;
|
||||
m_context.appendConditionalJumpTo(fallbackTag);
|
||||
|
||||
m_context.callYulFunction(m_context.utilFunctions().tryDecodePanicDataFunction(), 0, 2);
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: <code> <success>
|
||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||
m_context << Instruction::POP;
|
||||
m_context.appendJumpTo(fallbackTag);
|
||||
m_context.adjustStackOffset(1);
|
||||
|
||||
m_context << decodeSuccessTag;
|
||||
panic->accept(*this);
|
||||
m_context.appendJumpTo(endTag);
|
||||
m_context.adjustStackOffset(1);
|
||||
}
|
||||
if (error || panic)
|
||||
m_context << Instruction::POP; // selector
|
||||
m_context << fallbackTag;
|
||||
if (fallback)
|
||||
{
|
||||
|
||||
@@ -3985,19 +3985,36 @@ string YulUtilFunctions::panicFunction(util::PanicCode _code)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::returnDataSelectorFunction()
|
||||
{
|
||||
string const functionName = "return_data_selector";
|
||||
solAssert(m_evmVersion.supportsReturndata(), "");
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return util::Whiskers(R"(
|
||||
function <functionName>() -> sig {
|
||||
if gt(returndatasize(), 3) {
|
||||
returndatacopy(0, 0, 4)
|
||||
sig := <shr224>(mload(0))
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr224", shiftRightFunction(224))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::tryDecodeErrorMessageFunction()
|
||||
{
|
||||
string const functionName = "try_decode_error_message";
|
||||
solAssert(m_evmVersion.supportsReturndata(), "");
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return util::Whiskers(R"(
|
||||
function <functionName>() -> ret {
|
||||
if lt(returndatasize(), 0x44) { leave }
|
||||
|
||||
returndatacopy(0, 0, 4)
|
||||
let sig := <shr224>(mload(0))
|
||||
if iszero(eq(sig, 0x<ErrorSignature>)) { leave }
|
||||
|
||||
let data := mload(<freeMemoryPointer>)
|
||||
returndatacopy(data, 4, sub(returndatasize(), 4))
|
||||
|
||||
@@ -4021,14 +4038,32 @@ string YulUtilFunctions::tryDecodeErrorMessageFunction()
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr224", shiftRightFunction(224))
|
||||
("ErrorSignature", FixedHash<4>(util::keccak256("Error(string)")).hex())
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("roundUp", roundUpFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::tryDecodePanicDataFunction()
|
||||
{
|
||||
string const functionName = "try_decode_panic_data";
|
||||
solAssert(m_evmVersion.supportsReturndata(), "");
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return util::Whiskers(R"(
|
||||
function <functionName>() -> success, data {
|
||||
if gt(returndatasize(), 0x23) {
|
||||
returndatacopy(0, 4, 0x20)
|
||||
success := 1
|
||||
data := mload(0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::extractReturndataFunction()
|
||||
{
|
||||
string const functionName = "extract_returndata";
|
||||
|
||||
@@ -440,13 +440,21 @@ public:
|
||||
/// Reverts with ``Panic(uint256)`` and the given code.
|
||||
std::string panicFunction(util::PanicCode _code);
|
||||
|
||||
/// Returns the name of a function that decodes an error message.
|
||||
/// signature: () -> arrayPtr
|
||||
///
|
||||
/// Returns a newly allocated `bytes memory` array containing the decoded error message
|
||||
/// or 0 on failure.
|
||||
/// @returns the name of a function that returns the return data selector.
|
||||
/// Returns zero if return data is too short.
|
||||
std::string returnDataSelectorFunction();
|
||||
|
||||
/// @returns the name of a function that tries to abi-decode a string from offset 4 in the
|
||||
/// return data. On failure, returns 0, otherwise a pointer to the newly allocated string.
|
||||
/// Does not check the return data signature.
|
||||
/// signature: () -> ptr
|
||||
std::string tryDecodeErrorMessageFunction();
|
||||
|
||||
/// @returns the name of a function that tries to abi-decode a uint256 value from offset 4 in the
|
||||
/// return data.
|
||||
/// Does not check the return data signature.
|
||||
/// signature: () -> success, value
|
||||
std::string tryDecodePanicDataFunction();
|
||||
|
||||
/// Returns a function name that returns a newly allocated `bytes` array that contains the return data.
|
||||
///
|
||||
|
||||
@@ -2969,46 +2969,53 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||
|
||||
void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
|
||||
{
|
||||
if (_tryStatement.structuredClause())
|
||||
handleCatchStructuredAndFallback(*_tryStatement.structuredClause(), _tryStatement.fallbackClause());
|
||||
else if (_tryStatement.fallbackClause())
|
||||
handleCatchFallback(*_tryStatement.fallbackClause());
|
||||
else
|
||||
rethrow();
|
||||
}
|
||||
string const runFallback = m_context.newYulVariable();
|
||||
m_code << "let " << runFallback << " := 1\n";
|
||||
|
||||
void IRGeneratorForStatements::handleCatchStructuredAndFallback(
|
||||
TryCatchClause const& _structured,
|
||||
TryCatchClause const* _fallback
|
||||
)
|
||||
{
|
||||
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(), "");
|
||||
// This function returns zero on "short returndata". We have to add a success flag
|
||||
// once we implement custom error codes.
|
||||
if (_tryStatement.errorClause() || _tryStatement.panicClause())
|
||||
m_code << "switch " << m_utils.returnDataSelectorFunction() << "()\n";
|
||||
|
||||
// Try to decode the error message.
|
||||
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||
string const dataVariable = m_context.newYulVariable();
|
||||
|
||||
m_code << "let " << dataVariable << " := " << m_utils.tryDecodeErrorMessageFunction() << "()\n";
|
||||
m_code << "switch iszero(" << dataVariable << ") \n";
|
||||
m_code << "case 0 { // decoding success\n";
|
||||
if (_structured.parameters())
|
||||
if (TryCatchClause const* errorClause = _tryStatement.errorClause())
|
||||
{
|
||||
solAssert(_structured.parameters()->parameters().size() == 1, "");
|
||||
IRVariable const& var = m_context.addLocalVariable(*_structured.parameters()->parameters().front());
|
||||
define(var) << dataVariable << "\n";
|
||||
m_code << "case " << selectorFromSignature32("Error(string)") << " {\n";
|
||||
string const dataVariable = m_context.newYulVariable();
|
||||
m_code << "let " << dataVariable << " := " << m_utils.tryDecodeErrorMessageFunction() << "()\n";
|
||||
m_code << "if " << dataVariable << " {\n";
|
||||
m_code << runFallback << " := 0\n";
|
||||
if (errorClause->parameters())
|
||||
{
|
||||
solAssert(errorClause->parameters()->parameters().size() == 1, "");
|
||||
IRVariable const& var = m_context.addLocalVariable(*errorClause->parameters()->parameters().front());
|
||||
define(var) << dataVariable << "\n";
|
||||
}
|
||||
errorClause->accept(*this);
|
||||
m_code << "}\n";
|
||||
m_code << "}\n";
|
||||
}
|
||||
_structured.accept(*this);
|
||||
m_code << "}\n";
|
||||
m_code << "default { // decoding failure\n";
|
||||
if (_fallback)
|
||||
handleCatchFallback(*_fallback);
|
||||
if (TryCatchClause const* panicClause = _tryStatement.panicClause())
|
||||
{
|
||||
m_code << "case " << selectorFromSignature32("Panic(uint256)") << " {\n";
|
||||
string const success = m_context.newYulVariable();
|
||||
string const code = m_context.newYulVariable();
|
||||
m_code << "let " << success << ", " << code << " := " << m_utils.tryDecodePanicDataFunction() << "()\n";
|
||||
m_code << "if " << success << " {\n";
|
||||
m_code << runFallback << " := 0\n";
|
||||
if (panicClause->parameters())
|
||||
{
|
||||
solAssert(panicClause->parameters()->parameters().size() == 1, "");
|
||||
IRVariable const& var = m_context.addLocalVariable(*panicClause->parameters()->parameters().front());
|
||||
define(var) << code << "\n";
|
||||
}
|
||||
panicClause->accept(*this);
|
||||
m_code << "}\n";
|
||||
m_code << "}\n";
|
||||
}
|
||||
|
||||
m_code << "if " << runFallback << " {\n";
|
||||
if (_tryStatement.fallbackClause())
|
||||
handleCatchFallback(*_tryStatement.fallbackClause());
|
||||
else
|
||||
rethrow();
|
||||
m_code << "}\n";
|
||||
|
||||
@@ -100,10 +100,6 @@ public:
|
||||
private:
|
||||
/// Handles all catch cases of a try statement, except the success-case.
|
||||
void handleCatch(TryStatement const& _tryStatement);
|
||||
void handleCatchStructuredAndFallback(
|
||||
TryCatchClause const& _structured,
|
||||
TryCatchClause const* _fallback
|
||||
);
|
||||
void handleCatchFallback(TryCatchClause const& _fallback);
|
||||
|
||||
/// Generates code to rethrow an exception.
|
||||
|
||||
Reference in New Issue
Block a user