diff options
-rw-r--r-- | Changelog.md | 4 | ||||
-rw-r--r-- | libsolidity/codegen/ArrayUtils.cpp | 878 | ||||
-rw-r--r-- | libsolidity/codegen/ArrayUtils.h | 5 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.cpp | 54 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.h | 33 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 67 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.cpp | 1 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 22 |
8 files changed, 607 insertions, 457 deletions
diff --git a/Changelog.md b/Changelog.md index cd3e31b2..7dd607d8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,10 @@ Features: * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. + * Code generator: Extract array utils into low-level functions. + +Bugfixes: + * Code generator: Allow recursive structs. * Type checker: Allow multiple events of the same name (but with different arities or argument types) ### 0.4.8 (2017-01-13) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 352c7177..4d100d1d 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - IntegerType uint256(256); - Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType()); - Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType()); + TypePointer uint256 = make_shared<IntegerType>(256); + TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); + TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); // TODO unroll loop for small sizes @@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } // stack: target_ref source_ref source_length - m_context << Instruction::DUP3; - // stack: target_ref source_ref source_length target_ref - retrieveLength(_targetType); - // stack: target_ref source_ref source_length target_ref target_length - if (_targetType.isDynamicallySized()) - // store new target length - if (!_targetType.isByteArray()) - // Otherwise, length will be stored below. - m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; - if (sourceBaseType->category() == Type::Category::Mapping) - { - solAssert(targetBaseType->category() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == DataLocation::Storage, ""); - // nothing to copy - m_context - << Instruction::POP << Instruction::POP - << Instruction::POP << Instruction::POP; - return; - } - // stack: target_ref source_ref source_length target_ref target_length - // compute hashes (data positions) - m_context << Instruction::SWAP1; - if (_targetType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref source_ref source_length target_length target_data_pos - m_context << Instruction::SWAP1; - convertLengthToSize(_targetType); - m_context << Instruction::DUP2 << Instruction::ADD; - // stack: target_ref source_ref source_length target_data_pos target_data_end - m_context << Instruction::SWAP3; - // stack: target_ref target_data_end source_length target_data_pos source_ref - - eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); - - // special case for short byte arrays: Store them together with their length. - if (_targetType.isByteArray()) - { - // stack: target_ref target_data_end source_length target_data_pos source_ref - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); - // store the short byte array - solAssert(_sourceType.isByteArray(), ""); - if (_sourceType.location() == DataLocation::Storage) - { - // just copy the slot, it contains length and data - m_context << Instruction::DUP1 << Instruction::SLOAD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } - else + TypePointer targetType = _targetType.shared_from_this(); + TypePointer sourceType = _sourceType.shared_from_this(); + m_context.callLowLevelFunction( + "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), + 3, + 1, + [=](CompilerContext& _context) { - m_context << Instruction::DUP1; - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - // stack: target_ref target_data_end source_length target_data_pos source_ref value - // clear the lower-order byte - which will hold the length - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // fetch the length and shift it left by one - m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; - // combine value and length and store them - m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; - } - // end of special case, jump right into cleaning target data area - m_context.appendJumpTo(copyLoopEndWithoutByteOffset); - m_context << longByteArray; - // Store length (2*length+1) - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } + ArrayUtils utils(_context); + ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType); + ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType); + // stack: target_ref source_ref source_length + _context << Instruction::DUP3; + // stack: target_ref source_ref source_length target_ref + utils.retrieveLength(_targetType); + // stack: target_ref source_ref source_length target_ref target_length + if (_targetType.isDynamicallySized()) + // store new target length + if (!_targetType.isByteArray()) + // Otherwise, length will be stored below. + _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; + if (sourceBaseType->category() == Type::Category::Mapping) + { + solAssert(targetBaseType->category() == Type::Category::Mapping, ""); + solAssert(_sourceType.location() == DataLocation::Storage, ""); + // nothing to copy + _context + << Instruction::POP << Instruction::POP + << Instruction::POP << Instruction::POP; + return; + } + // stack: target_ref source_ref source_length target_ref target_length + // compute hashes (data positions) + _context << Instruction::SWAP1; + if (_targetType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref source_ref source_length target_length target_data_pos + _context << Instruction::SWAP1; + utils.convertLengthToSize(_targetType); + _context << Instruction::DUP2 << Instruction::ADD; + // stack: target_ref source_ref source_length target_data_pos target_data_end + _context << Instruction::SWAP3; + // stack: target_ref target_data_end source_length target_data_pos source_ref + + eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); + + // special case for short byte arrays: Store them together with their length. + if (_targetType.isByteArray()) + { + // stack: target_ref target_data_end source_length target_data_pos source_ref + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem longByteArray = _context.appendConditionalJump(); + // store the short byte array + solAssert(_sourceType.isByteArray(), ""); + if (_sourceType.location() == DataLocation::Storage) + { + // just copy the slot, it contains length and data + _context << Instruction::DUP1 << Instruction::SLOAD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } + else + { + _context << Instruction::DUP1; + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + // stack: target_ref target_data_end source_length target_data_pos source_ref value + // clear the lower-order byte - which will hold the length + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // fetch the length and shift it left by one + _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; + // combine value and length and store them + _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; + } + // end of special case, jump right into cleaning target data area + _context.appendJumpTo(copyLoopEndWithoutByteOffset); + _context << longByteArray; + // Store length (2*length+1) + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } - // skip copying if source length is zero - m_context << Instruction::DUP3 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); - - if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_data_end source_length target_data_pos source_data_pos - m_context << Instruction::SWAP2; - convertLengthToSize(_sourceType); - m_context << Instruction::DUP3 << Instruction::ADD; - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end - if (haveByteOffsetTarget) - m_context << u256(0); - if (haveByteOffsetSource) - m_context << u256(0); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context - << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump(); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // copy - if (sourceBaseType->category() == Type::Category::Array) - { - solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); - auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); - m_context << Instruction::DUP3; - if (sourceBaseArrayType.location() == DataLocation::Memory) - m_context << Instruction::MLOAD; - m_context << Instruction::DUP3; - copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); - m_context << Instruction::POP; - } - else if (directCopy) - { - solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); - m_context - << Instruction::DUP3 << Instruction::SLOAD - << Instruction::DUP3 << Instruction::SSTORE; - } - else - { - // Note that we have to copy each element on its own in case conversion is involved. - // We might copy too much if there is padding at the last element, but this way end - // checking is easier. - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - m_context << dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == DataLocation::Storage) - { + // skip copying if source length is zero + _context << Instruction::DUP3 << Instruction::ISZERO; + _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); + + if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref target_data_end source_length target_data_pos source_data_pos + _context << Instruction::SWAP2; + utils.convertLengthToSize(_sourceType); + _context << Instruction::DUP3 << Instruction::ADD; + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + _context << u256(0); if (haveByteOffsetSource) - m_context << Instruction::DUP2; + _context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + eth::AssemblyItem copyLoopStart = _context.newTag(); + _context << copyLoopStart; + // check for loop condition + _context + << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // copy + if (sourceBaseType->category() == Type::Category::Array) + { + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); + auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); + _context << Instruction::DUP3; + if (sourceBaseArrayType.location() == DataLocation::Memory) + _context << Instruction::MLOAD; + _context << Instruction::DUP3; + utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); + _context << Instruction::POP; + } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + _context + << Instruction::DUP3 << Instruction::SLOAD + << Instruction::DUP3 << Instruction::SSTORE; + } + else + { + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + _context << dupInstruction(3 + byteOffsetSize); + if (_sourceType.location() == DataLocation::Storage) + { + if (haveByteOffsetSource) + _context << Instruction::DUP2; + else + _context << u256(0); + StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } + else if (sourceBaseType->isValueType()) + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + else + solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... + solAssert( + 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + "Stack too deep, try removing local variables." + ); + // fetch target storage reference + _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); + if (haveByteOffsetTarget) + _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); + else + _context << u256(0); + StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); + } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // increment source + if (haveByteOffsetSource) + utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); else - m_context << u256(0); - StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + { + _context << swapInstruction(2 + byteOffsetSize); + if (sourceIsStorage) + _context << sourceBaseType->storageSize(); + else if (_sourceType.location() == DataLocation::Memory) + _context << sourceBaseType->memoryHeadSize(); + else + _context << sourceBaseType->calldataEncodedSize(true); + _context + << Instruction::ADD + << swapInstruction(2 + byteOffsetSize); + } + // increment target + if (haveByteOffsetTarget) + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + _context + << swapInstruction(1 + byteOffsetSize) + << targetBaseType->storageSize() + << Instruction::ADD + << swapInstruction(1 + byteOffsetSize); + _context.appendJumpTo(copyLoopStart); + _context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); + _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); + StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + _context.appendJumpTo(copyLoopEnd); + + _context << copyCleanupLoopEnd; + _context << Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + _context << Instruction::POP; + _context << copyLoopEndWithoutByteOffset; + + // zero-out leftovers in target + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end + _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + utils.clearStorageLoop(targetBaseType); + _context << Instruction::POP; } - else if (sourceBaseType->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - else - solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... - solAssert( - 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, - "Stack too deep, try removing local variables." - ); - // fetch target storage reference - m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); - if (haveByteOffsetTarget) - m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); - else - m_context << u256(0); - StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - } - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // increment source - if (haveByteOffsetSource) - incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); - else - { - m_context << swapInstruction(2 + byteOffsetSize); - if (sourceIsStorage) - m_context << sourceBaseType->storageSize(); - else if (_sourceType.location() == DataLocation::Memory) - m_context << sourceBaseType->memoryHeadSize(); - else - m_context << sourceBaseType->calldataEncodedSize(true); - m_context - << Instruction::ADD - << swapInstruction(2 + byteOffsetSize); - } - // increment target - if (haveByteOffsetTarget) - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - else - m_context - << swapInstruction(1 + byteOffsetSize) - << targetBaseType->storageSize() - << Instruction::ADD - << swapInstruction(1 + byteOffsetSize); - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - if (haveByteOffsetTarget) - { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); - m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - m_context.appendJumpTo(copyLoopEnd); - - m_context << copyCleanupLoopEnd; - m_context << Instruction::POP; // might pop the source, but then target is popped next - } - if (haveByteOffsetSource) - m_context << Instruction::POP; - m_context << copyLoopEndWithoutByteOffset; - - // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end - m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(*targetBaseType); - m_context << Instruction::POP; + ); } void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const @@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord } } -void ArrayUtils::clearArray(ArrayType const& _type) const +void ArrayUtils::clearArray(ArrayType const& _typeIn) const { - unsigned stackHeightStart = m_context.stackHeight(); - solAssert(_type.location() == DataLocation::Storage, ""); - if (_type.baseType()->storageBytes() < 32) - { - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); - } - if (_type.baseType()->isValueType()) - solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); - - m_context << Instruction::POP; // remove byte offset - if (_type.isDynamicallySized()) - clearDynamicArray(_type); - else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) - m_context << Instruction::POP; - else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) - { - // unroll loop for small arrays @todo choose a good value - // Note that we loop over storage slots here, not elements. - for (unsigned i = 1; i < _type.storageSize(); ++i) - m_context - << u256(0) << Instruction::DUP2 << Instruction::SSTORE - << u256(1) << Instruction::ADD; - m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; - } - else if (!_type.baseType()->isValueType() && _type.length() <= 4) - { - // unroll loop for small arrays @todo choose a good value - solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); - for (unsigned i = 1; i < _type.length(); ++i) + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$clearArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) { - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false); - m_context - << Instruction::POP - << u256(_type.baseType()->storageSize()) << Instruction::ADD; + ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); + unsigned stackHeightStart = _context.stackHeight(); + solAssert(_type.location() == DataLocation::Storage, ""); + if (_type.baseType()->storageBytes() < 32) + { + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); + } + if (_type.baseType()->isValueType()) + solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); + + _context << Instruction::POP; // remove byte offset + if (_type.isDynamicallySized()) + ArrayUtils(_context).clearDynamicArray(_type); + else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) + _context << Instruction::POP; + else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) + { + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.storageSize(); ++i) + _context + << u256(0) << Instruction::DUP2 << Instruction::SSTORE + << u256(1) << Instruction::ADD; + _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; + } + else if (!_type.baseType()->isValueType() && _type.length() <= 4) + { + // unroll loop for small arrays @todo choose a good value + solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); + for (unsigned i = 1; i < _type.length(); ++i) + { + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); + _context + << Instruction::POP + << u256(_type.baseType()->storageSize()) << Instruction::ADD; + } + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + else + { + _context << Instruction::DUP1 << _type.length(); + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::ADD << Instruction::SWAP1; + if (_type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + else + ArrayUtils(_context).clearStorageLoop(_type.baseType()); + _context << Instruction::POP; + } + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); } - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); - } - else - { - m_context << Instruction::DUP1 << _type.length(); - convertLengthToSize(_type); - m_context << Instruction::ADD << Instruction::SWAP1; - if (_type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); - m_context << Instruction::POP; - } - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + ); } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const @@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const << Instruction::SWAP1; // stack: data_pos_end data_pos if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); + clearStorageLoop(make_shared<IntegerType>(256)); else - clearStorageLoop(*_type.baseType()); + clearStorageLoop(_type.baseType()); // cleanup m_context << endTag; m_context << Instruction::POP; } -void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const +void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const { - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - unsigned stackHeightStart = m_context.stackHeight(); - eth::AssemblyItem resizeEnd = m_context.newTag(); - - // stack: ref new_length - // fetch old length - retrieveLength(_type, 1); - // stack: ref new_length old_length - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2"); - - // Special case for short byte arrays, they are stored together with their length - if (_type.isByteArray()) - { - eth::AssemblyItem regularPath = m_context.newTag(); - // We start by a large case-distinction about the old and new length of the byte array. - - m_context << Instruction::DUP3 << Instruction::SLOAD; - // stack: ref new_length current_length ref_value - - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::DUP2 << u256(31) << Instruction::LT; - eth::AssemblyItem currentIsLong = m_context.appendConditionalJump(); - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem newIsLong = m_context.appendConditionalJump(); - - // Here: short -> short - - // Compute 1 << (256 - 8 * new_size) - eth::AssemblyItem shortToShort = m_context.newTag(); - m_context << shortToShort; - m_context << Instruction::DUP3 << u256(8) << Instruction::MUL; - m_context << u256(0x100) << Instruction::SUB; - m_context << u256(2) << Instruction::EXP; - // Divide and multiply by that value, clearing bits. - m_context << Instruction::DUP1 << Instruction::SWAP2; - m_context << Instruction::DIV << Instruction::MUL; - // Insert 2*length. - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << Instruction::OR; - // Store. - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); - - m_context.adjustStackOffset(1); // we have to do that because of the jumps - // Here: short -> long - - m_context << newIsLong; - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - // Zero out lower-order byte. - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // Store at data location. - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::SSTORE; - // stack: ref new_length current_length - // Store new length: Compule 2*length + 1 and store it. - m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - // stack: ref new_length current_length 2*new_length+1 - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); - - m_context.adjustStackOffset(1); // we have to do that because of the jumps - - m_context << currentIsLong; - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - m_context.appendConditionalJumpTo(regularPath); - - // Here: long -> short - // Read the first word of the data and store it on the stack. Clear the data location and - // then jump to the short -> short case. - - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::POP << Instruction::DUP3; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location - m_context << Instruction::DUP3; - convertLengthToSize(_type); - m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location_end data_location - clearStorageLoop(IntegerType(256)); - m_context << Instruction::POP; - // stack: ref new_length current_length first_word - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context.appendJumpTo(shortToShort); - - m_context << regularPath; - // stack: ref new_length current_length ref_value - m_context << Instruction::POP; - } + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$resizeDynamicArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) + { + ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + + unsigned stackHeightStart = _context.stackHeight(); + eth::AssemblyItem resizeEnd = _context.newTag(); + + // stack: ref new_length + // fetch old length + ArrayUtils(_context).retrieveLength(_type, 1); + // stack: ref new_length old_length + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); + + // Special case for short byte arrays, they are stored together with their length + if (_type.isByteArray()) + { + eth::AssemblyItem regularPath = _context.newTag(); + // We start by a large case-distinction about the old and new length of the byte array. + + _context << Instruction::DUP3 << Instruction::SLOAD; + // stack: ref new_length current_length ref_value + + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::DUP2 << u256(31) << Instruction::LT; + eth::AssemblyItem currentIsLong = _context.appendConditionalJump(); + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem newIsLong = _context.appendConditionalJump(); + + // Here: short -> short + + // Compute 1 << (256 - 8 * new_size) + eth::AssemblyItem shortToShort = _context.newTag(); + _context << shortToShort; + _context << Instruction::DUP3 << u256(8) << Instruction::MUL; + _context << u256(0x100) << Instruction::SUB; + _context << u256(2) << Instruction::EXP; + // Divide and multiply by that value, clearing bits. + _context << Instruction::DUP1 << Instruction::SWAP2; + _context << Instruction::DIV << Instruction::MUL; + // Insert 2*length. + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << Instruction::OR; + // Store. + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); + + _context.adjustStackOffset(1); // we have to do that because of the jumps + // Here: short -> long + + _context << newIsLong; + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + // Zero out lower-order byte. + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // Store at data location. + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::SSTORE; + // stack: ref new_length current_length + // Store new length: Compule 2*length + 1 and store it. + _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + // stack: ref new_length current_length 2*new_length+1 + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); + + _context.adjustStackOffset(1); // we have to do that because of the jumps + + _context << currentIsLong; + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + _context.appendConditionalJumpTo(regularPath); + + // Here: long -> short + // Read the first word of the data and store it on the stack. Clear the data location and + // then jump to the short -> short case. + + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::POP << Instruction::DUP3; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location + _context << Instruction::DUP3; + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location_end data_location + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + _context << Instruction::POP; + // stack: ref new_length current_length first_word + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context.appendJumpTo(shortToShort); + + _context << regularPath; + // stack: ref new_length current_length ref_value + _context << Instruction::POP; + } - // Change of length for a regular array (i.e. length at location, data at sha3(location)). - // stack: ref new_length old_length - // store new length - m_context << Instruction::DUP2; - if (_type.isByteArray()) - // For a "long" byte array, store length as 2*length+1 - m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - m_context<< Instruction::DUP4 << Instruction::SSTORE; - // skip if size is not reduced - m_context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::ISZERO << Instruction::GT; - m_context.appendConditionalJumpTo(resizeEnd); - - // size reduced, clear the end of the array - // stack: ref new_length old_length - convertLengthToSize(_type); - m_context << Instruction::DUP2; - convertLengthToSize(_type); - // stack: ref new_length old_size new_size - // compute data positions - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - // stack: ref new_length old_size new_size data_pos - m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; - // stack: ref new_length data_pos new_size delete_end - m_context << Instruction::SWAP2 << Instruction::ADD; - // stack: ref new_length delete_end delete_start - if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); + // Change of length for a regular array (i.e. length at location, data at sha3(location)). + // stack: ref new_length old_length + // store new length + _context << Instruction::DUP2; + if (_type.isByteArray()) + // For a "long" byte array, store length as 2*length+1 + _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; + _context<< Instruction::DUP4 << Instruction::SSTORE; + // skip if size is not reduced + _context << Instruction::DUP2 << Instruction::DUP2 + << Instruction::ISZERO << Instruction::GT; + _context.appendConditionalJumpTo(resizeEnd); + + // size reduced, clear the end of the array + // stack: ref new_length old_length + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2; + ArrayUtils(_context).convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + _context << Instruction::SWAP2 << Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + else + ArrayUtils(_context).clearStorageLoop(_type.baseType()); - m_context << resizeEnd; - // cleanup - m_context << Instruction::POP << Instruction::POP << Instruction::POP; - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + _context << resizeEnd; + // cleanup + _context << Instruction::POP << Instruction::POP << Instruction::POP; + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); + } + ); } -void ArrayUtils::clearStorageLoop(Type const& _type) const +void ArrayUtils::clearStorageLoop(TypePointer const& _type) const { - unsigned stackHeightStart = m_context.stackHeight(); - if (_type.category() == Type::Category::Mapping) - { - m_context << Instruction::POP; - return; - } - // stack: end_pos pos - - // jump to and return from the loop to allow for duplicate code removal - eth::AssemblyItem returnTag = m_context.pushNewTag(); - m_context << Instruction::SWAP2 << Instruction::SWAP1; - - // stack: <return tag> end_pos pos - eth::AssemblyItem loopStart = m_context.appendJumpToNew(); - m_context << loopStart; - // check for loop condition - m_context << Instruction::DUP1 << Instruction::DUP3 - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem zeroLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(zeroLoopEnd); - // delete - m_context << u256(0); - StorageItem(m_context, _type).setToZero(SourceLocation(), false); - m_context << Instruction::POP; - // increment - m_context << _type.storageSize() << Instruction::ADD; - m_context.appendJumpTo(loopStart); - // cleanup - m_context << zeroLoopEnd; - m_context << Instruction::POP << Instruction::SWAP1; - // "return" - m_context << Instruction::JUMP; - - m_context << returnTag; - solAssert(m_context.stackHeight() == stackHeightStart - 1, ""); + m_context.callLowLevelFunction( + "$clearStorageLoop_" + _type->identifier(), + 2, + 1, + [_type](CompilerContext& _context) + { + unsigned stackHeightStart = _context.stackHeight(); + if (_type->category() == Type::Category::Mapping) + { + _context << Instruction::POP; + return; + } + // stack: end_pos pos + + // jump to and return from the loop to allow for duplicate code removal + eth::AssemblyItem returnTag = _context.pushNewTag(); + _context << Instruction::SWAP2 << Instruction::SWAP1; + + // stack: <return tag> end_pos pos + eth::AssemblyItem loopStart = _context.appendJumpToNew(); + _context << loopStart; + // check for loop condition + _context << Instruction::DUP1 << Instruction::DUP3 + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = _context.newTag(); + _context.appendConditionalJumpTo(zeroLoopEnd); + // delete + _context << u256(0); + StorageItem(_context, *_type).setToZero(SourceLocation(), false); + _context << Instruction::POP; + // increment + _context << _type->storageSize() << Instruction::ADD; + _context.appendJumpTo(loopStart); + // cleanup + _context << zeroLoopEnd; + _context << Instruction::POP << Instruction::SWAP1; + // "return" + _context << Instruction::JUMP; + + _context << returnTag; + solAssert(_context.stackHeight() == stackHeightStart - 1, ""); + } + ); } void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index d0ba2892..806fbea7 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -22,6 +22,8 @@ #pragma once +#include <memory> + namespace dev { namespace solidity @@ -30,6 +32,7 @@ namespace solidity class CompilerContext; class Type; class ArrayType; +using TypePointer = std::shared_ptr<Type const>; /** * Class that provides code generation for handling arrays. @@ -67,7 +70,7 @@ public: /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Stack pre: end_ref start_ref /// Stack post: end_ref - void clearStorageLoop(Type const& _type) const; + void clearStorageLoop(TypePointer const& _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index c14ab845..e26f96e8 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -21,15 +21,18 @@ */ #include <libsolidity/codegen/CompilerContext.h> -#include <utility> -#include <numeric> -#include <boost/algorithm/string/replace.hpp> +#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/Version.h> #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmStack.h> +#include <boost/algorithm/string/replace.hpp> + +#include <utility> +#include <numeric> + using namespace std; namespace dev @@ -57,6 +60,51 @@ void CompilerContext::startFunction(Declaration const& _function) *this << functionEntryLabel(_function); } +void CompilerContext::callLowLevelFunction( + string const& _name, + unsigned _inArgs, + unsigned _outArgs, + function<void(CompilerContext&)> const& _generator +) +{ + eth::AssemblyItem retTag = pushNewTag(); + CompilerUtils(*this).moveIntoStack(_inArgs); + + auto it = m_lowLevelFunctions.find(_name); + if (it == m_lowLevelFunctions.end()) + { + eth::AssemblyItem tag = newTag().pushTag(); + m_lowLevelFunctions.insert(make_pair(_name, tag)); + m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator)); + *this << tag; + } + else + *this << it->second; + appendJump(eth::AssemblyItem::JumpType::IntoFunction); + adjustStackOffset(int(_outArgs) - 1 - _inArgs); + *this << retTag.tag(); +} + +void CompilerContext::appendMissingLowLevelFunctions() +{ + while (!m_lowLevelFunctionGenerationQueue.empty()) + { + string name; + unsigned inArgs; + unsigned outArgs; + function<void(CompilerContext&)> generator; + tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front(); + m_lowLevelFunctionGenerationQueue.pop(); + + setStackOffset(inArgs + 1); + *this << m_lowLevelFunctions.at(name).tag(); + generator(*this); + CompilerUtils(*this).moveToStackTop(outArgs); + appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + "."); + } +} + void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 80671528..f024b010 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,17 +22,21 @@ #pragma once -#include <ostream> -#include <stack> -#include <queue> -#include <utility> -#include <libevmasm/Instruction.h> -#include <libevmasm/Assembly.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> + +#include <libevmasm/Instruction.h> +#include <libevmasm/Assembly.h> + #include <libdevcore/Common.h> +#include <ostream> +#include <stack> +#include <queue> +#include <utility> +#include <functional> + namespace dev { namespace solidity { @@ -90,6 +94,19 @@ public: /// as "having code". void startFunction(Declaration const& _function); + /// Appends a call to the named low-level function and inserts the generator into the + /// list of low-level-functions to be generated, unless it already exists. + /// Note that the generator should not assume that objects are still alive when it is called, + /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). + void callLowLevelFunction( + std::string const& _name, + unsigned _inArgs, + unsigned _outArgs, + std::function<void(CompilerContext&)> const& _generator + ); + /// Generates the code for missing low-level functions, i.e. calls the generators passed above. + void appendMissingLowLevelFunctions(); + ModifierDefinition const& functionModifier(std::string const& _name) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; @@ -248,6 +265,10 @@ private: CompilerContext *m_runtimeContext; /// The index of the runtime subroutine. size_t m_runtimeSub = -1; + /// An index of low-level function labels by name. + std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions; + /// The queue of low-level functions to generate. + std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7d382aba..caf3b1ac 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -820,37 +820,46 @@ void CompilerUtils::pushZeroValue(Type const& _type) } solAssert(referenceType->location() == DataLocation::Memory, ""); - m_context << u256(max(32u, _type.calldataEncodedSize())); - allocateMemory(); - m_context << Instruction::DUP1; + TypePointer type = _type.shared_from_this(); + m_context.callLowLevelFunction( + "$pushZeroValue_" + referenceType->identifier(), + 0, + 1, + [type](CompilerContext& _context) { + CompilerUtils utils(_context); + _context << u256(max(32u, type->calldataEncodedSize())); + utils.allocateMemory(); + _context << Instruction::DUP1; + + if (auto structType = dynamic_cast<StructType const*>(type.get())) + for (auto const& member: structType->members(nullptr)) + { + utils.pushZeroValue(*member.type); + utils.storeInMemoryDynamic(*member.type); + } + else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) + { + if (arrayType->isDynamicallySized()) + { + // zero length + _context << u256(0); + utils.storeInMemoryDynamic(IntegerType(256)); + } + else if (arrayType->length() > 0) + { + _context << arrayType->length() << Instruction::SWAP1; + // stack: items_to_do memory_pos + utils.zeroInitialiseMemoryArray(*arrayType); + // stack: updated_memory_pos + } + } + else + solAssert(false, "Requested initialisation for unknown type: " + type->toString()); - if (auto structType = dynamic_cast<StructType const*>(&_type)) - for (auto const& member: structType->members(nullptr)) - { - pushZeroValue(*member.type); - storeInMemoryDynamic(*member.type); + // remove the updated memory pointer + _context << Instruction::POP; } - else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) - { - if (arrayType->isDynamicallySized()) - { - // zero length - m_context << u256(0); - storeInMemoryDynamic(IntegerType(256)); - } - else if (arrayType->length() > 0) - { - m_context << arrayType->length() << Instruction::SWAP1; - // stack: items_to_do memory_pos - zeroInitialiseMemoryArray(*arrayType); - // stack: updated_memory_pos - } - } - else - solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); - - // remove the updated memory pointer - m_context << Instruction::POP; + ); } void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index bdff0da4..9dc1fb37 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -827,6 +827,7 @@ void ContractCompiler::appendMissingFunctions() function->accept(*this); solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); } + m_context.appendMissingLowLevelFunctions(); } void ContractCompiler::appendModifierOrFunctionCode() diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index da7adbbf..e8e5ced1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8992,6 +8992,28 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) compileAndRun(sourceCode, 0, "C2"); } +BOOST_AUTO_TEST_CASE(recursive_structs) +{ + char const* sourceCode = R"( + contract C { + struct S { + S[] x; + } + S sstorage; + function f() returns (uint) { + S memory s; + s.x = new S[](10); + delete s; + sstorage.x.length++; + delete sstorage; + return 1; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_SUITE_END() } |