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/MultiUseYulFunctionCollector.h
|
||||
codegen/MultiUseYulFunctionCollector.cpp
|
||||
codegen/ReturnInfo.h
|
||||
codegen/ReturnInfo.cpp
|
||||
codegen/YulUtilFunctions.h
|
||||
codegen/YulUtilFunctions.cpp
|
||||
codegen/ir/IRGenerator.cpp
|
||||
|
@ -756,3 +756,25 @@ string Literal::getChecksummedAddress() const
|
||||
address.insert(address.begin(), 40 - address.size(), '0');
|
||||
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; }
|
||||
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
||||
|
||||
TryCatchClause const* successClause() const;
|
||||
TryCatchClause const* structuredClause() const;
|
||||
TryCatchClause const* fallbackClause() const;
|
||||
|
||||
private:
|
||||
ASTPointer<Expression> m_externalCall;
|
||||
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
||||
|
@ -949,43 +949,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> 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 := <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.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
|
||||
m_context << Instruction::DUP1;
|
||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||
m_context << Instruction::POP;
|
||||
|
@ -22,12 +22,14 @@
|
||||
|
||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/ReturnInfo.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/LValue.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libevmasm/GasMeter.h>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
@ -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;
|
||||
|
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);
|
||||
}
|
||||
|
||||
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 <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
@ -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.
|
||||
|
@ -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<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 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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/ReturnInfo.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libevmasm/GasMeter.h>
|
||||
@ -41,6 +42,7 @@
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
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<string> 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(<address>)) { revert(0, 0) }
|
||||
</checkExistence>
|
||||
|
||||
// storage for arguments and returned data
|
||||
let <pos> := <freeMemory>
|
||||
|
||||
mstore(<pos>, <shl28>(<funId>))
|
||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||
|
||||
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
if iszero(<result>) { <forwardingRevert>() }
|
||||
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
<?noTryCall>
|
||||
if iszero(<success>) { <forwardingRevert>() }
|
||||
</noTryCall>
|
||||
<?hasRetVars> let <retVars> </hasRetVars>
|
||||
if <success> {
|
||||
<?dynamicReturnSize>
|
||||
// copy dynamic return data out
|
||||
returndatacopy(<pos>, 0, returndatasize())
|
||||
</dynamicReturnSize>
|
||||
|
||||
<?dynamicReturnSize>
|
||||
returndatacopy(<pos>, 0, returndatasize())
|
||||
</dynamicReturnSize>
|
||||
// update freeMemoryPointer according to dynamic return size
|
||||
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
|
||||
|
||||
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
|
||||
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||
// decode return parameters from external try-call into retVars
|
||||
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||
}
|
||||
)");
|
||||
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<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/IRVariable.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
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(
|
||||
|
@ -12,6 +12,7 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// EVMVersion: >=byzantium
|
||||
// ----
|
||||
// f(bool): true -> 1, 2, 96, 0
|
||||
|
@ -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"
|
||||
|
@ -13,5 +13,7 @@ contract C {
|
||||
}
|
||||
function fun() public pure {}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9
|
||||
|
@ -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
|
||||
|
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
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(bool): true -> 1, 2, 0x60, 7, "success"
|
||||
// f(bool): false -> 0, 0, 0x60, 7, "message"
|
||||
|
@ -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"
|
||||
|
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