Codegen for catch.

This commit is contained in:
chriseth 2021-02-03 08:06:35 +01:00
parent 6fd0cf547e
commit 7aa7e708f9
6 changed files with 183 additions and 90 deletions

View File

@ -190,13 +190,15 @@ string ABIFunctions::tupleEncoderPacked(
return templ.render();
});
}
string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
string ABIFunctions::tupleDecoder(TypePointers const& _types, OnError _onError, bool _fromMemory)
{
string functionName = string("abi_decode_tuple_");
for (auto const& t: _types)
functionName += t->identifier();
if (_fromMemory)
functionName += "_fromMemory";
if (_onError == OnError::ReturnFalse)
functionName += "_try";
return createFunction(functionName, [&]() {
TypePointers decodingTypes;
@ -204,17 +206,25 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
decodingTypes.emplace_back(t->decodingType());
Whiskers templ(R"(
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
function <functionName>(headStart, dataEnd) <+returnParams> -> <returnParams> </+returnParams> {
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
<?try> success := 1 </try>
<decodeElements>
}
)");
templ("functionName", functionName);
templ("try", _onError == OnError::ReturnFalse);
if (_onError == OnError::ReturnFalse)
// TODO rename
templ("revertString", "success := 0 leave");
else
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
templ("minimumSize", to_string(headSize(decodingTypes)));
string decodeElements;
vector<string> valueReturnParams;
if (_onError == OnError::ReturnFalse)
valueReturnParams.emplace_back("success");
size_t headPos = 0;
size_t stackPos = 0;
for (size_t i = 0; i < _types.size(); ++i)
@ -225,6 +235,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
solAssert(sizeOnStack > 0, "");
vector<string> valueNamesLocal;
if (_onError == OnError::ReturnFalse)
valueNamesLocal.push_back("success");
for (size_t j = 0; j < sizeOnStack; j++)
{
valueNamesLocal.emplace_back("value" + to_string(stackPos));
@ -240,15 +252,22 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
let offset := <pos>
</dynamic>
<values> := <abiDecode>(add(headStart, offset), dataEnd)
<?try> if iszero(success) { leave } </try>
}
)");
elementTempl("dynamic", decodingTypes[i]->isDynamicallyEncoded());
// TODO add test
if (_onError == OnError::ReturnFalse)
// TODO rename
elementTempl("revertString", "success := 0 leave");
else
elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset"));
elementTempl("load", _fromMemory ? "mload" : "calldataload");
// TODO assign success
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
elementTempl("pos", to_string(headPos));
elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
elementTempl("try", _onError == OnError::ReturnFalse);
decodeElements += elementTempl.render();
headPos += decodingTypes[i]->calldataHeadSize();
}
@ -1062,7 +1081,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
});
}
string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack)
string ABIFunctions::abiDecodingFunction(Type const& _type, OnError _onError, bool _fromMemory, bool _forUseOnStack)
{
// The decoding function has to perform bounds checks unless it decodes a value type.
// Conversely, bounds checks have to be performed before the decoding function
@ -1076,28 +1095,28 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
if (arrayType->dataStoredIn(DataLocation::CallData))
{
solAssert(!_fromMemory, "");
return abiDecodingFunctionCalldataArray(*arrayType);
return abiDecodingFunctionCalldataArray(*arrayType, _onError);
}
else
return abiDecodingFunctionArray(*arrayType, _fromMemory);
return abiDecodingFunctionArray(*arrayType, _onError, _fromMemory);
}
else if (auto const* structType = dynamic_cast<StructType const*>(decodingType))
{
if (structType->dataStoredIn(DataLocation::CallData))
{
solAssert(!_fromMemory, "");
return abiDecodingFunctionCalldataStruct(*structType);
return abiDecodingFunctionCalldataStruct(*structType, _onError);
}
else
return abiDecodingFunctionStruct(*structType, _fromMemory);
return abiDecodingFunctionStruct(*structType, _onError, _fromMemory);
}
else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType))
return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
return abiDecodingFunctionFunctionType(*functionType, _onError, _fromMemory, _forUseOnStack);
else
return abiDecodingFunctionValueType(_type, _fromMemory);
return abiDecodingFunctionValueType(_type, _onError, _fromMemory);
}
string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, OnError _onError, bool _fromMemory)
{
TypePointer decodingType = _type.decodingType();
solAssert(decodingType, "");
@ -1109,12 +1128,13 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
string functionName =
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
(_fromMemory ? "_fromMemory" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(offset, end) -> value {
function <functionName>(offset, end) -> <?try> success, </try> value {
value := <load>(offset)
<validator>(value)
<?try> success := </try> <validator>(value)
}
)");
templ("functionName", functionName);
@ -1122,58 +1142,65 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
// Validation should use the type and not decodingType, because e.g.
// the decoding type of an enum is a plain int.
templ("validator", m_utils.validatorFunction(_type, true));
// TODO extend the validator to retrun success
return templ.render();
});
}
string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, OnError _onError, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
string functionName =
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
(_fromMemory ? "_fromMemory" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
string load = _fromMemory ? "mload" : "calldataload";
Whiskers templ(
R"(
// <readableTypeName>
function <functionName>(offset, end) -> array {
function <functionName>(offset, end) -> <?try> success, </try> array {
if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
let length := <retrieveLength>
array := <abiDecodeAvailableLen>(<offset>, length, end)
<?try> success, </try> array := <abiDecodeAvailableLen>(<offset>, length, end)
}
)"
);
// TODO add test
if (_onError == OnError::ReturnFalse)
// TODO rename
templ("revertString", "success := 0 leave");
else
templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
templ("retrieveLength", _type.isDynamicallySized() ? (load + "(offset)") : toCompactHexWithPrefix(_type.length()));
templ("offset", _type.isDynamicallySized() ? "add(offset, 0x20)" : "offset");
templ("abiDecodeAvailableLen", abiDecodingFunctionArrayAvailableLength(_type, _fromMemory));
templ("abiDecodeAvailableLen", abiDecodingFunctionArrayAvailableLength(_type, _onError, _fromMemory));
return templ.render();
});
}
string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, OnError _onError, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (_type.isByteArray())
return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory);
return abiDecodingFunctionByteArrayAvailableLength(_type, _onError, _fromMemory);
string functionName =
"abi_decode_available_length_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
(_fromMemory ? "_fromMemory" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
// <readableTypeName>
function <functionName>(offset, length, end) -> array {
function <functionName>(offset, length, end) -> <?try> success, </try> array {
array := <allocate>(<allocationSize>(length))
let dst := array
<storeLength>
@ -1209,17 +1236,20 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
templ("staticBoundsCheck", "if gt(add(src, mul(length, " +
calldataStride +
")), end) { " +
revertReasonIfDebug("ABI decoding: invalid calldata array stride") +
(
_onError == OnError::ReturnFalse ? "success := 0 leave" :
revertReasonIfDebug("ABI decoding: invalid calldata array stride")
) +
" }"
);
templ("retrieveElementPos", "src");
}
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _onError, _fromMemory, false));
return templ.render();
});
}
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type, OnError _onError)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
if (!_type.isDynamicallySized())
@ -1229,14 +1259,16 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
string functionName =
"abi_decode_" +
_type.identifier();
_type.identifier() +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers w;
if (_type.isDynamicallySized())
{
w = Whiskers(R"(
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos, length {
function <functionName>(offset, end) -> <+try> success, </try> arrayPos, length {
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
length := calldataload(offset)
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
@ -1244,6 +1276,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos> }
}
)");
// TODO modify to return failure
w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length"));
}
@ -1251,13 +1284,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
{
w = Whiskers(R"(
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos {
function <functionName>(offset, end) -> <+try> success, </try> arrayPos {
arrayPos := offset
if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos> }
}
)");
w("length", toCompactHexWithPrefix(_type.length()));
}
// TODO modify to return failure
w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride"));
w("functionName", functionName);
w("readableTypeName", _type.toString(true));
@ -1268,7 +1302,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
});
}
string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, OnError _onError, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
solAssert(_type.isByteArray(), "");
@ -1276,11 +1310,12 @@ string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const
string functionName =
"abi_decode_available_length_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
(_fromMemory ? "_fromMemory" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(src, length, end) -> array {
function <functionName>(src, length, end) -> <?try> success, </try> array {
array := <allocate>(<allocationSize>(length))
mstore(array, length)
let dst := add(array, 0x20)
@ -1288,6 +1323,7 @@ string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const
<copyToMemFun>(src, dst, length)
}
)");
// TODO modify
templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length"));
templ("functionName", functionName);
templ("allocate", m_utils.allocationFunction());
@ -1297,22 +1333,24 @@ string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const
});
}
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type, OnError _onError)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName =
"abi_decode_" +
_type.identifier();
_type.identifier() +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers w{R"(
// <readableTypeName>
function <functionName>(offset, end) -> value {
function <functionName>(offset, end) -> <+try> success, </try> value {
if slt(sub(end, offset), <minimumSize>) { <revertString> }
value := offset
}
)"};
// TODO add test
// TODO modfy
w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short"));
w("functionName", functionName);
w("readableTypeName", _type.toString(true));
@ -1321,18 +1359,19 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
});
}
string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, OnError _onError, bool _fromMemory)
{
solAssert(!_type.dataStoredIn(DataLocation::CallData), "");
string functionName =
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
(_fromMemory ? "_fromMemory" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
// <readableTypeName>
function <functionName>(headStart, end) -> value {
function <functionName>(headStart, end) -> <?try> success, </try> value {
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
value := <allocate>(<memorySize>)
<#members>
@ -1344,6 +1383,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
}
)");
// TODO add test
// TODO modify
templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short"));
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
@ -1365,10 +1405,12 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
<!dynamic>
let offset := <pos>
</dynamic>
// TODO outline and leave on error
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)");
memberTempl("dynamic", decodingType->isDynamicallyEncoded());
// TODO add test
// TODO modify
memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset"));
memberTempl("load", _fromMemory ? "mload" : "calldataload");
memberTempl("pos", to_string(headPos));
@ -1386,7 +1428,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
});
}
string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack)
string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, OnError _onError, bool _fromMemory, bool _forUseOnStack)
{
solAssert(_type.kind() == FunctionType::Kind::External, "");
@ -1394,13 +1436,15 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "") +
(_forUseOnStack ? "_onStack" : "");
(_forUseOnStack ? "_onStack" : "") +
(_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
if (_forUseOnStack)
{
return Whiskers(R"(
function <functionName>(offset, end) -> addr, function_selector {
function <functionName>(offset, end) -> <?try> failure, </try> addr, function_selector {
// TODO split
addr, function_selector := <splitExtFun>(<decodeFun>(offset, end))
}
)")
@ -1412,7 +1456,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
else
{
return Whiskers(R"(
function <functionName>(offset, end) -> fun {
function <functionName>(offset, end) -> <?try> failure, </try> fun {
fun := <load>(offset)
<validateExtFun>(fun)
}
@ -1425,17 +1469,18 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
});
}
string ABIFunctions::calldataAccessFunction(Type const& _type)
string ABIFunctions::calldataAccessFunction(Type const& _type, OnError _onError)
{
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_access_" + _type.identifier();
string functionName = "calldata_access_" + _type.identifier() + (_onError == OnError::ReturnFalse ? "_try" : "");
return createFunction(functionName, [&]() {
if (_type.isDynamicallyEncoded())
{
unsigned int tailSize = _type.calldataEncodedTailSize();
solAssert(tailSize > 1, "");
Whiskers w(R"(
function <functionName>(base_ref, ptr) -> <return> {
function <functionName>(base_ref, ptr) -> <?try> failure, </try> <return> {
let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset> }
value := add(rel_offset_of_tail, base_ref)
@ -1454,8 +1499,10 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
)")
("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride()))
// TODO add test
// TODO modify
("revertStringLength", revertReasonIfDebug("Invalid calldata access length"))
// TODO add test
// TODO modify
("revertStringStride", revertReasonIfDebug("Invalid calldata access stride"))
.render());
w("return", "value, length");
@ -1467,6 +1514,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
}
w("neededLength", toCompactHexWithPrefix(tailSize));
w("functionName", functionName);
// TODO modify
w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset"));
return w.render();
}
@ -1484,6 +1532,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
}
)")
("functionName", functionName)
// TODO modify
("decodingFunction", decodingFunction)
.render();
}
@ -1494,6 +1543,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
_type.category() == Type::Category::Struct,
""
);
// TODO modify
return Whiskers(R"(
function <functionName>(baseRef, ptr) -> value {
value := ptr

View File

@ -116,6 +116,8 @@ public:
return tupleEncoderPacked(_givenTypes, _targetTypes, true);
}
enum OnError { Revert, ReturnFalse };
/// @returns name of an assembly function to ABI-decode values of @a _types
/// into memory. If @a _fromMemory is true, decodes from memory instead of
/// from calldata.
@ -124,7 +126,7 @@ public:
/// Outputs: <value0> <value1> ... <valuen>
/// The values represent stack slots. If a type occupies more or less than one
/// stack slot, it takes exactly that number of values.
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
std::string tupleDecoder(TypePointers const& _types, OnError _onError, bool _fromMemory = false);
struct EncodingOptions
{
@ -168,12 +170,12 @@ public:
/// Decodes array in case of dynamic arrays with offset pointing to
/// data and length already on stack
/// signature: (dataOffset, length, dataEnd) -> decodedArray
std::string abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory);
std::string abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, OnError _onError, bool _fromMemory);
/// Internal decoding function that is also used by some copying routines.
/// @returns the name of a function that decodes structs.
/// signature: (dataStart, dataEnd) -> decodedStruct
std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
std::string abiDecodingFunctionStruct(StructType const& _type, OnError _onError, bool _fromMemory);
private:
/// Part of @a abiEncodingFunction for array target type and given calldata array.
@ -239,17 +241,17 @@ private:
);
/// Part of @a abiDecodingFunction for value types.
std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory);
std::string abiDecodingFunctionValueType(Type const& _type, OnError _onError, bool _fromMemory);
/// Part of @a abiDecodingFunction for "regular" array types.
std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory);
std::string abiDecodingFunctionArray(ArrayType const& _type, OnError _onError, bool _fromMemory);
/// Part of @a abiDecodingFunction for calldata array types.
std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
std::string abiDecodingFunctionCalldataArray(ArrayType const& _type, OnError _onError);
/// Part of @a abiDecodingFunctionArrayWithAvailableLength
std::string abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory);
std::string abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, OnError _onError, bool _fromMemory);
/// Part of @a abiDecodingFunction for calldata struct types.
std::string abiDecodingFunctionCalldataStruct(StructType const& _type);
std::string abiDecodingFunctionCalldataStruct(StructType const& _type, OnError _onError);
/// Part of @a abiDecodingFunction for array types.
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, OnError _onError, bool _fromMemory, bool _forUseOnStack);
/// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type const& _type);

