Catch panic.

This commit is contained in:
chriseth
2020-12-22 11:08:44 +01:00
parent b78443ac75
commit b965446182
21 changed files with 619 additions and 80 deletions
+37 -16
View File
@@ -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(...)`."
);
}
}
+9 -1
View File
@@ -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")
+4 -1
View File
@@ -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:
+57 -9
View File
@@ -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)
{
+41 -6
View File
@@ -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";
+13 -5
View File
@@ -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.