From 70b796bd1afb02763f18a4bd9229b07c1f5caa6c Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 5 Sep 2019 20:02:58 +0200 Subject: [PATCH] Code generator for try/catch. --- libsolidity/codegen/ContractCompiler.cpp | 179 +++++++++++++++++++++ libsolidity/codegen/ContractCompiler.h | 3 + libsolidity/codegen/ExpressionCompiler.cpp | 51 ++++-- libsolidity/codegen/ExpressionCompiler.h | 5 +- 4 files changed, 226 insertions(+), 12 deletions(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a9c3990f8..587b13446 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -34,6 +34,8 @@ #include +#include + #include #include @@ -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] + 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 exprTypes{_tryStatement.externalCall().annotation().type}; + if (auto tupleType = dynamic_cast(exprTypes.front())) + exprTypes = tupleType->components(); + vector> 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> const& _catchClauses) +{ + // Stack is empty. + ASTPointer structured{}; + ASTPointer 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 := + if iszero(eq(sig, 0x)) { 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 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); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 86013eb87..c781b44f8 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -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> 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; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index bc4a6de8e..2ee9db441 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -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> const& _arguments + vector> 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) diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 8bdb1479a..c29794858 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -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> const& _arguments + std::vector> 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.