From e66383994d7f14b3903524bebe323b60efa448e4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 5 Jun 2015 17:32:13 +0200 Subject: [PATCH 1/2] Dynamic memory. --- libsolidity/AST.cpp | 3 -- libsolidity/Compiler.cpp | 51 +++++++++++++++++++----------- libsolidity/CompilerUtils.cpp | 19 ++++++++++- libsolidity/CompilerUtils.h | 12 ++++++- libsolidity/ExpressionCompiler.cpp | 37 +++++++++++++++------- 5 files changed, 87 insertions(+), 35 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 4c7168afa..7fd1425e5 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -462,9 +462,6 @@ void FunctionDefinition::checkTypeRequirements() { if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); - // todo delete when will be implemented arrays as parameter type in internal functions - if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array) - BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions.")); if (getVisibility() >= Visibility::Public && !(var->getType()->externalType())) BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions.")); } diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 0a75e55a9..0d7fbbfed 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -52,6 +52,7 @@ void Compiler::compileContract(ContractDefinition const& _contract, map const& _contracts) { m_context = CompilerContext(); // clear it just in case + CompilerUtils(m_context).initialiseFreeMemoryPointer(); initializeContext(_contract, _contracts); appendFunctionSelector(_contract); set functions = m_context.getFunctionsWithoutCode(); @@ -67,6 +68,7 @@ void Compiler::compileContract(ContractDefinition const& _contract, // Swap the runtime context with the creation-time context swap(m_context, m_runtimeContext); + CompilerUtils(m_context).initialiseFreeMemoryPointer(); initializeContext(_contract, _contracts); packIntoContractCreator(_contract, m_runtimeContext); if (m_optimize) @@ -233,31 +235,42 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool m_context << u256(CompilerUtils::dataStartOffset); for (TypePointer const& type: _typeParameters) { - switch (type->getCategory()) + if (type->getCategory() == Type::Category::Array) { - case Type::Category::Array: - if (type->isDynamicallySized()) + auto const& arrayType = dynamic_cast(*type); + if (arrayType.location() == ReferenceType::Location::CallData) { - // put on stack: data_pointer length - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); - // stack: data_offset next_pointer - //@todo once we support nested arrays, this offset needs to be dynamic. - m_context << eth::Instruction::SWAP1 << u256(CompilerUtils::dataStartOffset); - m_context << eth::Instruction::ADD; - // stack: next_pointer data_pointer - // retrieve length - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); - // stack: next_pointer length data_pointer - m_context << eth::Instruction::SWAP2; + if (type->isDynamicallySized()) + { + // put on stack: data_pointer length + CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); + // stack: data_offset next_pointer + //@todo once we support nested arrays, this offset needs to be dynamic. + m_context << eth::Instruction::SWAP1 << u256(CompilerUtils::dataStartOffset); + m_context << eth::Instruction::ADD; + // stack: next_pointer data_pointer + // retrieve length + CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); + // stack: next_pointer length data_pointer + m_context << eth::Instruction::SWAP2; + } + else + { + // leave the pointer on the stack + m_context << eth::Instruction::DUP1; + m_context << u256(type->getCalldataEncodedSize()) << eth::Instruction::ADD; + } } else { - // leave the pointer on the stack - m_context << eth::Instruction::DUP1; - m_context << u256(type->getCalldataEncodedSize()) << eth::Instruction::ADD; + solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + CompilerUtils(m_context).storeInMemoryDynamic(*type); + CompilerUtils(m_context).storeFreeMemoryPointer(); } - break; - default: + } + else + { solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true); } diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 3549ef98d..693bd4665 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -31,7 +31,24 @@ namespace dev namespace solidity { -const unsigned int CompilerUtils::dataStartOffset = 4; +const unsigned CompilerUtils::dataStartOffset = 4; +const size_t CompilerUtils::freeMemoryPointer = 64; + +void CompilerUtils::initialiseFreeMemoryPointer() +{ + m_context << u256(freeMemoryPointer + 32); + storeFreeMemoryPointer(); +} + +void CompilerUtils::fetchFreeMemoryPointer() +{ + m_context << u256(freeMemoryPointer) << eth::Instruction::MLOAD; +} + +void CompilerUtils::storeFreeMemoryPointer() +{ + m_context << u256(freeMemoryPointer) << eth::Instruction::MSTORE; +} unsigned CompilerUtils::loadFromMemory( unsigned _offset, diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 45f53e12e..30ea5cc67 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -35,6 +35,13 @@ class CompilerUtils public: CompilerUtils(CompilerContext& _context): m_context(_context) {} + /// Stores the initial value of the free-memory-pointer at its position; + void initialiseFreeMemoryPointer(); + /// Copies the free memory pointer to the stack. + void fetchFreeMemoryPointer(); + /// Stores the free memory pointer from the stack. + void storeFreeMemoryPointer(); + /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) /// @param _type data type to load @@ -95,7 +102,10 @@ public: /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. - static const unsigned int dataStartOffset; + static const unsigned dataStartOffset; + + /// Position of the free-memory-pointer in memory; + static const size_t freeMemoryPointer; private: /// Prepares the given type for storing in memory by shifting it if necessary. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index ba80a8ea2..31bb6dd10 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -1061,22 +1061,32 @@ void ExpressionCompiler::appendExternalFunctionCall( bool returnSuccessCondition = _functionType.getLocation() == FunctionType::Location::Bare || _functionType.getLocation() == FunctionType::Location::BareCallCode; + + // Output data will be at FreeMemPtr, replacing input data. + //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : _functionType.getReturnParameterTypes().front().get(); unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; if (returnSuccessCondition) retSize = 0; // return value actually is success condition - m_context << u256(retSize) << u256(0); + // put on stack: + m_context << u256(retSize); + CompilerUtils(m_context).fetchFreeMemoryPointer(); - if (_functionType.isBareCall()) - m_context << u256(0); - else + //@TODO CHECK ALL CALLS OF appendTypeMoveToMemory + + // copy arguments to memory and + // put on stack: + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP1; + if (!_functionType.isBareCall()) { // copy function identifier m_context << eth::dupInstruction(gasValueSize + 3); - CompilerUtils(m_context).storeInMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8)); - m_context << u256(CompilerUtils::dataStartOffset); + CompilerUtils(m_context).storeInMemoryDynamic( + IntegerType(CompilerUtils::dataStartOffset * 8), + false + ); } // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes, @@ -1090,10 +1100,11 @@ void ExpressionCompiler::appendExternalFunctionCall( _functionType.getLocation() == FunctionType::Location::BareCallCode, _functionType.takesArbitraryParameters() ); + // now on stack: ... + m_context << eth::Instruction::SUB << eth::Instruction::DUP2; - // CALL arguments: outSize, outOff, inSize, (already present up to here) - // inOff, value, addr, gas (stack top) - m_context << u256(0); + // CALL arguments: outSize, outOff, inSize, inOff (already present up to here) + // value, addr, gas (stack top) if (_functionType.valueSet()) m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); else @@ -1141,11 +1152,15 @@ void ExpressionCompiler::appendExternalFunctionCall( else if (_functionType.getLocation() == FunctionType::Location::RIPEMD160) { // fix: built-in contract returns right-aligned data - CompilerUtils(m_context).loadFromMemory(0, IntegerType(160), false, true); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(160), false, true, false); appendTypeConversion(IntegerType(160), FixedBytesType(20)); } else if (firstType) - CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true); + { + CompilerUtils(m_context).fetchFreeMemoryPointer(); + CompilerUtils(m_context).loadFromMemoryDynamic(*firstType, false, true, false); + } } void ExpressionCompiler::appendArgumentsCopyToMemory( From e66a5ca0b54f81b2a4c4b656c514dde027f98d51 Mon Sep 17 00:00:00 2001 From: chriseth Date: Sat, 6 Jun 2015 00:57:51 +0200 Subject: [PATCH 2/2] Use dynamic memory for argument encoding. --- libsolidity/CompilerUtils.cpp | 8 + libsolidity/CompilerUtils.h | 4 +- libsolidity/ExpressionCompiler.cpp | 307 +++++++++++------- libsolidity/ExpressionCompiler.h | 25 +- test/libsolidity/SolidityEndToEndTest.cpp | 51 ++- .../SolidityNameAndTypeResolution.cpp | 10 - 6 files changed, 267 insertions(+), 138 deletions(-) diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 693bd4665..7a96db928 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -50,6 +50,13 @@ void CompilerUtils::storeFreeMemoryPointer() m_context << u256(freeMemoryPointer) << eth::Instruction::MSTORE; } +void CompilerUtils::toSizeAfterFreeMemoryPointer() +{ + fetchFreeMemoryPointer(); + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::SUB; + m_context << eth::Instruction::SWAP1; +} + unsigned CompilerUtils::loadFromMemory( unsigned _offset, Type const& _type, @@ -204,6 +211,7 @@ unsigned CompilerUtils::getSizeOnStack(vector> const& _va void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundaries) { unsigned length = storeInMemory(0, _type, _padToWordBoundaries); + solAssert(length <= CompilerUtils::freeMemoryPointer, ""); m_context << u256(length) << u256(0) << eth::Instruction::SHA3; } diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 30ea5cc67..27c46ba11 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -41,6 +41,8 @@ public: void fetchFreeMemoryPointer(); /// Stores the free memory pointer from the stack. void storeFreeMemoryPointer(); + /// Appends code that transforms memptr to (memptr - free_memptr) memptr + void toSizeAfterFreeMemoryPointer(); /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) @@ -74,7 +76,7 @@ public: bool _padToWordBoundaries = false ); /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack - /// and also updates that. + /// and also updates that. For arrays, only copies the data part. /// Stack pre: memory_offset value... /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 31bb6dd10..d9b6da14e 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -73,6 +73,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { if (auto mappingType = dynamic_cast(returnType.get())) { + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); // pop offset m_context << eth::Instruction::POP; // move storage offset to memory. @@ -470,21 +471,28 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.getExpression().accept(*this); solAssert(!function.gasSet(), "Gas limit set for contract creation."); solAssert(function.getReturnParameterTypes().size() == 1, ""); + TypePointers argumentTypes; + for (auto const& arg: arguments) + { + arg->accept(*this); + argumentTypes.push_back(arg->getType()); + } ContractDefinition const& contract = dynamic_cast( *function.getReturnParameterTypes().front()).getContractDefinition(); // copy the contract's code into memory bytes const& bytecode = m_context.getCompiledContract(contract); - m_context << u256(bytecode.size()); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + m_context << u256(bytecode.size()) << eth::Instruction::DUP1; //@todo could be done by actually appending the Assembly, but then we probably need to compile // multiple times. Will revisit once external fuctions are inlined. m_context.appendData(bytecode); - //@todo copy to memory position 0, shift as soon as we use memory - m_context << u256(0) << eth::Instruction::CODECOPY; + m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; - m_context << u256(bytecode.size()); - appendArgumentsCopyToMemory(arguments, function.getParameterTypes()); - // size, offset, endowment - m_context << u256(0); + m_context << eth::Instruction::ADD; + encodeToMemory(argumentTypes, function.getParameterTypes()); + // now on stack: memory_end_ptr + // need: size, offset, endowment + CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); if (function.valueSet()) m_context << eth::dupInstruction(3); else @@ -546,12 +554,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::SHA3: { - // we might compute a sha as part of argumentsAppendCopyToMemory, this is only a hack - // and should be removed once we have a real free memory pointer - m_context << u256(0x40); - appendArgumentsCopyToMemory(arguments, TypePointers(), function.padArguments(), false, true); - m_context << u256(0x40) << eth::Instruction::SWAP1 << eth::Instruction::SUB; - m_context << u256(0x40) << eth::Instruction::SHA3; + TypePointers argumentTypes; + for (auto const& arg: arguments) + { + arg->accept(*this); + argumentTypes.push_back(arg->getType()); + } + CompilerUtils(m_context).fetchFreeMemoryPointer(); + encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); + CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + m_context << eth::Instruction::SHA3; break; } case Location::Log0: @@ -566,9 +578,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments[arg]->accept(*this); appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); } - m_context << u256(0); - appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front()); - m_context << u256(0) << eth::logInstruction(logNumber); + arguments.front()->accept(*this); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + encodeToMemory( + {arguments.front()->getType()}, + {function.getParameterTypes().front()}, + false, + true); + CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + m_context << eth::logInstruction(logNumber); break; } case Location::Event: @@ -582,8 +600,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { ++numIndexed; arguments[arg - 1]->accept(*this); - appendTypeConversion(*arguments[arg - 1]->getType(), - *function.getParameterTypes()[arg - 1], true); + appendTypeConversion( + *arguments[arg - 1]->getType(), + *function.getParameterTypes()[arg - 1], + true + ); } if (!event.isAnonymous()) { @@ -593,18 +614,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(numIndexed <= 4, "Too many indexed arguments."); // Copy all non-indexed arguments to memory (data) // Memory position is only a hack and should be removed once we have free memory pointer. - m_context << u256(0x40); - vector> nonIndexedArgs; - TypePointers nonIndexedTypes; + TypePointers nonIndexedArgTypes; + TypePointers nonIndexedParamTypes; for (unsigned arg = 0; arg < arguments.size(); ++arg) if (!event.getParameters()[arg]->isIndexed()) { - nonIndexedArgs.push_back(arguments[arg]); - nonIndexedTypes.push_back(function.getParameterTypes()[arg]); + arguments[arg]->accept(*this); + nonIndexedArgTypes.push_back(arguments[arg]->getType()); + nonIndexedParamTypes.push_back(function.getParameterTypes()[arg]); } - appendArgumentsCopyToMemory(nonIndexedArgs, nonIndexedTypes); - m_context << u256(0x40) << eth::Instruction::SWAP1 << eth::Instruction::SUB; - m_context << u256(0x40) << eth::logInstruction(numIndexed); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); + // need: topic1 ... topicn memsize memstart + CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + m_context << eth::logInstruction(numIndexed); break; } case Location::BlockHash: @@ -804,8 +827,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) Type const& keyType = *dynamic_cast(baseType).getKeyType(); m_context << u256(0); // memory position solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); + solAssert(keyType.getCalldataEncodedSize() <= 0x20, "Dynamic keys not yet implemented."); appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); m_context << eth::Instruction::SWAP1; + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; m_context << u256(0); @@ -1058,50 +1083,78 @@ void ExpressionCompiler::appendExternalFunctionCall( unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); unsigned valueStackPos = m_context.currentToBaseStackOffset(1); - bool returnSuccessCondition = - _functionType.getLocation() == FunctionType::Location::Bare || - _functionType.getLocation() == FunctionType::Location::BareCallCode; - - // Output data will be at FreeMemPtr, replacing input data. + using FunctionKind = FunctionType::Location; + FunctionKind funKind = _functionType.getLocation(); + bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; //@todo only return the first return value for now - Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : - _functionType.getReturnParameterTypes().front().get(); - unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; + Type const* firstReturnType = + _functionType.getReturnParameterTypes().empty() ? + nullptr : + _functionType.getReturnParameterTypes().front().get(); + unsigned retSize = firstReturnType ? firstReturnType->getCalldataEncodedSize() : 0; if (returnSuccessCondition) retSize = 0; // return value actually is success condition - // put on stack: - m_context << u256(retSize); - CompilerUtils(m_context).fetchFreeMemoryPointer(); - - //@TODO CHECK ALL CALLS OF appendTypeMoveToMemory - // copy arguments to memory and - // put on stack: - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP1; - if (!_functionType.isBareCall()) + // Evaluate arguments. + TypePointers argumentTypes; + bool manualFunctionId = + (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) && + !_arguments.empty() && + _arguments.front()->getType()->getRealType()->getCalldataEncodedSize(false) == + CompilerUtils::dataStartOffset; + if (manualFunctionId) { - // copy function identifier - m_context << eth::dupInstruction(gasValueSize + 3); - CompilerUtils(m_context).storeInMemoryDynamic( - IntegerType(CompilerUtils::dataStartOffset * 8), - false + // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as + // function identifier. + _arguments.front()->accept(*this); + appendTypeConversion( + *_arguments.front()->getType(), + IntegerType(8 * CompilerUtils::dataStartOffset), + true ); + for (unsigned i = 0; i < gasValueSize; ++i) + m_context << eth::swapInstruction(gasValueSize - i); + gasStackPos++; + valueStackPos++; + } + for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i) + { + _arguments[i]->accept(*this); + argumentTypes.push_back(_arguments[i]->getType()); } - // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes, - // do not pad it to 32 bytes. + // Copy function identifier to memory. + CompilerUtils(m_context).fetchFreeMemoryPointer(); + if (!_functionType.isBareCall() || manualFunctionId) + { + m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::getSizeOnStack(argumentTypes)); + appendTypeMoveToMemory(IntegerType(8 * CompilerUtils::dataStartOffset), false); + } // If the function takes arbitrary parameters, copy dynamic length data in place. - appendArgumentsCopyToMemory( - _arguments, + // Move argumenst to memory, will not update the free memory pointer (but will update the memory + // pointer on the stack). + encodeToMemory( + argumentTypes, _functionType.getParameterTypes(), _functionType.padArguments(), - _functionType.getLocation() == FunctionType::Location::Bare || - _functionType.getLocation() == FunctionType::Location::BareCallCode, _functionType.takesArbitraryParameters() ); - // now on stack: ... - m_context << eth::Instruction::SUB << eth::Instruction::DUP2; + + // Stack now: + // + // input_memory_end + // value [if _functionType.valueSet()] + // gas [if _functionType.gasSet()] + // function identifier [unless bare] + // contract address + + // Output data will replace input data. + // put on stack: + m_context << u256(retSize); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB; + m_context << eth::Instruction::DUP2; // CALL arguments: outSize, outOff, inSize, inOff (already present up to here) // value, addr, gas (stack top) @@ -1120,19 +1173,16 @@ void ExpressionCompiler::appendExternalFunctionCall( u256(eth::c_callGas + 10 + (_functionType.valueSet() ? eth::c_callValueTransferGas : 0) + eth::c_callNewAccountGas) << eth::Instruction::GAS << eth::Instruction::SUB; - if ( - _functionType.getLocation() == FunctionType::Location::CallCode || - _functionType.getLocation() == FunctionType::Location::BareCallCode - ) + if (funKind == FunctionKind::CallCode || funKind == FunctionKind::BareCallCode) m_context << eth::Instruction::CALLCODE; else m_context << eth::Instruction::CALL; unsigned remainsSize = - 1 + // contract address + 2 + // contract address, input_memory_end _functionType.valueSet() + _functionType.gasSet() + - !_functionType.isBareCall(); + (!_functionType.isBareCall() || manualFunctionId); if (returnSuccessCondition) m_context << eth::swapInstruction(remainsSize); @@ -1149,56 +1199,93 @@ void ExpressionCompiler::appendExternalFunctionCall( { // already there } - else if (_functionType.getLocation() == FunctionType::Location::RIPEMD160) + else if (funKind == FunctionKind::RIPEMD160) { // fix: built-in contract returns right-aligned data CompilerUtils(m_context).fetchFreeMemoryPointer(); CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(160), false, true, false); appendTypeConversion(IntegerType(160), FixedBytesType(20)); } - else if (firstType) + else if (firstReturnType) { + //@todo manually update free memory pointer if we accept returning memory-stored objects CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).loadFromMemoryDynamic(*firstType, false, true, false); + CompilerUtils(m_context).loadFromMemoryDynamic(*firstReturnType, false, true, false); } } -void ExpressionCompiler::appendArgumentsCopyToMemory( - vector> const& _arguments, - TypePointers const& _types, +void ExpressionCompiler::encodeToMemory( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, bool _padToWordBoundaries, - bool _padExceptionIfFourBytes, bool _copyDynamicDataInPlace ) { - solAssert(_types.empty() || _types.size() == _arguments.size(), ""); - TypePointers types = _types; - if (_types.empty()) - for (ASTPointer const& argument: _arguments) - types.push_back(argument->getType()->getRealType()); - - vector dynamicArguments; - unsigned stackSizeOfDynamicTypes = 0; - for (size_t i = 0; i < _arguments.size(); ++i) + // stack: ... + TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; + solAssert(targetTypes.size() == _givenTypes.size(), ""); + for (TypePointer& t: targetTypes) + t = t->getRealType()->externalType(); + + // Stack during operation: + // ... ... + // The values dyn_head_i are added during the first loop and they point to the head part + // of the ith dynamic parameter, which is filled once the dynamic parts are processed. + + // store memory start pointer + m_context << eth::Instruction::DUP1; + + unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); + unsigned stackPos = 0; // advances through the argument values + unsigned dynPointers = 0; // number of dynamic head pointers on the stack + for (size_t i = 0; i < _givenTypes.size(); ++i) { - _arguments[i]->accept(*this); - TypePointer argType = types[i]->externalType(); - solAssert(!!argType, "Externalable type expected."); - if (argType->isValueType()) - appendTypeConversion(*_arguments[i]->getType(), *argType, true); + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + // leave end_of_mem as dyn head pointer + m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; + dynPointers++; + } else - argType = _arguments[i]->getType()->getRealType()->externalType(); - solAssert(!!argType, "Externalable type expected."); - bool pad = _padToWordBoundaries; - // Do not pad if the first argument has exactly four bytes - if (i == 0 && pad && _padExceptionIfFourBytes && argType->getCalldataEncodedSize(false) == 4) - pad = false; - if (!_copyDynamicDataInPlace && argType->isDynamicallySized()) { - solAssert(argType->getCategory() == Type::Category::Array, "Unknown dynamic type."); - auto const& arrayType = dynamic_cast(*_arguments[i]->getType()); - // move memory reference to top of stack - CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack()); + CompilerUtils(m_context).copyToStackTop( + argSize - stackPos + dynPointers + 2, + _givenTypes[i]->getSizeOnStack() + ); + if (targetType->isValueType()) + appendTypeConversion(*_givenTypes[i], *targetType, true); + solAssert(!!targetType, "Externalable type expected."); + appendTypeMoveToMemory(*targetType, _padToWordBoundaries); + } + stackPos += _givenTypes[i]->getSizeOnStack(); + } + + // now copy the dynamic part + // Stack: ... ... + stackPos = 0; + unsigned thisDynPointer = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); + auto const& arrayType = dynamic_cast(*_givenTypes[i]); + // copy tail pointer (=mem_end - mem_start) to memory + m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; + m_context << eth::Instruction::SUB; + m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); + m_context << eth::Instruction::MSTORE; + // now copy the array + CompilerUtils(m_context).copyToStackTop( + argSize - stackPos + dynPointers + 2, + arrayType.getSizeOnStack() + ); + // copy length to memory + m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); if (arrayType.location() == ReferenceType::Location::CallData) m_context << eth::Instruction::DUP2; // length is on stack else if (arrayType.location() == ReferenceType::Location::Storage) @@ -1209,31 +1296,19 @@ void ExpressionCompiler::appendArgumentsCopyToMemory( m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; } appendTypeMoveToMemory(IntegerType(256), true); - stackSizeOfDynamicTypes += arrayType.getSizeOnStack(); - dynamicArguments.push_back(i); - } - else - appendTypeMoveToMemory(*argType, pad); - } + // copy the new memory pointer + m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; + // copy data part + appendTypeMoveToMemory(arrayType, true); - // copy dynamic values to memory - unsigned dynStackPointer = stackSizeOfDynamicTypes; - // stack layout: ... - for (size_t i: dynamicArguments) - { - auto const& arrayType = dynamic_cast(*_arguments[i]->getType()); - CompilerUtils(m_context).copyToStackTop(1 + dynStackPointer, arrayType.getSizeOnStack()); - dynStackPointer -= arrayType.getSizeOnStack(); - appendTypeMoveToMemory(arrayType, true); + thisDynPointer++; + } + stackPos += _givenTypes[i]->getSizeOnStack(); } - solAssert(dynStackPointer == 0, ""); - // remove dynamic values (and retain memory pointer) - if (stackSizeOfDynamicTypes > 0) - { - m_context << eth::swapInstruction(stackSizeOfDynamicTypes); - CompilerUtils(m_context).popStackSlots(stackSizeOfDynamicTypes); - } + // remove unneeded stack elements (and retain memory pointer) + m_context << eth::swapInstruction(argSize + dynPointers + 1); + CompilerUtils(m_context).popStackSlots(argSize + dynPointers + 1); } void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries) diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 174e16d8d..90994dfdb 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -98,21 +98,28 @@ private: void appendHighBitsCleanup(IntegerType const& _typeOnStack); /// Appends code to call a function of the given type with the given arguments. - void appendExternalFunctionCall(FunctionType const& _functionType, std::vector> const& _arguments); - /// Appends code that evaluates the given arguments and moves the result to memory encoded as - /// specified by the ABI. The memory offset is expected to be on the stack and is updated by - /// this call. If @a _padToWordBoundaries is set to false, all values are concatenated without - /// padding. If @a _copyDynamicDataInPlace is set, dynamic types is stored (without length) + void appendExternalFunctionCall( + FunctionType const& _functionType, + std::vector> const& _arguments + ); + /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given + /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. + /// Removes the values from the stack and leaves the updated memory pointer. + /// Stack pre: ... + /// Stack post: + /// Does not touch the memory-free pointer. + /// @param _padToWordBoundaries if false, all values are concatenated without padding. + /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) /// together with fixed-length data. - void appendArgumentsCopyToMemory( - std::vector> const& _arguments, - TypePointers const& _types = {}, + void encodeToMemory( + TypePointers const& _givenTypes = {}, + TypePointers const& _targetTypes = {}, bool _padToWordBoundaries = true, - bool _padExceptionIfFourBytes = false, bool _copyDynamicDataInPlace = false ); /// Appends code that moves a stack element of the given type to memory. The memory offset is /// expected below the stack element and is updated by this call. + /// For arrays, this only copies the data part. void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true); /// 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. diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 89ed81e23..73f7d60d6 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2396,7 +2396,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 4, 15) + FixedHash<4>(dev::sha3("deposit()")).asBytes()); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 4) + FixedHash<4>(dev::sha3("deposit()")).asBytes()); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -2420,7 +2420,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data_from_storage) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 3, 15) + asBytes("ABC")); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3) + asBytes("ABC")); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -2531,6 +2531,27 @@ BOOST_AUTO_TEST_CASE(sha3_with_bytes) BOOST_CHECK(callContractFunction("foo()") == encodeArgs(true)); } +BOOST_AUTO_TEST_CASE(iterated_sha3_with_bytes) +{ + char const* sourceCode = R"( + contract c { + bytes data; + function foo() returns (bytes32) + { + data.length = 3; + data[0] = "x"; + data[1] = "y"; + data[2] = "z"; + return sha3("b", sha3(data), "a"); + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("foo()") == encodeArgs( + u256(dev::sha3(bytes{'b'} + dev::sha3("xyz").asBytes() + bytes{'a'})) + )); +} + BOOST_AUTO_TEST_CASE(generic_call) { char const* sourceCode = R"**( @@ -4209,6 +4230,32 @@ BOOST_AUTO_TEST_CASE(failing_send) BOOST_REQUIRE(callContractFunction("callHelper(address)", c_helperAddress) == encodeArgs(true, 20)); } +BOOST_AUTO_TEST_CASE(reusing_memory) +{ + // Invoke some features that use memory and test that they do not interfere with each other. + char const* sourceCode = R"( + contract Helper { + uint public flag; + function Helper(uint x) { + flag = x; + } + } + contract Main { + mapping(uint => uint) map; + function f(uint x) returns (uint) { + map[x] = x; + return (new Helper(uint(sha3(this.g(map[x]))))).flag(); + } + function g(uint a) returns (uint) + { + return map[a]; + } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction("f(uint256)", 0x34) == encodeArgs(dev::sha3(dev::toBigEndian(u256(0x34))))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 73bbcb162..111637f43 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -558,16 +558,6 @@ BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } -// todo delete when implemented -BOOST_AUTO_TEST_CASE(arrays_in_internal_functions) -{ - char const* text = R"( - contract Test { - function foo(address[] addresses) {} - })"; - BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); -} - BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion) { char const* text = R"(