mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8246 from ethereum/yul-codegen-trycatch
Sol-to-Yul codegen for try-catch statement
This commit is contained in:
commit
25b0df3dde
@ -75,6 +75,8 @@ set(sources
|
|||||||
codegen/LValue.h
|
codegen/LValue.h
|
||||||
codegen/MultiUseYulFunctionCollector.h
|
codegen/MultiUseYulFunctionCollector.h
|
||||||
codegen/MultiUseYulFunctionCollector.cpp
|
codegen/MultiUseYulFunctionCollector.cpp
|
||||||
|
codegen/ReturnInfo.h
|
||||||
|
codegen/ReturnInfo.cpp
|
||||||
codegen/YulUtilFunctions.h
|
codegen/YulUtilFunctions.h
|
||||||
codegen/YulUtilFunctions.cpp
|
codegen/YulUtilFunctions.cpp
|
||||||
codegen/ir/IRGenerator.cpp
|
codegen/ir/IRGenerator.cpp
|
||||||
|
@ -756,3 +756,25 @@ string Literal::getChecksummedAddress() const
|
|||||||
address.insert(address.begin(), 40 - address.size(), '0');
|
address.insert(address.begin(), 40 - address.size(), '0');
|
||||||
return util::getChecksummedAddress(address);
|
return util::getChecksummedAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::successClause() const
|
||||||
|
{
|
||||||
|
solAssert(m_clauses.size() > 0, "");
|
||||||
|
return m_clauses[0].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::structuredClause() const
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||||
|
if (m_clauses[i]->errorName() == "Error")
|
||||||
|
return m_clauses[i].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::fallbackClause() const
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||||
|
if (m_clauses[i]->errorName().empty())
|
||||||
|
return m_clauses[i].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
@ -1451,6 +1451,10 @@ public:
|
|||||||
Expression const& externalCall() const { return *m_externalCall; }
|
Expression const& externalCall() const { return *m_externalCall; }
|
||||||
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
||||||
|
|
||||||
|
TryCatchClause const* successClause() const;
|
||||||
|
TryCatchClause const* structuredClause() const;
|
||||||
|
TryCatchClause const* fallbackClause() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ASTPointer<Expression> m_externalCall;
|
ASTPointer<Expression> m_externalCall;
|
||||||
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
||||||
|
@ -949,43 +949,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
|||||||
|
|
||||||
// Try to decode the error message.
|
// Try to decode the error message.
|
||||||
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||||
m_context << u256(0);
|
m_context.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
|
||||||
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 := <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;
|
m_context << Instruction::DUP1;
|
||||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||||
m_context << Instruction::POP;
|
m_context << Instruction::POP;
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||||
|
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/codegen/ReturnInfo.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
|
||||||
#include <libsolidity/codegen/CompilerContext.h>
|
#include <libsolidity/codegen/CompilerContext.h>
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
#include <libsolidity/codegen/LValue.h>
|
#include <libsolidity/codegen/LValue.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
#include <libevmasm/GasMeter.h>
|
#include <libevmasm/GasMeter.h>
|
||||||
#include <libsolutil/Common.h>
|
#include <libsolutil/Common.h>
|
||||||
#include <libsolutil/Keccak256.h>
|
#include <libsolutil/Keccak256.h>
|
||||||
@ -2185,30 +2187,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
|||||||
solAssert(!_functionType.isBareCall(), "");
|
solAssert(!_functionType.isBareCall(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
ReturnInfo const returnInfo{m_context.evmVersion(), _functionType};
|
||||||
unsigned retSize = 0;
|
bool const haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||||
bool dynamicReturnSize = false;
|
unsigned const retSize = returnInfo.estimatedReturnSize;
|
||||||
TypePointers returnTypes;
|
bool const dynamicReturnSize = returnInfo.dynamicReturnSize;
|
||||||
if (!returnSuccessConditionAndReturndata)
|
TypePointers const& returnTypes = returnInfo.returnTypes;
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate arguments.
|
// Evaluate arguments.
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
|
55
libsolidity/codegen/ReturnInfo.cpp
Normal file
55
libsolidity/codegen/ReturnInfo.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <libsolidity/codegen/ReturnInfo.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
47
libsolidity/codegen/ReturnInfo.h
Normal file
47
libsolidity/codegen/ReturnInfo.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Component that computes information relevant during decoding an external function
|
||||||
|
* call's return values.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -2252,3 +2252,84 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _message)
|
|||||||
{
|
{
|
||||||
return revertReasonIfDebug(m_revertStrings, _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 <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))
|
||||||
|
|
||||||
|
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(<freeMemoryPointer>, add(add(msg, 0x20), <roundUp>(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 <functionName>() -> data {
|
||||||
|
<?supportsReturndata>
|
||||||
|
switch returndatasize()
|
||||||
|
case 0 {
|
||||||
|
data := <emptyArray>()
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
// allocate some memory into data of size returndatasize() + PADDING
|
||||||
|
data := <allocate>(<roundUp>(add(returndatasize(), 0x20)))
|
||||||
|
|
||||||
|
// store array length into the front
|
||||||
|
mstore(data, returndatasize())
|
||||||
|
|
||||||
|
// append to data
|
||||||
|
returndatacopy(add(data, 0x20), 0, returndatasize())
|
||||||
|
}
|
||||||
|
<!supportsReturndata>
|
||||||
|
data := <emptyArray>()
|
||||||
|
</supportsReturndata>
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
("supportsReturndata", m_evmVersion.supportsReturndata())
|
||||||
|
("allocate", allocationFunction())
|
||||||
|
("roundUp", roundUpFunction())
|
||||||
|
("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory()))
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||||
|
|
||||||
#include <libsolidity/interface/DebugSettings.h>
|
#include <libsolidity/interface/DebugSettings.h>
|
||||||
@ -322,6 +323,20 @@ public:
|
|||||||
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
|
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
|
||||||
|
|
||||||
std::string revertReasonIfDebug(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:
|
private:
|
||||||
/// Special case of conversionFunction - handles everything that does not
|
/// Special case of conversionFunction - handles everything that does not
|
||||||
/// use exactly one variable to hold the value.
|
/// use exactly one variable to hold the value.
|
||||||
|
@ -87,6 +87,17 @@ string IRGenerationContext::newYulVariable()
|
|||||||
return "_" + to_string(++m_varCounter);
|
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<FunctionCallAnnotation const&>(_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 IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||||
{
|
{
|
||||||
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_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);
|
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,10 @@ public:
|
|||||||
|
|
||||||
RevertStrings revertStrings() const { return m_revertStrings; }
|
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:
|
private:
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings m_revertStrings;
|
RevertStrings m_revertStrings;
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||||
#include <libsolidity/codegen/ABIFunctions.h>
|
#include <libsolidity/codegen/ABIFunctions.h>
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
|
#include <libsolidity/codegen/ReturnInfo.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
#include <libevmasm/GasMeter.h>
|
#include <libevmasm/GasMeter.h>
|
||||||
@ -41,6 +42,7 @@
|
|||||||
#include <libsolutil/Keccak256.h>
|
#include <libsolutil/Keccak256.h>
|
||||||
#include <libsolutil/Visitor.h>
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <boost/range/adaptor/transformed.hpp>
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -1260,39 +1262,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
_arguments.size() == funType.parameterTypes().size(), ""
|
_arguments.size() == funType.parameterTypes().size(), ""
|
||||||
);
|
);
|
||||||
solUnimplementedAssert(!funType.bound(), "");
|
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::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
|
||||||
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
|
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
|
||||||
|
|
||||||
bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
|
bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
||||||
bool 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 useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
|
||||||
|
|
||||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
vector<string> argumentStrings;
|
vector<string> argumentStrings;
|
||||||
@ -1311,8 +1289,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
// (which we would have to subtract from the gas left)
|
// (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
|
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
||||||
// would remove that, so we use MSTORE here.
|
// would remove that, so we use MSTORE here.
|
||||||
if (!funType.gasSet() && estimatedReturnSize > 0)
|
if (!funType.gasSet() && returnInfo.estimatedReturnSize > 0)
|
||||||
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
|
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||||
@ -1323,30 +1301,61 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
||||||
</checkExistence>
|
</checkExistence>
|
||||||
|
|
||||||
|
// storage for arguments and returned data
|
||||||
let <pos> := <freeMemory>
|
let <pos> := <freeMemory>
|
||||||
|
|
||||||
mstore(<pos>, <shl28>(<funId>))
|
mstore(<pos>, <shl28>(<funId>))
|
||||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||||
|
|
||||||
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||||
if iszero(<result>) { <forwardingRevert>() }
|
<?noTryCall>
|
||||||
|
if iszero(<success>) { <forwardingRevert>() }
|
||||||
|
</noTryCall>
|
||||||
|
<?hasRetVars> let <retVars> </hasRetVars>
|
||||||
|
if <success> {
|
||||||
|
<?dynamicReturnSize>
|
||||||
|
// copy dynamic return data out
|
||||||
|
returndatacopy(<pos>, 0, returndatasize())
|
||||||
|
</dynamicReturnSize>
|
||||||
|
|
||||||
<?dynamicReturnSize>
|
// update freeMemoryPointer according to dynamic return size
|
||||||
returndatacopy(<pos>, 0, returndatasize())
|
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
|
||||||
</dynamicReturnSize>
|
|
||||||
|
|
||||||
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
|
// decode return parameters from external try-call into retVars
|
||||||
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||||
|
}
|
||||||
)");
|
)");
|
||||||
templ("pos", m_context.newYulVariable());
|
templ("pos", m_context.newYulVariable());
|
||||||
templ("end", 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("freeMemory", freeMemory());
|
||||||
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
|
||||||
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
||||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||||
templ("address", IRVariable(_functionCall.expression()).part("address").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.
|
// 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
|
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
||||||
// pointer on the stack).
|
// pointer on the stack).
|
||||||
@ -1401,24 +1410,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
|
|
||||||
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
||||||
|
|
||||||
solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
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();
|
m_code << templ.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1749,3 +1743,119 @@ Type const& IRGeneratorForStatements::type(Expression const& _expression)
|
|||||||
solAssert(_expression.annotation().type, "Type of expression not set.");
|
solAssert(_expression.annotation().type, "Type of expression not set.");
|
||||||
return *_expression.annotation().type;
|
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<VariableDeclaration> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include <libsolidity/codegen/ir/IRLValue.h>
|
#include <libsolidity/codegen/ir/IRLValue.h>
|
||||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -70,7 +72,21 @@ public:
|
|||||||
void endVisit(Identifier const& _identifier) override;
|
void endVisit(Identifier const& _identifier) override;
|
||||||
bool visit(Literal const& _literal) override;
|
bool visit(Literal const& _literal) override;
|
||||||
|
|
||||||
|
bool visit(TryStatement const& _tryStatement) override;
|
||||||
|
bool visit(TryCatchClause const& _tryCatchClause) override;
|
||||||
|
|
||||||
private:
|
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.
|
/// Appends code to call an external function with the given arguments.
|
||||||
/// All involved expressions have already been visited.
|
/// All involved expressions have already been visited.
|
||||||
void appendExternalFunctionCall(
|
void appendExternalFunctionCall(
|
||||||
|
@ -12,6 +12,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
// EVMVersion: >=byzantium
|
// EVMVersion: >=byzantium
|
||||||
// ----
|
// ----
|
||||||
// f(bool): true -> 1, 2, 96, 0
|
// f(bool): true -> 1, 2, 96, 0
|
||||||
|
@ -26,6 +26,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
// EVMVersion: >=byzantium
|
// EVMVersion: >=byzantium
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f(bool,bool): true, true -> 1, 2, 96, 7, "success"
|
// f(bool,bool): true, true -> 1, 2, 96, 7, "success"
|
||||||
// f(bool,bool): true, false -> 12, 0, 96, 7, "failure"
|
// f(bool,bool): true, false -> 12, 0, 96, 7, "failure"
|
||||||
|
@ -13,5 +13,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
function fun() public pure {}
|
function fun() public pure {}
|
||||||
}
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9
|
// f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
contract C {
|
contract C {
|
||||||
function g(bool b) public pure returns (uint, uint) {
|
function g(bool b) public pure returns (uint x, uint y) {
|
||||||
require(b);
|
require(b);
|
||||||
return (1, 2);
|
return (1, 2);
|
||||||
}
|
}
|
||||||
function f(bool b) public returns (uint x, uint y) {
|
function f(bool flag) public view returns (uint x, uint y) {
|
||||||
try this.g(b) returns (uint a, uint b) {
|
try this.g(flag) returns (uint a, uint b) {
|
||||||
(x, y) = (a, b);
|
(x, y) = (a, b);
|
||||||
} catch {
|
} catch {
|
||||||
(x, y) = (9, 10);
|
(x, y) = (9, 10);
|
||||||
@ -13,6 +13,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
// EVMVersion: >=byzantium
|
// EVMVersion: >=byzantium
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f(bool): true -> 1, 2
|
// f(bool): true -> 1, 2
|
||||||
// f(bool): false -> 9, 10
|
// f(bool): false -> 9, 10
|
||||||
|
19
test/libsolidity/semanticTests/tryCatch/simple_notuple.sol
Normal file
19
test/libsolidity/semanticTests/tryCatch/simple_notuple.sol
Normal file
@ -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
|
@ -14,6 +14,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
// EVMVersion: >=byzantium
|
// EVMVersion: >=byzantium
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f(bool): true -> 1, 2, 0x60, 7, "success"
|
// f(bool): true -> 1, 2, 0x60, 7, "success"
|
||||||
// f(bool): false -> 0, 0, 0x60, 7, "message"
|
// f(bool): false -> 0, 0, 0x60, 7, "message"
|
||||||
|
@ -18,6 +18,7 @@ contract C {
|
|||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
// EVMVersion: >=byzantium
|
// EVMVersion: >=byzantium
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f(bool): true -> 1, 2, 96, 7, "success"
|
// 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"
|
// f(bool): false -> 99, 0, 96, 82, "message longer than 32 bytes 32 ", "bytes 32 bytes 32 bytes 32 bytes", " 32 bytes 32 bytes"
|
||||||
|
18
test/libsolidity/semanticTests/tryCatch/super_trivial.sol
Normal file
18
test/libsolidity/semanticTests/tryCatch/super_trivial.sol
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user