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 <liblangutil/ErrorReporter.h>
|
||||||
|
|
||||||
|
#include <libdevcore/Whiskers.h>
|
||||||
|
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -761,6 +763,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
return false;
|
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)
|
bool ContractCompiler::visit(IfStatement const& _ifStatement)
|
||||||
{
|
{
|
||||||
StackHeightChecker checker(m_context);
|
StackHeightChecker checker(m_context);
|
||||||
|
@ -104,6 +104,9 @@ private:
|
|||||||
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
bool visit(FunctionDefinition const& _function) override;
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) 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(IfStatement const& _ifStatement) override;
|
||||||
bool visit(WhileStatement const& _whileStatement) override;
|
bool visit(WhileStatement const& _whileStatement) override;
|
||||||
bool visit(ForStatement const& _forStatement) override;
|
bool visit(ForStatement const& _forStatement) override;
|
||||||
|
@ -586,13 +586,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
|
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::External:
|
|
||||||
case FunctionType::Kind::DelegateCall:
|
|
||||||
case FunctionType::Kind::BareCall:
|
case FunctionType::Kind::BareCall:
|
||||||
case FunctionType::Kind::BareDelegateCall:
|
case FunctionType::Kind::BareDelegateCall:
|
||||||
case FunctionType::Kind::BareStaticCall:
|
case FunctionType::Kind::BareStaticCall:
|
||||||
|
solAssert(!_functionCall.annotation().tryCall, "");
|
||||||
|
[[fallthrough]];
|
||||||
|
case FunctionType::Kind::External:
|
||||||
|
case FunctionType::Kind::DelegateCall:
|
||||||
_functionCall.expression().accept(*this);
|
_functionCall.expression().accept(*this);
|
||||||
appendExternalFunctionCall(function, arguments);
|
appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall);
|
||||||
break;
|
break;
|
||||||
case FunctionType::Kind::BareCallCode:
|
case FunctionType::Kind::BareCallCode:
|
||||||
solAssert(false, "Callcode has been removed.");
|
solAssert(false, "Callcode has been removed.");
|
||||||
@ -620,6 +622,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
else
|
else
|
||||||
m_context << u256(0);
|
m_context << u256(0);
|
||||||
m_context << Instruction::CREATE;
|
m_context << Instruction::CREATE;
|
||||||
|
solUnimplementedAssert(!_functionCall.annotation().tryCall, "");
|
||||||
// Check if zero (out of stack or not enough balance).
|
// Check if zero (out of stack or not enough balance).
|
||||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||||
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
|
// 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,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
{}
|
{},
|
||||||
|
false
|
||||||
);
|
);
|
||||||
if (function.kind() == FunctionType::Kind::Transfer)
|
if (function.kind() == FunctionType::Kind::Transfer)
|
||||||
{
|
{
|
||||||
@ -835,7 +839,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
m_context << contractAddresses.at(function.kind());
|
m_context << contractAddresses.at(function.kind());
|
||||||
for (unsigned i = function.sizeOnStack(); i > 0; --i)
|
for (unsigned i = function.sizeOnStack(); i > 0; --i)
|
||||||
m_context << swapInstruction(i);
|
m_context << swapInstruction(i);
|
||||||
appendExternalFunctionCall(function, arguments);
|
solAssert(!_functionCall.annotation().tryCall, "");
|
||||||
|
appendExternalFunctionCall(function, arguments, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::ByteArrayPush:
|
case FunctionType::Kind::ByteArrayPush:
|
||||||
@ -1964,7 +1969,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
|
|||||||
|
|
||||||
void ExpressionCompiler::appendExternalFunctionCall(
|
void ExpressionCompiler::appendExternalFunctionCall(
|
||||||
FunctionType const& _functionType,
|
FunctionType const& _functionType,
|
||||||
vector<ASTPointer<Expression const>> const& _arguments
|
vector<ASTPointer<Expression const>> const& _arguments,
|
||||||
|
bool _tryCall
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
@ -2000,6 +2006,12 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
|||||||
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
||||||
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
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();
|
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||||
unsigned retSize = 0;
|
unsigned retSize = 0;
|
||||||
bool dynamicReturnSize = false;
|
bool dynamicReturnSize = false;
|
||||||
@ -2171,17 +2183,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
|||||||
(_functionType.gasSet() ? 1 : 0) +
|
(_functionType.gasSet() ? 1 : 0) +
|
||||||
(!_functionType.isBareCall() ? 1 : 0);
|
(!_functionType.isBareCall() ? 1 : 0);
|
||||||
|
|
||||||
if (returnSuccessConditionAndReturndata)
|
eth::AssemblyItem endTag = m_context.newTag();
|
||||||
m_context << swapInstruction(remainsSize);
|
|
||||||
else
|
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 << Instruction::ISZERO;
|
||||||
m_context.appendConditionalRevert(true);
|
m_context.appendConditionalRevert(true);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
m_context << swapInstruction(remainsSize);
|
||||||
utils().popStackSlots(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)
|
if (returnSuccessConditionAndReturndata)
|
||||||
{
|
{
|
||||||
// success condition is already there
|
// success condition is already there
|
||||||
@ -2243,6 +2265,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
|||||||
|
|
||||||
utils().abiDecode(returnTypes, true);
|
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)
|
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.
|
/// 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(
|
void appendExternalFunctionCall(
|
||||||
FunctionType const& _functionType,
|
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
|
/// 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.
|
/// expected to be on the stack and is updated by this call.
|
||||||
|
Loading…
Reference in New Issue
Block a user