View File

@ -992,6 +992,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
if (error || panic)
// Note that this function returns zero on failure, which is not a problem yet,
// but will be a problem once we allow user-defined errors.
// TODO
m_context.callYulFunction(m_context.utilFunctions().returnDataSelectorFunction(), 0, 1);
// stack: <selector>
if (error)

View File

@ -4129,11 +4129,13 @@ string YulUtilFunctions::returnDataSelectorFunction()
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> sig {
if gt(returndatasize(), 3) {
function <functionName>() -> sig, error {
switch gt(returndatasize(), 3)
case 1 {
returndatacopy(0, 0, 4)
sig := <shr224>(mload(0))
}
default { error := 1 }
}
)")
("functionName", functionName)
@ -4201,6 +4203,30 @@ string YulUtilFunctions::tryDecodePanicDataFunction()
});
}
string YulUtilFunctions::tryDecodeReturndata(vector<Type const*> const& _types)
{
string const functionName = "try_decode_returndata_";
for (Type const* type: _types)
functionName += type->identifier();
solAssert(m_evmVersion.supportsReturndata(), "");
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> success<+values>, <values></+values> {
if lt(returndatasize(), 4) { leave }
let data := <allocate>(sub(returndatasize(), 4))
returndatacopy(data, 4, sub(returndatasize(), 4))
ret := msg
}
)")
("functionName", functionName)
("allocate", allocationFunction())
("finalizeAllocation", finalizeAllocationFunction())
.render();
});
}
string YulUtilFunctions::extractReturndataFunction()
{
string const functionName = "extract_returndata";

View File

@ -471,6 +471,12 @@ public:
/// signature: () -> success, value
std::string tryDecodePanicDataFunction();
/// @returns the name of a function that tries to abi-decode parameters in a catch clause
/// from the return data.
/// Does not check the return data signature.
/// signature: () -> success, value...
std::string tryDecodeReturndata(std::vector<Type const*> const& _types);
/// 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.

View File

@ -2999,49 +2999,57 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
{
string const runFallback = m_context.newYulVariable();
string const selector = m_context.newYulVariable();
string const shortCalldata = m_context.newYulVariable();
m_code << "let " << runFallback << " := 1\n";
m_code << "let " << selector << ", " << shortCalldata << ", " << " := " << m_utils.returnDataSelectorFunction() << "()\n";
m_code << "if iszero(" << shortCalldata << ") {\n";
// This function returns zero on "short returndata". We have to add a success flag
// once we implement custom error codes.
if (_tryStatement.errorClause() || _tryStatement.panicClause())
m_code << "switch " << m_utils.returnDataSelectorFunction() << "()\n";
if (_tryStatement.clauses().size() - 1 - (_tryStatement.fallback() ? 1 : 0) > 0)
m_code << "switch " << selector << "\n";
if (TryCatchClause const* errorClause = _tryStatement.errorClause())
for (ASTPointer<TryCatchClause> const& clause: _tryStatement.clauses())
{
m_code << "case " << selectorFromSignature32("Error(string)") << " {\n";
string const dataVariable = m_context.newYulVariable();
m_code << "let " << dataVariable << " := " << m_utils.tryDecodeErrorMessageFunction() << "()\n";
m_code << "if " << dataVariable << " {\n";
m_code << runFallback << " := 0\n";
if (errorClause->parameters())
if (clause->kind() == TryCatchClause::Kind::Success || clause->kind() == TryCatchClause::Kind::Fallback)
continue;
string signature;
vector<Type const*> parameterTypes;
if (clause->kind() == TryCatchClause::Kind::Error)
{
solAssert(errorClause->parameters()->parameters().size() == 1, "");
IRVariable const& var = m_context.addLocalVariable(*errorClause->parameters()->parameters().front());
define(var) << dataVariable << "\n";
signature = "Error(string)";
parameterTypes.push_back(TypeProvider::stringMemory);
}
else if (clause->kind() == TryCatchClause::Kind::Panic)
{
signature = "Panic(uint256)";
parameterTypes.push_back(TypeProvider::uint256();
}
else
{
ErrorDefinition const& error = dynamic_cast<ErrorDefinition const&>(
*clause->errorName().annotation().referecedDeclaration
);
signature = error->functionType(true)->externalSignature();
parameterTypes = error->functionType(true)->parameterTypes();
}
solAssert(clause->parameters(), "");
vector<string> variables;
string const success = m_context.newYulVariable();
variables.push_back(success);
for (ASTPointer<VariableDeclaration> const& varDecl: error->parameters()->parameters())
variables += m_context.addLocalVariable(*varDecl).stackSlots();
m_code << "case " << selectorFromSignature32(signature) << " {\n";
m_code << "let " << joinHumanReadable(variables) << " := " << m_utils.tryDecodeReturndata(parameterTypes) << "()\n";
m_code << "if " << success << " {\n";
m_code << runFallback << " := 0\n";
errorClause->accept(*this);
m_code << "}\n";
m_code << "}\n";
}
if (TryCatchClause const* panicClause = _tryStatement.panicClause())
{
m_code << "case " << selectorFromSignature32("Panic(uint256)") << " {\n";
string const success = m_context.newYulVariable();
string const code = m_context.newYulVariable();
m_code << "let " << success << ", " << code << " := " << m_utils.tryDecodePanicDataFunction() << "()\n";
m_code << "if " << success << " {\n";
m_code << runFallback << " := 0\n";
if (panicClause->parameters())
{
solAssert(panicClause->parameters()->parameters().size() == 1, "");
IRVariable const& var = m_context.addLocalVariable(*panicClause->parameters()->parameters().front());
define(var) << code << "\n";
}
panicClause->accept(*this);
m_code << "}\n";
m_code << "}\n";
}
m_code << "}\n";
m_code << "if " << runFallback << " {\n";
if (_tryStatement.fallbackClause())
handleCatchFallback(*_tryStatement.fallbackClause());