From e047c8d4d90d549b75f91ea5395c60820e2152ba Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 6 May 2019 19:48:31 +0200 Subject: [PATCH] Code generator for external function calls. --- libsolidity/codegen/YulUtilFunctions.cpp | 25 +++ libsolidity/codegen/YulUtilFunctions.h | 4 + .../codegen/ir/IRGenerationContext.cpp | 8 + libsolidity/codegen/ir/IRGenerationContext.h | 3 + .../codegen/ir/IRGeneratorForStatements.cpp | 200 +++++++++++++++++- .../codegen/ir/IRGeneratorForStatements.h | 12 ++ 6 files changed, 249 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 5431436c4..01e6543cc 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -1036,6 +1036,31 @@ string YulUtilFunctions::packedHashFunction( }); } +string YulUtilFunctions::forwardingRevertFunction() +{ + bool forward = m_evmVersion.supportsReturndata(); + string functionName = "revert_forward_" + to_string(forward); + return m_functionCollector->createFunction(functionName, [&]() { + if (forward) + return Whiskers(R"( + function () { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function () { + revert(0, 0) + } + )") + ("functionName", functionName) + .render(); + }); +} + string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) { string result; diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 9150d2bbd..d2ce6be14 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -161,6 +161,10 @@ public: std::string packedHashFunction(std::vector const& _givenTypes, std::vector const& _targetTypes); + /// @returns the name of a function that reverts and uses returndata (if available) + /// as reason string. + std::string forwardingRevertFunction(); + /// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed /// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, /// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix. diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 3032fb94d..225cd6f49 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -101,6 +101,14 @@ string IRGenerationContext::variable(Expression const& _expression) return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size); } +string IRGenerationContext::variablePart(Expression const& _expression, size_t _part) +{ + size_t numVars = _expression.annotation().type->sizeOnStack(); + solAssert(numVars > 1, ""); + solAssert(1 <= _part && _part <= numVars, ""); + return "expr_" + to_string(_expression.id()) + "_" + to_string(_part); +} + string IRGenerationContext::internalDispatch(size_t _in, size_t _out) { // TODO can we limit the generated functions to only those visited diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 70ce39031..7152975df 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -83,6 +83,9 @@ public: /// @returns the variable (or comma-separated list of variables) that contain /// the value of the given expression. std::string variable(Expression const& _expression); + /// @returns the variable of a multi-variable expression. Variables are numbered + /// starting from 1. + std::string variablePart(Expression const& _expression, size_t _part); std::string internalDispatch(size_t _in, size_t _out); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 8835820f0..c9bfe639c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -28,6 +28,8 @@ #include #include +#include + #include #include #include @@ -392,6 +394,15 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) ")\n"; break; } + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + appendExternalFunctionCall(_functionCall, arguments); + break; + case FunctionType::Kind::BareCallCode: + solAssert(false, "Callcode has been removed."); case FunctionType::Kind::Event: { auto const& event = dynamic_cast(functionType->declaration()); @@ -502,9 +513,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - // TODO here, we need to assign address and function identifier to two variables. - // We migt also just combine them into a single variable already.... - solUnimplementedAssert(false, ""); + + defineExpressionPart(_memberAccess, 1) << expressionAsType( + _memberAccess.expression(), + type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address() + ) << "\n"; + defineExpressionPart(_memberAccess, 2) << formatNumber(identifier) << "\n"; } else solAssert(false, "Invalid member access in contract"); @@ -763,6 +777,181 @@ bool IRGeneratorForStatements::visit(Literal const& _literal) return false; } +void IRGeneratorForStatements::appendExternalFunctionCall( + FunctionCall const& _functionCall, + vector> const& _arguments +) +{ + FunctionType const& funType = dynamic_cast(type(_functionCall.expression())); + solAssert( + funType.takesArbitraryParameters() || + _arguments.size() == funType.parameterTypes().size(), "" + ); + solUnimplementedAssert(!funType.bound(), ""); + FunctionType::Kind funKind = funType.kind(); + + solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); + solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); + + bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; + bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; + bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); + + bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); + unsigned retSize = 0; + bool dynamicReturnSize = false; + TypePointers returnTypes; + if (!returnSuccessConditionAndReturndata) + { + if (haveReturndatacopy) + returnTypes = funType.returnParameterTypes(); + else + returnTypes = funType.returnParameterTypesWithoutDynamicTypes(); + + for (auto const& retType: returnTypes) + if (retType->isDynamicallyEncoded()) + { + solAssert(haveReturndatacopy, ""); + dynamicReturnSize = true; + retSize = 0; + break; + } + else if (retType->decodingType()) + retSize += retType->decodingType()->calldataEncodedSize(); + else + retSize += retType->calldataEncodedSize(); + } + + TypePointers argumentTypes; + string argumentString; + for (auto const& arg: _arguments) + { + argumentTypes.emplace_back(&type(*arg)); + string var = m_context.variable(*arg); + if (!var.empty()) + argumentString += ", " + move(var); + } + + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + if (!m_context.evmVersion().canOverchargeGasForCall()) + { + // Touch the end of the output area so that we do not pay for memory resize during the call + // (which we would have to subtract from the gas left) + // We could also just use MLOAD; POP right before the gas calculation, but the optimizer + // would remove that, so we use MSTORE here. + if (!funType.gasSet() && retSize > 0) + m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n"; + } + + ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + + solUnimplementedAssert(!funType.isBareCall(), ""); + Whiskers templ(R"( + + if iszero(extcodesize(
)) { revert(0, 0) } + + + let := + mstore(, ()) + let := (add(, 4) ) + + let := (,
, , , sub(, ), , ) + if iszero() { } + + + returndatacopy(, 0, returndatasize()) + + + mstore(, add(, and(add(, 0x1f), not(0x1f)))) + let := (, ) + )"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("result", m_context.newYulVariable()); + templ("freeMem", fetchFreeMem()); + templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4))); + templ("funId", m_context.variablePart(_functionCall.expression(), 2)); + + // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place. + // Move arguments to memory, will not update the free memory pointer (but will update the memory + // pointer on the stack). + bool encodeInPlace = funType.takesArbitraryParameters() || funType.isBareCall(); + if (funType.kind() == FunctionType::Kind::ECRecover) + // This would be the only combination of padding and in-place encoding, + // but all parameters of ecrecover are value types anyway. + encodeInPlace = false; + bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; + solUnimplementedAssert(!encodeInPlace, ""); + solUnimplementedAssert(!funType.padArguments(), ""); + templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall)); + templ("argumentString", argumentString); + + // Output data will replace input data, unless we have ECRecover (then, output + // area will be 32 bytes just before input area). + templ("retSize", to_string(retSize)); + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + if (isDelegateCall) + solAssert(!funType.valueSet(), "Value set for delegatecall"); + else if (useStaticCall) + solAssert(!funType.valueSet(), "Value set for staticcall"); + else if (funType.valueSet()) + templ("value", m_context.variablePart(_functionCall.expression(), 4)); + else + templ("value", "0"); + + // Check that the target contract exists (has code) for non-low-level calls. + bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall); + templ("checkExistence", checkExistence); + + if (funType.gasSet()) + templ("gas", m_context.variablePart(_functionCall.expression(), 3)); + else if (m_context.evmVersion().canOverchargeGasForCall()) + // Send all gas (requires tangerine whistle EVM) + templ("gas", "gas()"); + else + { + // send all gas except the amount needed to execute "SUB" and "CALL" + // @todo this retains too much gas for now, needs to be fine-tuned. + u256 gasNeededByCaller = eth::GasCosts::callGas(m_context.evmVersion()) + 10; + if (funType.valueSet()) + gasNeededByCaller += eth::GasCosts::callValueTransferGas; + if (!checkExistence) + gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know + templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); + } + // Order is important here, STATICCALL might overlap with DELEGATECALL. + if (isDelegateCall) + templ("call", "delegatecall"); + else if (useStaticCall) + templ("call", "staticcall"); + else + templ("call", "call"); + + templ("forwardingRevert", m_utils.forwardingRevertFunction()); + + solUnimplementedAssert(!returnSuccessConditionAndReturndata, ""); + solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, ""); + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + templ("dynamicReturnSize", dynamicReturnSize); + // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. + // This ensures it can catch badly formatted input from external calls. + if (haveReturndatacopy) + templ("returnSize", "returndatasize()"); + else + templ("returnSize", to_string(retSize)); + templ("abiDecode", abi.tupleDecoder(returnTypes, true)); + templ("returns", !returnTypes.empty()); + templ("retVars", m_context.variable(_functionCall)); +} + +string IRGeneratorForStatements::fetchFreeMem() const +{ + return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")"; +} + string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to) { Type const& from = type(_expression); @@ -787,6 +976,11 @@ ostream& IRGeneratorForStatements::defineExpression(Expression const& _expressio return m_code << "let " << m_context.variable(_expression) << " := "; } +ostream& IRGeneratorForStatements::defineExpressionPart(Expression const& _expression, size_t _part) +{ + return m_code << "let " << m_context.variablePart(_expression, _part) << " := "; +} + void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp) { langutil::Token const op = _binOp.getOperator(); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 20225f35a..79737e477 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -64,10 +64,22 @@ public: bool visit(Literal const& _literal) override; private: + /// Appends code to call an external function with the given arguments. + /// All involved expressions have already been visited. + void appendExternalFunctionCall( + FunctionCall const& _functionCall, + std::vector> const& _arguments + ); + + std::string fetchFreeMem() const; + /// @returns a Yul expression representing the current value of @a _expression, /// converted to type @a _to if it does not yet have that type. std::string expressionAsType(Expression const& _expression, Type const& _to); std::ostream& defineExpression(Expression const& _expression); + /// Defines only one of many variables corresponding to an expression. + /// We start counting at 1 instead of 0. + std::ostream& defineExpressionPart(Expression const& _expression, size_t _part); void appendAndOrOperatorCode(BinaryOperation const& _binOp);