diff options
Diffstat (limited to 'libsolidity/codegen/ExpressionCompiler.cpp')
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 111 |
1 files changed, 90 insertions, 21 deletions
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b973a117..3d05edd3 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -23,6 +23,7 @@ #include <utility> #include <numeric> #include <boost/range/adaptor/reversed.hpp> +#include <boost/algorithm/string/replace.hpp> #include <libdevcore/Common.h> #include <libdevcore/SHA3.h> #include <libsolidity/ast/AST.h> @@ -297,9 +298,6 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::BitNot: // ~ m_context << Instruction::NOT; break; - case Token::After: // after - m_context << Instruction::TIMESTAMP << Instruction::ADD; - break; case Token::Delete: // delete solAssert(!!m_currentLValue, "LValue not retrieved."); m_currentLValue->setToZero(_unaryOperation.location()); @@ -536,6 +534,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) else m_context << u256(0); m_context << Instruction::CREATE; + // Check if zero (out of stack or not enough balance). + m_context << Instruction::DUP1 << Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -567,12 +568,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::Send: _functionCall.expression().accept(*this); - m_context << u256(0); // do not send gas (there still is the stipend) + // Provide the gas stipend manually at first because we may send zero ether. + // Will be zeroed if we send more than zero ether. + m_context << u256(eth::GasCosts::callStipend); arguments.front()->accept(*this); utils().convertType( *arguments.front()->annotation().type, *function.parameterTypes().front(), true ); + // gas <- gas * !value + m_context << Instruction::SWAP1 << Instruction::DUP2; + m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; appendExternalFunctionCall( FunctionType( TypePointers{}, @@ -582,6 +588,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) Location::Bare, false, nullptr, + false, + false, true, true ), @@ -662,7 +670,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } if (!event.isAnonymous()) { - m_context << u256(h256::Arith(dev::sha3(function.externalSignature()))); + m_context << u256(h256::Arith(dev::keccak256(function.externalSignature()))); ++numIndexed; } solAssert(numIndexed <= 4, "Too many indexed arguments."); @@ -792,15 +800,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().storeFreeMemoryPointer(); // Stack: memptr requested_length + // Check if length is zero + m_context << Instruction::DUP1 << Instruction::ISZERO; + auto skipInit = m_context.appendConditionalJump(); + // We only have to initialise if the base type is a not a value type. if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get())) { m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; utils().zeroInitialiseMemoryArray(arrayType); - m_context << Instruction::POP; } - else - m_context << Instruction::POP; + m_context << skipInit; + m_context << Instruction::POP; break; } default: @@ -1320,11 +1331,18 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty m_context << Instruction::MUL; break; case Token::Div: - m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); - break; case Token::Mod: - m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); + { + // Test for division by zero + m_context << Instruction::DUP2 << Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + + if (_operator == Token::Div) + m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); + else + m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); break; + } case Token::Exp: m_context << Instruction::EXP; break; @@ -1445,6 +1463,31 @@ void ExpressionCompiler::appendExternalFunctionCall( argumentTypes.push_back(_arguments[i]->annotation().type); } + if (funKind == FunctionKind::ECRecover) + { + // Clears 32 bytes of currently free memory and advances free memory pointer. + // Output area will be "start of input area" - 32. + // The reason is that a failing ECRecover cannot be detected, it will just return + // zero bytes (which we cannot detect). + solAssert(0 < retSize && retSize <= 32, ""); + utils().fetchFreeMemoryPointer(); + m_context << Instruction::DUP1 << u256(0) << Instruction::MSTORE; + m_context << u256(32) << Instruction::ADD; + utils().storeFreeMemoryPointer(); + } + + // 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 (!_functionType.gasSet() && retSize > 0) + { + m_context << u256(0); + utils().fetchFreeMemoryPointer(); + // This touches too much, but that way we save some rounding arithmetics + m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + } + // Copy function identifier to memory. utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall() || manualFunctionId) @@ -1453,7 +1496,7 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false); } // If the function takes arbitrary parameters, copy dynamic length data in place. - // Move argumenst to memory, will not update the free memory pointer (but will update the memory + // Move arguments to memory, will not update the free memory pointer (but will update the memory // pointer on the stack). utils().encodeToMemory( argumentTypes, @@ -1471,12 +1514,24 @@ void ExpressionCompiler::appendExternalFunctionCall( // function identifier [unless bare] // contract address - // Output data will replace input data. + // Output data will replace input data, unless we have ECRecover (then, output + // area will be 32 bytes just before input area). // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input> m_context << u256(retSize); - utils().fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::SUB; - m_context << Instruction::DUP2; + utils().fetchFreeMemoryPointer(); // This is the start of input + if (funKind == FunctionKind::ECRecover) + { + // In this case, output is 32 bytes before input and has already been cleared. + m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1; + // Here: <input end> <output size> <outpos> <input pos> + m_context << Instruction::DUP1 << Instruction::DUP5 << Instruction::SUB; + m_context << Instruction::SWAP1; + } + else + { + m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::SUB; + m_context << Instruction::DUP2; + } // CALL arguments: outSize, outOff, inSize, inOff (already present up to here) // [value,] addr, gas (stack top) @@ -1488,6 +1543,15 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context << u256(0); m_context << dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + bool existenceChecked = false; + // Check the the target contract exists (has code) for non-low-level calls. + if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) + { + m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + existenceChecked = true; + } + if (_functionType.gasSet()) m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); else @@ -1497,12 +1561,9 @@ void ExpressionCompiler::appendExternalFunctionCall( u256 gasNeededByCaller = eth::GasCosts::callGas + 10; if (_functionType.valueSet()) gasNeededByCaller += eth::GasCosts::callValueTransferGas; - if (!isCallCode && !isDelegateCall) + if (!isCallCode && !isDelegateCall && !existenceChecked) gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know - m_context << - gasNeededByCaller << - Instruction::GAS << - Instruction::SUB; + m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } if (isDelegateCall) m_context << Instruction::DELEGATECALL; @@ -1539,6 +1600,14 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); utils().convertType(IntegerType(160), FixedBytesType(20)); } + else if (funKind == FunctionKind::ECRecover) + { + // Output is 32 bytes before input / free mem pointer. + // Failing ecrecover cannot be detected, so we clear output before the call. + m_context << u256(32); + utils().fetchFreeMemoryPointer(); + m_context << Instruction::SUB << Instruction::MLOAD; + } else if (!_functionType.returnParameterTypes().empty()) { utils().fetchFreeMemoryPointer(); |