diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 7e07b8807..fe1bbad7a 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -75,6 +75,8 @@ set(sources codegen/LValue.h codegen/MultiUseYulFunctionCollector.h codegen/MultiUseYulFunctionCollector.cpp + codegen/ReturnInfo.h + codegen/ReturnInfo.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp codegen/ir/IRGenerator.cpp diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a599de6a4..18de2ba84 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -949,43 +949,7 @@ void ContractCompiler::handleCatch(vector> const& _ca // 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( - util::Whiskers(R"({ - data := mload(0x40) - mstore(data, 0) - for {} 1 {} { - if lt(returndatasize(), 0x44) { data := 0 break } - returndatacopy(0, 0, 4) - let sig := - if iszero(eq(sig, 0x)) { 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.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1); m_context << Instruction::DUP1; AssemblyItem decodeSuccessTag = m_context.appendConditionalJump(); m_context << Instruction::POP; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a02497561..354a24099 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -22,12 +22,14 @@ #include -#include -#include +#include #include #include #include +#include +#include + #include #include #include @@ -2185,30 +2187,11 @@ void ExpressionCompiler::appendExternalFunctionCall( solAssert(!_functionType.isBareCall(), ""); } - bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); - unsigned retSize = 0; - bool dynamicReturnSize = false; - TypePointers returnTypes; - if (!returnSuccessConditionAndReturndata) - { - if (haveReturndatacopy) - returnTypes = _functionType.returnParameterTypes(); - else - returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes(); - - for (auto const& retType: returnTypes) - if (retType->isDynamicallyEncoded()) - { - solAssert(haveReturndatacopy, ""); - dynamicReturnSize = true; - retSize = 0; - break; - } - else if (retType->decodingType()) - retSize += retType->decodingType()->calldataEncodedSize(); - else - retSize += retType->calldataEncodedSize(); - } + ReturnInfo const returnInfo{m_context.evmVersion(), _functionType}; + bool const haveReturndatacopy = m_context.evmVersion().supportsReturndata(); + unsigned const retSize = returnInfo.estimatedReturnSize; + bool const dynamicReturnSize = returnInfo.dynamicReturnSize; + TypePointers const& returnTypes = returnInfo.returnTypes; // Evaluate arguments. TypePointers argumentTypes; diff --git a/libsolidity/codegen/ReturnInfo.cpp b/libsolidity/codegen/ReturnInfo.cpp new file mode 100644 index 000000000..b522addd4 --- /dev/null +++ b/libsolidity/codegen/ReturnInfo.cpp @@ -0,0 +1,55 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +using namespace solidity::frontend; +using namespace solidity::langutil; + +ReturnInfo::ReturnInfo(EVMVersion const& _evmVersion, FunctionType const& _functionType) +{ + FunctionType::Kind const funKind = _functionType.kind(); + bool const haveReturndatacopy = _evmVersion.supportsReturndata(); + bool const returnSuccessConditionAndReturndata = + funKind == FunctionType::Kind::BareCall || + funKind == FunctionType::Kind::BareDelegateCall || + funKind == FunctionType::Kind::BareStaticCall; + + if (!returnSuccessConditionAndReturndata) + { + if (haveReturndatacopy) + returnTypes = _functionType.returnParameterTypes(); + else + returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes(); + + for (auto const& retType: returnTypes) + if (retType->isDynamicallyEncoded()) + { + solAssert(haveReturndatacopy, ""); + dynamicReturnSize = true; + estimatedReturnSize = 0; + break; + } + else if (retType->decodingType()) + estimatedReturnSize += retType->decodingType()->calldataEncodedSize(); + else + estimatedReturnSize += retType->calldataEncodedSize(); + } +} diff --git a/libsolidity/codegen/ReturnInfo.h b/libsolidity/codegen/ReturnInfo.h new file mode 100644 index 000000000..01aa30862 --- /dev/null +++ b/libsolidity/codegen/ReturnInfo.h @@ -0,0 +1,47 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that computes information relevant during decoding an external function + * call's return values. + */ +#pragma once + +#include +#include + +namespace solidity::frontend +{ + +/** + * Computes and holds information relevant during decoding an external function + * call's return values. + */ +struct ReturnInfo +{ + ReturnInfo(langutil::EVMVersion const& _evmVersion, FunctionType const& _functionType); + + /// Vector of TypePointer, for each return variable. Dynamic types are already replaced if required. + TypePointers returnTypes = {}; + + /// Boolean, indicating whether or not return size is only known at runtime. + bool dynamicReturnSize = false; + + /// Contains the at compile time estimated return size. + unsigned estimatedReturnSize = 0; +}; + +} diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 28a4f69fb..2f14ae744 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -2252,3 +2252,84 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _message) { return revertReasonIfDebug(m_revertStrings, _message); } + +string YulUtilFunctions::tryDecodeErrorMessageFunction() +{ + string const functionName = "try_decode_error_message"; + + return m_functionCollector.createFunction(functionName, [&]() { + return util::Whiskers(R"( + function () -> ret { + if lt(returndatasize(), 0x44) { leave } + + returndatacopy(0, 0, 4) + let sig := (mload(0)) + if iszero(eq(sig, 0x)) { leave } + + let data := mload() + returndatacopy(data, 4, sub(returndatasize(), 4)) + + let offset := mload(data) + if or( + gt(offset, 0xffffffffffffffff), + gt(add(offset, 0x24), returndatasize()) + ) { + leave + } + + let msg := add(data, offset) + let length := mload(msg) + if gt(length, 0xffffffffffffffff) { leave } + + let end := add(add(msg, 0x20), length) + if gt(end, add(data, returndatasize())) { leave } + + mstore(, add(add(msg, 0x20), (length))) + ret := msg + } + )") + ("functionName", functionName) + ("shr224", shiftRightFunction(224)) + ("ErrorSignature", FixedHash<4>(util::keccak256("Error(string)")).hex()) + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("roundUp", roundUpFunction()) + .render(); + }); +} + +string YulUtilFunctions::extractReturndataFunction() +{ + string const functionName = "extract_returndata"; + + return m_functionCollector.createFunction(functionName, [&]() { + return util::Whiskers(R"( + function () -> data { + + switch returndatasize() + case 0 { + data := () + } + default { + // allocate some memory into data of size returndatasize() + PADDING + data := ((add(returndatasize(), 0x20))) + + // store array length into the front + mstore(data, returndatasize()) + + // append to data + returndatacopy(add(data, 0x20), 0, returndatasize()) + } + + data := () + + } + )") + ("functionName", functionName) + ("supportsReturndata", m_evmVersion.supportsReturndata()) + ("allocate", allocationFunction()) + ("roundUp", roundUpFunction()) + ("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory())) + .render(); + }); +} + diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index b902f9f64..9550b9e89 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -22,6 +22,7 @@ #include +#include #include #include @@ -322,6 +323,20 @@ public: static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = ""); std::string revertReasonIfDebug(std::string const& _message = ""); + + /// 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. + std::string tryDecodeErrorMessageFunction(); + + + /// Returns a function name that returns a newly allocated `bytes` array that contains the return data. + /// + /// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead. + std::string extractReturndataFunction(); + private: /// Special case of conversionFunction - handles everything that does not /// use exactly one variable to hold the value. diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 26f6f0309..45cb1ce14 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -87,6 +87,17 @@ string IRGenerationContext::newYulVariable() return "_" + to_string(++m_varCounter); } +string IRGenerationContext::trySuccessConditionVariable(Expression const& _expression) const +{ + // NB: The TypeChecker already ensured that the Expression is of type FunctionCall. + solAssert( + static_cast(_expression.annotation()).tryCall, + "Parameter must be a FunctionCall with tryCall-annotation set." + ); + + return "trySuccessCondition_" + to_string(_expression.id()); +} + string IRGenerationContext::internalDispatch(size_t _in, size_t _out) { string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); @@ -141,3 +152,4 @@ std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message { return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); } + diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index b0fc92cdb..0dac6d65a 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -99,6 +99,10 @@ public: RevertStrings revertStrings() const { return m_revertStrings; } + /// @returns the variable name that can be used to inspect the success or failure of an external + /// function call that was invoked as part of the try statement. + std::string trySuccessConditionVariable(Expression const& _expression) const; + private: langutil::EVMVersion m_evmVersion; RevertStrings m_revertStrings; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 57c8cb152..a6858d854 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,7 @@ #include #include +#include #include using namespace std; @@ -1260,39 +1262,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall( _arguments.size() == funType.parameterTypes().size(), "" ); solUnimplementedAssert(!funType.bound(), ""); - FunctionType::Kind funKind = funType.kind(); + FunctionType::Kind const funKind = funType.kind(); solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); - bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; - bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; - bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); + bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; + bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); - bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); - unsigned estimatedReturnSize = 0; - bool dynamicReturnSize = false; - TypePointers returnTypes; - if (!returnSuccessConditionAndReturndata) - { - if (haveReturndatacopy) - returnTypes = funType.returnParameterTypes(); - else - returnTypes = funType.returnParameterTypesWithoutDynamicTypes(); - - for (auto const& retType: returnTypes) - if (retType->isDynamicallyEncoded()) - { - solAssert(haveReturndatacopy, ""); - dynamicReturnSize = true; - estimatedReturnSize = 0; - break; - } - else if (retType->decodingType()) - estimatedReturnSize += retType->decodingType()->calldataEncodedSize(); - else - estimatedReturnSize += retType->calldataEncodedSize(); - } + ReturnInfo const returnInfo{m_context.evmVersion(), funType}; TypePointers argumentTypes; vector argumentStrings; @@ -1311,8 +1289,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall( // (which we would have to subtract from the gas left) // We could also just use MLOAD; POP right before the gas calculation, but the optimizer // would remove that, so we use MSTORE here. - if (!funType.gasSet() && estimatedReturnSize > 0) - m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n"; + if (!funType.gasSet() && returnInfo.estimatedReturnSize > 0) + m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n"; } ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); @@ -1323,30 +1301,61 @@ void IRGeneratorForStatements::appendExternalFunctionCall( if iszero(extcodesize(
)) { revert(0, 0) } + // storage for arguments and returned data let := - mstore(, ()) let := (add(, 4) ) - let := (,
, , , sub(, ), , ) - if iszero() { () } + let := (,
, , , sub(, ), , ) + + if iszero() { () } + + let + if { + + // copy dynamic return data out + returndatacopy(, 0, returndatasize()) + - - returndatacopy(, 0, returndatasize()) - + // update freeMemoryPointer according to dynamic return size + mstore(, add(, ())) - mstore(, add(, and(add(, 0x1f), not(0x1f)))) - let := (, add(, )) + // decode return parameters from external try-call into retVars + := (, add(, )) + } )"); templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); - templ("result", m_context.newYulVariable()); + if (_functionCall.annotation().tryCall) + templ("success", m_context.trySuccessConditionVariable(_functionCall)); + else + templ("success", m_context.newYulVariable()); templ("freeMemory", freeMemory()); - templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4))); templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name()); templ("address", IRVariable(_functionCall.expression()).part("address").name()); + // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. + // This ensures it can catch badly formatted input from external calls. + if (m_context.evmVersion().supportsReturndata()) + templ("returnSize", "returndatasize()"); + else + templ("returnSize", to_string(returnInfo.estimatedReturnSize)); + + templ("reservedReturnSize", returnInfo.dynamicReturnSize ? "0" : to_string(returnInfo.estimatedReturnSize)); + + string const retVars = IRVariable(_functionCall).commaSeparatedList(); + templ("retVars", retVars); + templ("hasRetVars", !retVars.empty()); + solAssert(retVars.empty() == returnInfo.returnTypes.empty(), ""); + + templ("roundUp", m_utils.roundUpFunction()); + templ("abiDecode", abi.tupleDecoder(returnInfo.returnTypes, true)); + templ("dynamicReturnSize", returnInfo.dynamicReturnSize); + templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); + + templ("noTryCall", !_functionCall.annotation().tryCall); + // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place. // Move arguments to memory, will not update the free memory pointer (but will update the memory // pointer on the stack). @@ -1401,24 +1410,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall( templ("forwardingRevert", m_utils.forwardingRevertFunction()); - solUnimplementedAssert(!returnSuccessConditionAndReturndata, ""); solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, ""); solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); - templ("dynamicReturnSize", dynamicReturnSize); - // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. - // This ensures it can catch badly formatted input from external calls. - if (haveReturndatacopy) - templ("returnSize", "returndatasize()"); - else - templ("returnSize", to_string(estimatedReturnSize)); - - templ("reservedReturnSize", dynamicReturnSize ? "0" : to_string(estimatedReturnSize)); - - templ("abiDecode", abi.tupleDecoder(returnTypes, true)); - templ("returns", !returnTypes.empty()); - templ("retVars", IRVariable(_functionCall).commaSeparatedList()); - m_code << templ.render(); } @@ -1749,3 +1743,119 @@ Type const& IRGeneratorForStatements::type(Expression const& _expression) solAssert(_expression.annotation().type, "Type of expression not set."); return *_expression.annotation().type; } + +bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement) +{ + Expression const& externalCall = _tryStatement.externalCall(); + externalCall.accept(*this); + + m_code << "switch iszero(" << m_context.trySuccessConditionVariable(externalCall) << ")\n"; + + m_code << "case 0 { // success case\n"; + TryCatchClause const& successClause = *_tryStatement.clauses().front(); + if (successClause.parameters()) + { + size_t i = 0; + for (ASTPointer const& varDecl: successClause.parameters()->parameters()) + { + solAssert(varDecl, ""); + define(m_context.addLocalVariable(*varDecl), + successClause.parameters()->parameters().size() == 1 ? + IRVariable(externalCall) : + IRVariable(externalCall).tupleComponent(i++) + ); + } + } + + successClause.block().accept(*this); + m_code << "}\n"; + + m_code << "default { // failure case\n"; + handleCatch(_tryStatement); + m_code << "}\n"; + + return false; +} + +void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement) +{ + if (_tryStatement.structuredClause()) + handleCatchStructuredAndFallback(*_tryStatement.structuredClause(), _tryStatement.fallbackClause()); + else if (_tryStatement.fallbackClause()) + handleCatchFallback(*_tryStatement.fallbackClause()); + else + rethrow(); +} + +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(), ""); + + // 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()) + { + solAssert(_structured.parameters()->parameters().size() == 1, ""); + IRVariable const& var = m_context.addLocalVariable(*_structured.parameters()->parameters().front()); + define(var) << dataVariable << "\n"; + } + _structured.accept(*this); + m_code << "}\n"; + m_code << "default { // decoding failure\n"; + if (_fallback) + handleCatchFallback(*_fallback); + else + rethrow(); + m_code << "}\n"; +} + +void IRGeneratorForStatements::handleCatchFallback(TryCatchClause const& _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(), + "" + ); + + VariableDeclaration const& paramDecl = *_fallback.parameters()->parameters().front(); + define(m_context.addLocalVariable(paramDecl)) << m_utils.extractReturndataFunction() << "()\n"; + } + _fallback.accept(*this); +} + +void IRGeneratorForStatements::rethrow() +{ + if (m_context.evmVersion().supportsReturndata()) + m_code << R"( + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + )"s; + else + m_code << "revert(0, 0) // rethrow\n"s; +} + +bool IRGeneratorForStatements::visit(TryCatchClause const& _clause) +{ + _clause.block().accept(*this); + return false; +} + diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 710961cd7..553e81dcc 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -24,6 +24,8 @@ #include #include +#include + namespace solidity::frontend { @@ -70,7 +72,21 @@ public: void endVisit(Identifier const& _identifier) override; bool visit(Literal const& _literal) override; + bool visit(TryStatement const& _tryStatement) override; + bool visit(TryCatchClause const& _tryCatchClause) override; + 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. + void rethrow(); + /// Appends code to call an external function with the given arguments. /// All involved expressions have already been visited. void appendExternalFunctionCall( diff --git a/test/libsolidity/semanticTests/tryCatch/lowLevel.sol b/test/libsolidity/semanticTests/tryCatch/lowLevel.sol index ac275522d..c707cca59 100644 --- a/test/libsolidity/semanticTests/tryCatch/lowLevel.sol +++ b/test/libsolidity/semanticTests/tryCatch/lowLevel.sol @@ -12,6 +12,7 @@ contract C { } } // ==== +// compileViaYul: also // EVMVersion: >=byzantium // ---- // f(bool): true -> 1, 2, 96, 0 diff --git a/test/libsolidity/semanticTests/tryCatch/nested.sol b/test/libsolidity/semanticTests/tryCatch/nested.sol index 7c56a81b7..03e6ad553 100644 --- a/test/libsolidity/semanticTests/tryCatch/nested.sol +++ b/test/libsolidity/semanticTests/tryCatch/nested.sol @@ -26,6 +26,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool,bool): true, true -> 1, 2, 96, 7, "success" // f(bool,bool): true, false -> 12, 0, 96, 7, "failure" diff --git a/test/libsolidity/semanticTests/tryCatch/return_function.sol b/test/libsolidity/semanticTests/tryCatch/return_function.sol index 82d5dc821..b9c2d903f 100644 --- a/test/libsolidity/semanticTests/tryCatch/return_function.sol +++ b/test/libsolidity/semanticTests/tryCatch/return_function.sol @@ -13,5 +13,7 @@ contract C { } function fun() public pure {} } +// ==== +// compileViaYul: also // ---- // f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/tryCatch/simple.sol b/test/libsolidity/semanticTests/tryCatch/simple.sol index b7f367016..1f12614a7 100644 --- a/test/libsolidity/semanticTests/tryCatch/simple.sol +++ b/test/libsolidity/semanticTests/tryCatch/simple.sol @@ -1,10 +1,10 @@ contract C { - function g(bool b) public pure returns (uint, uint) { + function g(bool b) public pure returns (uint x, uint y) { require(b); return (1, 2); } - function f(bool b) public returns (uint x, uint y) { - try this.g(b) returns (uint a, uint b) { + function f(bool flag) public view returns (uint x, uint y) { + try this.g(flag) returns (uint a, uint b) { (x, y) = (a, b); } catch { (x, y) = (9, 10); @@ -13,6 +13,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool): true -> 1, 2 // f(bool): false -> 9, 10 diff --git a/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol b/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol new file mode 100644 index 000000000..7b58006f4 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol @@ -0,0 +1,19 @@ +contract C { + function g(bool b) public pure returns (uint x) { + require(b); + return 13; + } + function f(bool flag) public view returns (uint x) { + try this.g(flag) returns (uint a) { + x = a; + } catch { + x = 9; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileViaYul: also +// ---- +// f(bool): true -> 13 +// f(bool): false -> 9 diff --git a/test/libsolidity/semanticTests/tryCatch/structured.sol b/test/libsolidity/semanticTests/tryCatch/structured.sol index e5aa238b3..e4c04932a 100644 --- a/test/libsolidity/semanticTests/tryCatch/structured.sol +++ b/test/libsolidity/semanticTests/tryCatch/structured.sol @@ -14,6 +14,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool): true -> 1, 2, 0x60, 7, "success" // f(bool): false -> 0, 0, 0x60, 7, "message" diff --git a/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol b/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol index 8a8cb3d1b..91542d7ba 100644 --- a/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol +++ b/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol @@ -18,6 +18,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // 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" diff --git a/test/libsolidity/semanticTests/tryCatch/super_trivial.sol b/test/libsolidity/semanticTests/tryCatch/super_trivial.sol new file mode 100644 index 000000000..9c0340522 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/super_trivial.sol @@ -0,0 +1,18 @@ +contract C { + function g(bool x) external pure { + require(x); + } + function f(bool x) public returns (uint) { + try this.g(x) { + return 1; + } catch { + return 2; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileViaYul: also +// ---- +// f(bool): true -> 1 +// f(bool): false -> 2