Repository: netty/netty
Issue: CompositeByteBuf.decompose() doesn't work as expected.
Labels: defect

### Expected behavior
It is expected that the arguments to CompositeByteBuf decompose() are the same as for slice()

### Actual behavior
We get random garbage at the start.

### Steps to reproduce

Call decompose with valid arguments.

### Minimal yet complete reproducer code (or URL to code)

    ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;    
    ByteBuf buf = alloc.directBuffer(16384).setIndex(0, 16384);

    StringBuilder sb = new StringBuilder(16384);
    while (sb.length() < 16000) {
      sb.append(UUID.randomUUID());
    }
    buf.setBytes(0, sb.toString().getBytes());

    CompositeByteBuf composite = alloc.compositeBuffer();
    composite.addComponents(true,
        buf.retainedSlice(100, 200),
        buf.retainedSlice(300, 400),
        buf.retainedSlice(10000, 1000));

    ByteBuf test1 = composite.slice(150, 700);

    ByteBuf test2 = Unpooled.wrappedBuffer(composite.decompose(150, 700).toArray(new ByteBuf[0]));

    Assert.assertTrue(ByteBufUtil.equals(test1, test2)); // this equality test fails.

### Netty version
4.1.22

### JVM version (e.g. `java -version`)
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

### OS version (e.g. `uname -a`)
Linux acurtis-ld1 3.10.0-514.36.5.el7.x86_64 #1 SMP Thu Dec 28 21:42:18 EST 2017 x86_64 x86_64 x86_64 GNU/Linux


Issue comments:
@atcurtis thanks for reporting... Should be fixed by https://github.com/netty/netty/pull/8403. PTAL

Pull request review comments:

Patch that resolved the issue:
diff --git a/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java b/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java
index 85160c79a6e..f987e48681c 100644
--- a/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java
+++ b/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java
@@ -526,39 +526,31 @@ public List<ByteBuf> decompose(int offset, int length) {
         }
 
         int componentId = toComponentIndex(offset);
-        List<ByteBuf> slice = new ArrayList<ByteBuf>(components.size());
-
+        int bytesToSlice = length;
         // The first component
         Component firstC = components.get(componentId);
-        ByteBuf first = firstC.buf.duplicate();
-        first.readerIndex(offset - firstC.offset);
+        int firstBufOffset = offset - firstC.offset;
 
-        ByteBuf buf = first;
-        int bytesToSlice = length;
-        do {
-            int readableBytes = buf.readableBytes();
-            if (bytesToSlice <= readableBytes) {
-                // Last component
-                buf.writerIndex(buf.readerIndex() + bytesToSlice);
-                slice.add(buf);
-                break;
-            } else {
-                // Not the last component
-                slice.add(buf);
-                bytesToSlice -= readableBytes;
-                componentId ++;
+        ByteBuf slice = firstC.buf.slice(firstBufOffset + firstC.buf.readerIndex(),
+                                         Math.min(firstC.length - firstBufOffset, bytesToSlice));
+        bytesToSlice -= slice.readableBytes();
 
-                // Fetch the next component.
-                buf = components.get(componentId).buf.duplicate();
-            }
-        } while (bytesToSlice > 0);
-
-        // Slice all components because only readable bytes are interesting.
-        for (int i = 0; i < slice.size(); i ++) {
-            slice.set(i, slice.get(i).slice());
+        if (bytesToSlice == 0) {
+            return Collections.singletonList(slice);
         }
 
-        return slice;
+        List<ByteBuf> sliceList = new ArrayList<ByteBuf>(components.size() - componentId);
+        sliceList.add(slice);
+
+        // Add all the slices until there is nothing more left and then return the List.
+        do {
+            Component component = components.get(++componentId);
+            slice = component.buf.slice(component.buf.readerIndex(), Math.min(component.length, bytesToSlice));
+            bytesToSlice -= slice.readableBytes();
+            sliceList.add(slice);
+        } while (bytesToSlice > 0);
+
+        return sliceList;
     }
 
     @Override


Test patch:
diff --git a/buffer/src/test/java/io/netty/buffer/AbstractCompositeByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractCompositeByteBufTest.java
index 22ca0546aa9..03e55913a06 100644
--- a/buffer/src/test/java/io/netty/buffer/AbstractCompositeByteBufTest.java
+++ b/buffer/src/test/java/io/netty/buffer/AbstractCompositeByteBufTest.java
@@ -16,6 +16,7 @@
 package io.netty.buffer;
 
 import io.netty.util.ReferenceCountUtil;
+import io.netty.util.internal.PlatformDependent;
 import org.junit.Assume;
 import org.junit.Test;
 
@@ -1136,4 +1137,45 @@ private void testAllocatorIsSameWhenCopy(boolean withIndexAndLength) {
         buffer.release();
         copy.release();
     }
+
+    @Test
+    public void testDecomposeMultiple() {
+        testDecompose(150, 500, 3);
+    }
+
+    @Test
+    public void testDecomposeOne() {
+        testDecompose(310, 50, 1);
+    }
+
+    @Test
+    public void testDecomposeNone() {
+        testDecompose(310, 0, 0);
+    }
+
+    private static void testDecompose(int offset, int length, int expectedListSize) {
+        byte[] bytes = new byte[1024];
+        PlatformDependent.threadLocalRandom().nextBytes(bytes);
+        ByteBuf buf = wrappedBuffer(bytes);
+
+        CompositeByteBuf composite = compositeBuffer();
+        composite.addComponents(true,
+                                buf.retainedSlice(100, 200),
+                                buf.retainedSlice(300, 400),
+                                buf.retainedSlice(700, 100));
+
+        ByteBuf slice = composite.slice(offset, length);
+        List<ByteBuf> bufferList = composite.decompose(offset, length);
+        assertEquals(expectedListSize, bufferList.size());
+        ByteBuf wrapped = wrappedBuffer(bufferList.toArray(new ByteBuf[0]));
+
+        assertEquals(slice, wrapped);
+        composite.release();
+        buf.release();
+
+        for (ByteBuf buffer: bufferList) {
+            assertEquals(0, buffer.refCnt());
+        }
+    }
+
 }