Code generator for try/catch.

This commit is contained in:
chriseth 2019-09-05 20:02:58 +02:00
parent 8e736a9f49
commit 70b796bd1a
4 changed files with 226 additions and 12 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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.