mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Code generator for try/catch.
This commit is contained in:
parent
8e736a9f49
commit
70b796bd1a
@ -34,6 +34,8 @@
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
@ -761,6 +763,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContractCompiler::visit(TryStatement const& _tryStatement)
|
||||
{
|
||||
StackHeightChecker checker(m_context);
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _tryStatement);
|
||||
|
||||
compileExpression(_tryStatement.externalCall());
|
||||
unsigned returnSize = _tryStatement.externalCall().annotation().type->sizeOnStack();
|
||||
|
||||
// Stack: [ return values] <success flag>
|
||||
eth::AssemblyItem successTag = m_context.appendConditionalJump();
|
||||
|
||||
// Catch case.
|
||||
m_context.adjustStackOffset(-returnSize);
|
||||
|
||||
handleCatch(_tryStatement.clauses());
|
||||
|
||||
eth::AssemblyItem endTag = m_context.appendJumpToNew();
|
||||
|
||||
m_context << successTag;
|
||||
m_context.adjustStackOffset(returnSize);
|
||||
{
|
||||
// Success case.
|
||||
// Stack: return values
|
||||
TryCatchClause const& successClause = *_tryStatement.clauses().front();
|
||||
if (successClause.parameters())
|
||||
{
|
||||
vector<TypePointer> exprTypes{_tryStatement.externalCall().annotation().type};
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(exprTypes.front()))
|
||||
exprTypes = tupleType->components();
|
||||
vector<ASTPointer<VariableDeclaration>> const& params = successClause.parameters()->parameters();
|
||||
solAssert(exprTypes.size() == params.size(), "");
|
||||
for (size_t i = 0; i < exprTypes.size(); ++i)
|
||||
solAssert(params[i] && exprTypes[i] && *params[i]->annotation().type == *exprTypes[i], "");
|
||||
}
|
||||
else
|
||||
CompilerUtils(m_context).popStackSlots(returnSize);
|
||||
|
||||
_tryStatement.clauses().front()->accept(*this);
|
||||
}
|
||||
|
||||
m_context << endTag;
|
||||
checker.check();
|
||||
return false;
|
||||
}
|
||||
|
||||
void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _catchClauses)
|
||||
{
|
||||
// Stack is empty.
|
||||
ASTPointer<TryCatchClause> structured{};
|
||||
ASTPointer<TryCatchClause> fallback{};
|
||||
for (size_t i = 1; i < _catchClauses.size(); ++i)
|
||||
if (_catchClauses[i]->errorName() == "Error")
|
||||
structured = _catchClauses[i];
|
||||
else if (_catchClauses[i]->errorName().empty())
|
||||
fallback = _catchClauses[i];
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(_catchClauses.size() == size_t(1 + (structured ? 1 : 0) + (fallback ? 1 : 0)), "");
|
||||
|
||||
eth::AssemblyItem endTag = m_context.newTag();
|
||||
eth::AssemblyItem fallbackTag = m_context.newTag();
|
||||
if (structured)
|
||||
{
|
||||
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(), "");
|
||||
|
||||
string errorHash = FixedHash<4>(dev::keccak256("Error(string)")).hex();
|
||||
|
||||
// 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(
|
||||
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;
|
||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||
m_context << Instruction::POP;
|
||||
m_context.appendJumpTo(fallbackTag);
|
||||
m_context.adjustStackOffset(1);
|
||||
|
||||
m_context << decodeSuccessTag;
|
||||
structured->accept(*this);
|
||||
m_context.appendJumpTo(endTag);
|
||||
}
|
||||
m_context << fallbackTag;
|
||||
if (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(),
|
||||
""
|
||||
);
|
||||
CompilerUtils(m_context).returnDataToArray();
|
||||
}
|
||||
|
||||
fallback->accept(*this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// re-throw
|
||||
if (m_context.evmVersion().supportsReturndata())
|
||||
m_context.appendInlineAssembly(R"({
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
})");
|
||||
else
|
||||
m_context.appendRevert();
|
||||
}
|
||||
m_context << endTag;
|
||||
}
|
||||
|
||||
bool ContractCompiler::visit(TryCatchClause const& _clause)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _clause);
|
||||
|
||||
unsigned varSize = 0;
|
||||
|
||||
if (_clause.parameters())
|
||||
for (ASTPointer<VariableDeclaration> const& varDecl: _clause.parameters()->parameters() | boost::adaptors::reversed)
|
||||
{
|
||||
solAssert(varDecl, "");
|
||||
varSize += varDecl->annotation().type->sizeOnStack();
|
||||
m_context.addVariable(*varDecl, varSize);
|
||||
}
|
||||
|
||||
_clause.block().accept(*this);
|
||||
|
||||
m_context.removeVariablesAboveStackHeight(m_context.stackHeight() - varSize);
|
||||
CompilerUtils(m_context).popStackSlots(varSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContractCompiler::visit(IfStatement const& _ifStatement)
|
||||
{
|
||||
StackHeightChecker checker(m_context);
|
||||
|
@ -104,6 +104,9 @@ private:
|
||||
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
||||
bool visit(FunctionDefinition const& _function) override;
|
||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||
bool visit(TryStatement const& _tryStatement) override;
|
||||
void handleCatch(std::vector<ASTPointer<TryCatchClause>> const& _catchClauses);
|
||||
bool visit(TryCatchClause const& _clause) override;
|
||||
bool visit(IfStatement const& _ifStatement) override;
|
||||
bool visit(WhileStatement const& _whileStatement) override;
|
||||
bool visit(ForStatement const& _forStatement) override;
|
||||
|
@ -586,13 +586,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::External:
|
||||
case FunctionType::Kind::DelegateCall:
|
||||
case FunctionType::Kind::BareCall:
|
||||
case FunctionType::Kind::BareDelegateCall:
|
||||
case FunctionType::Kind::BareStaticCall:
|
||||
solAssert(!_functionCall.annotation().tryCall, "");
|
||||
[[fallthrough]];
|
||||
case FunctionType::Kind::External:
|
||||
case FunctionType::Kind::DelegateCall:
|
||||
_functionCall.expression().accept(*this);
|
||||
appendExternalFunctionCall(function, arguments);
|
||||
appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall);
|
||||
break;
|
||||
case FunctionType::Kind::BareCallCode:
|
||||
solAssert(false, "Callcode has been removed.");
|
||||
@ -620,6 +622,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
else
|
||||
m_context << u256(0);
|
||||
m_context << Instruction::CREATE;
|
||||
solUnimplementedAssert(!_functionCall.annotation().tryCall, "");
|
||||
// Check if zero (out of stack or not enough balance).
|
||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
|
||||
@ -675,7 +678,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
true,
|
||||
true
|
||||
),
|
||||
{}
|
||||
{},
|
||||
false
|
||||
);
|
||||
if (function.kind() == FunctionType::Kind::Transfer)
|
||||
{
|
||||
@ -835,7 +839,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context << contractAddresses.at(function.kind());
|
||||
for (unsigned i = function.sizeOnStack(); i > 0; --i)
|
||||
m_context << swapInstruction(i);
|
||||
appendExternalFunctionCall(function, arguments);
|
||||
solAssert(!_functionCall.annotation().tryCall, "");
|
||||
appendExternalFunctionCall(function, arguments, false);
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::ByteArrayPush:
|
||||
@ -1964,7 +1969,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
|
||||
|
||||
void ExpressionCompiler::appendExternalFunctionCall(
|
||||
FunctionType const& _functionType,
|
||||
vector<ASTPointer<Expression const>> const& _arguments
|
||||
vector<ASTPointer<Expression const>> const& _arguments,
|
||||
bool _tryCall
|
||||
)
|
||||
{
|
||||
solAssert(
|
||||
@ -2000,6 +2006,12 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
||||
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
||||
|
||||
if (_tryCall)
|
||||
{
|
||||
solAssert(!returnSuccessConditionAndReturndata, "");
|
||||
solAssert(!_functionType.isBareCall(), "");
|
||||
}
|
||||
|
||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||
unsigned retSize = 0;
|
||||
bool dynamicReturnSize = false;
|
||||
@ -2171,17 +2183,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
(_functionType.gasSet() ? 1 : 0) +
|
||||
(!_functionType.isBareCall() ? 1 : 0);
|
||||
|
||||
if (returnSuccessConditionAndReturndata)
|
||||
m_context << swapInstruction(remainsSize);
|
||||
else
|
||||
eth::AssemblyItem endTag = m_context.newTag();
|
||||
|
||||
if (!returnSuccessConditionAndReturndata && !_tryCall)
|
||||
{
|
||||
//Propagate error condition (if CALL pushes 0 on stack).
|
||||
// Propagate error condition (if CALL pushes 0 on stack).
|
||||
m_context << Instruction::ISZERO;
|
||||
m_context.appendConditionalRevert(true);
|
||||
}
|
||||
|
||||
else
|
||||
m_context << swapInstruction(remainsSize);
|
||||
utils().popStackSlots(remainsSize);
|
||||
|
||||
// Only success flag is remaining on stack.
|
||||
|
||||
if (_tryCall)
|
||||
{
|
||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||
m_context.appendConditionalJumpTo(endTag);
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
|
||||
if (returnSuccessConditionAndReturndata)
|
||||
{
|
||||
// success condition is already there
|
||||
@ -2243,6 +2265,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
|
||||
utils().abiDecode(returnTypes, true);
|
||||
}
|
||||
|
||||
if (_tryCall)
|
||||
{
|
||||
// Success branch will reach this, failure branch will directly jump to endTag.
|
||||
m_context << u256(1);
|
||||
m_context << endTag;
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)
|
||||
|
@ -96,9 +96,12 @@ private:
|
||||
/// @}
|
||||
|
||||
/// Appends code to call a function of the given type with the given arguments.
|
||||
/// @param _tryCall if true, this is the external call of a try statement. In that case,
|
||||
/// returns success flag on top of stack and does not revert on failure.
|
||||
void appendExternalFunctionCall(
|
||||
FunctionType const& _functionType,
|
||||
std::vector<ASTPointer<Expression const>> const& _arguments
|
||||
std::vector<ASTPointer<Expression const>> const& _arguments,
|
||||
bool _tryCall
|
||||
);
|
||||
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
|
||||
/// expected to be on the stack and is updated by this call.
|
||||
|
Loading…
Reference in New Issue
Block a user