diff --git a/src/main/java/io/netty/buffer/api/Allocator.java b/src/main/java/io/netty/buffer/api/Allocator.java index 1dd4db9..c24e096 100644 --- a/src/main/java/io/netty/buffer/api/Allocator.java +++ b/src/main/java/io/netty/buffer/api/Allocator.java @@ -70,7 +70,7 @@ public interface Allocator extends AutoCloseable { *
{@code
      *     try (Buf a = allocator.allocate(size);
      *          Buf b = allocator.allocate(size)) {
-     *         return Buf.compose(a, b); // Reference counts for 'a' and 'b' incremented here.
+     *         return allocator.compose(a, b); // Reference counts for 'a' and 'b' incremented here.
      *     } // Reference count for 'a' and 'b' decremented here; composite buffer now holds the last references.
      * }
*

diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index 36d4305..6906be4 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -154,6 +154,7 @@ public interface Buf extends Rc, BufAccessors { * @return This Buf. * @throws IndexOutOfBoundsException if the specified {@code offset} is less than the current * {@link #readerOffset()} or greater than {@link #capacity()}. + * @throws IllegalStateException if this buffer is {@linkplain #readOnly() read-only}. */ Buf writerOffset(int offset); @@ -178,6 +179,7 @@ public interface Buf extends Rc, BufAccessors { * * @param value The byte value to write at every position in the buffer. * @return This Buf. + * @throws IllegalStateException if this buffer is {@linkplain #readOnly() read-only}. */ Buf fill(byte value); @@ -187,6 +189,20 @@ public interface Buf extends Rc, BufAccessors { */ long getNativeAddress(); + /** + * Set the read-only state of this buffer. + * + * @return this buffer. + */ + Buf readOnly(boolean readOnly); + + /** + * Query if this buffer is read-only or not. + * + * @return {@code true} if this buffer is read-only, {@code false} otherwise. + */ + boolean readOnly(); + /** * Returns a slice of this buffer's readable bytes. * Modifying the content of the returned buffer or this buffer affects each other's content, @@ -371,8 +387,8 @@ public interface Buf extends Rc, BufAccessors { * {@code false}. * * @param size The requested number of bytes of space that should be available for writing. - * @throws IllegalStateException if this buffer is not in an owned state. - * That is, if {@link #isOwned()} is {@code false}. + * @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state, + * or is {@linkplain #readOnly() read-only}. */ default void ensureWritable(int size) { ensureWritable(size, true); @@ -410,8 +426,8 @@ public interface Buf extends Rc, BufAccessors { * @param allowCompaction {@code true} if the method is allowed to modify the * {@linkplain #readerOffset() reader offset} and * {@linkplain #writerOffset() writer offset}, otherwise {@code false}. - * @throws IllegalStateException if this buffer is not in an owned state. - * That is, if {@link #isOwned()} is {@code false}. + * @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state, + * * or is {@linkplain #readOnly() read-only}. */ void ensureWritable(int size, boolean allowCompaction); @@ -462,7 +478,8 @@ public interface Buf extends Rc, BufAccessors { /** * Discards the read bytes, and moves the buffer contents to the beginning of the buffer. * - * The buffer must be {@linkplain #isOwned() owned}, or an exception will be thrown. + * @throws IllegalStateException if this buffer is not in an {@linkplain #isOwned() owned} state, + * or is {@linkplain #readOnly() read-only}. */ void compact(); } diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index 7dd818d..e7c6208 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -20,9 +20,6 @@ import java.nio.ByteOrder; import java.util.Arrays; import java.util.Objects; -import static jdk.incubator.foreign.MemoryAccess.setByteAtOffset; -import static jdk.incubator.foreign.MemoryAccess.setLongAtOffset; - final class CompositeBuf extends RcSupport implements Buf { /** * The max array size is JVM implementation dependant, but most seem to settle on {@code Integer.MAX_VALUE - 8}. @@ -48,6 +45,7 @@ final class CompositeBuf extends RcSupport implements Buf { private int subOffset; // The next offset *within* a consituent buffer to read from or write to. private ByteOrder order; private boolean closed; + private boolean readOnly; CompositeBuf(Allocator allocator, Buf[] bufs) { this(allocator, true, bufs.clone(), COMPOSITE_DROP); // Clone prevents external modification of array. @@ -68,6 +66,14 @@ final class CompositeBuf extends RcSupport implements Buf { } } order = bufs[0].order(); + + boolean targetReadOnly = bufs[0].readOnly(); + for (Buf buf : bufs) { + if (buf.readOnly() != targetReadOnly) { + throw new IllegalArgumentException("Constituent buffers have inconsistent read-only state."); + } + } + readOnly = targetReadOnly; } else { order = ByteOrder.nativeOrder(); } @@ -201,6 +207,20 @@ final class CompositeBuf extends RcSupport implements Buf { return 0; } + @Override + public Buf readOnly(boolean readOnly) { + for (Buf buf : bufs) { + buf.readOnly(readOnly); + } + this.readOnly = readOnly; + return this; + } + + @Override + public boolean readOnly() { + return readOnly; + } + @Override public Buf slice(int offset, int length) { checkWriteBounds(offset, length); @@ -236,7 +256,7 @@ final class CompositeBuf extends RcSupport implements Buf { slices = new Buf[] { choice.slice(subOffset, 0) }; } - return new CompositeBuf(allocator, false, slices, drop).writerOffset(length); + return new CompositeBuf(allocator, false, slices, drop); } catch (Throwable throwable) { // We called acquire prior to the try-clause. We need to undo that if we're not creating a composite buffer: close(); @@ -267,10 +287,10 @@ final class CompositeBuf extends RcSupport implements Buf { throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.'); } if (srcPos < 0) { - throw indexOutOfBounds(srcPos); + throw indexOutOfBounds(srcPos, false); } if (srcPos + length > capacity) { - throw indexOutOfBounds(srcPos + length); + throw indexOutOfBounds(srcPos + length, false); } while (length > 0) { var buf = (Buf) chooseBuffer(srcPos, 0); @@ -293,10 +313,10 @@ final class CompositeBuf extends RcSupport implements Buf { throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.'); } if (srcPos < 0) { - throw indexOutOfBounds(srcPos); + throw indexOutOfBounds(srcPos, false); } if (srcPos + length > capacity) { - throw indexOutOfBounds(srcPos + length); + throw indexOutOfBounds(srcPos + length, false); } // Iterate in reverse to account for src and dest buffer overlap. @@ -530,6 +550,9 @@ final class CompositeBuf extends RcSupport implements Buf { if (size < 0) { throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.'); } + if (readOnly) { + throw bufferIsReadOnly(); + } if (writableBytes() >= size) { // We already have enough space. return; @@ -586,15 +609,23 @@ final class CompositeBuf extends RcSupport implements Buf { "This buffer uses " + order() + " byte order, and cannot be extended with " + "a buffer that uses " + extension.order() + " byte order."); } - if (bufs.length == 0) { - order = extension.order(); + if (bufs.length > 0 && extension.readOnly() != readOnly()) { + throw new IllegalArgumentException( + "This buffer is " + (readOnly? "read-only" : "writable") + ", " + + "and cannot be extended with a buffer that is " + + (extension.readOnly()? "read-only." : "writable.")); } + long newSize = capacity() + (long) extension.capacity(); Allocator.checkSize(newSize); Buf[] restoreTemp = bufs; // We need this to restore our buffer array, in case offset computations fail. try { unsafeExtendWith(extension.acquire()); + if (restoreTemp.length == 0) { + order = extension.order(); + readOnly = extension.readOnly(); + } } catch (Exception e) { bufs = restoreTemp; throw e; @@ -642,6 +673,9 @@ final class CompositeBuf extends RcSupport implements Buf { if (!isOwned()) { throw new IllegalStateException("Buffer must be owned in order to compact."); } + if (readOnly()) { + throw new IllegalStateException("Buffer must be writable in order to compact, but was read-only."); + } int distance = roff; if (distance == 0) { return; @@ -962,6 +996,7 @@ final class CompositeBuf extends RcSupport implements Buf { received[i] = sends[i].receive(); } var composite = new CompositeBuf(allocator, true, received, drop); + composite.readOnly = readOnly; drop.attach(composite); return composite; } @@ -1034,7 +1069,7 @@ final class CompositeBuf extends RcSupport implements Buf { private void checkReadBounds(int index, int size) { if (index < 0 || woff < index + size) { - throw indexOutOfBounds(index); + throw indexOutOfBounds(index, false); } } @@ -1051,19 +1086,30 @@ final class CompositeBuf extends RcSupport implements Buf { private void checkWriteBounds(int index, int size) { if (index < 0 || capacity < index + size) { - throw indexOutOfBounds(index); + throw indexOutOfBounds(index, true); } } - private RuntimeException indexOutOfBounds(int index) { + private RuntimeException indexOutOfBounds(int index, boolean write) { if (closed) { - return new IllegalStateException("This buffer is closed."); + return bufferIsClosed(); + } + if (write && readOnly) { + return bufferIsReadOnly(); } return new IndexOutOfBoundsException( "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + (capacity - 1) + "]."); } + private static IllegalStateException bufferIsClosed() { + return new IllegalStateException("This buffer is closed."); + } + + private static IllegalStateException bufferIsReadOnly() { + return new IllegalStateException("This buffer is read-only."); + } + private BufAccessors chooseBuffer(int index, int size) { int i = searchOffsets(index); if (i == bufs.length) { diff --git a/src/main/java/io/netty/buffer/api/SizeClassedMemoryPool.java b/src/main/java/io/netty/buffer/api/SizeClassedMemoryPool.java index 98a7d62..8d82b09 100644 --- a/src/main/java/io/netty/buffer/api/SizeClassedMemoryPool.java +++ b/src/main/java/io/netty/buffer/api/SizeClassedMemoryPool.java @@ -43,7 +43,11 @@ class SizeClassedMemoryPool implements Allocator, AllocatorControl, Drop { var sizeClassPool = getSizeClassPool(size); Send send = sizeClassPool.poll(); if (send != null) { - return send.receive().reset().fill((byte) 0).order(ByteOrder.nativeOrder()); + return send.receive() + .reset() + .readOnly(false) + .fill((byte) 0) + .order(ByteOrder.nativeOrder()); } return createBuf(size, getDrop()); } diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 6484c04..bc901e3 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -59,6 +59,7 @@ class MemSegBuf extends RcSupport implements Buf { private final AllocatorControl alloc; private final boolean isSendable; private MemorySegment seg; + private MemorySegment wseg; private ByteOrder order; private int roff; private int woff; @@ -71,6 +72,7 @@ class MemSegBuf extends RcSupport implements Buf { super(drop); this.alloc = alloc; seg = segment; + wseg = segment; this.isSendable = isSendable; order = ByteOrder.nativeOrder(); } @@ -122,6 +124,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf fill(byte value) { + checkWrite(0, capacity()); seg.fill(value); return this; } @@ -135,6 +138,17 @@ class MemSegBuf extends RcSupport implements Buf { } } + @Override + public Buf readOnly(boolean readOnly) { + wseg = readOnly? CLOSED_SEGMENT : seg; + return this; + } + + @Override + public boolean readOnly() { + return wseg == CLOSED_SEGMENT && seg != CLOSED_SEGMENT; + } + @Override public Buf slice(int offset, int length) { if (length < 0) { @@ -147,7 +161,10 @@ class MemSegBuf extends RcSupport implements Buf { b.makeInaccessible(); }; var sendable = false; // Sending implies ownership change, which we can't do for slices. - return new MemSegBuf(slice, drop, alloc, sendable).writerOffset(length).order(order()); + return new MemSegBuf(slice, drop, alloc, sendable) + .writerOffset(length) + .order(order()) + .readOnly(readOnly()); } @Override @@ -165,12 +182,31 @@ class MemSegBuf extends RcSupport implements Buf { } private void copyInto(int srcPos, MemorySegment dest, int destPos, int length) { + if (seg == CLOSED_SEGMENT) { + throw bufferIsClosed(); + } + if (srcPos < 0) { + throw new IllegalArgumentException("The srcPos cannot be negative: " + srcPos + '.'); + } + if (length < 0) { + throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); + } + if (seg.byteSize() < srcPos + length) { + throw new IllegalArgumentException("The srcPos + length is beyond the end of the buffer: " + + "srcPos = " + srcPos + ", length = " + length + '.'); + } dest.asSlice(destPos, length).copyFrom(seg.asSlice(srcPos, length)); } @Override public void copyInto(int srcPos, Buf dest, int destPos, int length) { - // todo optimise: specialise for MemSegBuf. + if (dest instanceof MemSegBuf) { + var memSegBuf = (MemSegBuf) dest; + memSegBuf.checkWrite(destPos, length); + copyInto(srcPos, memSegBuf.seg, destPos, length); + return; + } + // Iterate in reverse to account for src and dest buffer overlap. var itr = openReverseCursor(srcPos + length - 1, length); ByteOrder prevOrder = dest.order(); @@ -202,8 +238,8 @@ class MemSegBuf extends RcSupport implements Buf { throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); } if (seg.byteSize() < fromOffset + length) { - throw new IllegalArgumentException("The fromOffset+length is beyond the end of the buffer: " + - "fromOffset=" + fromOffset + ", length=" + length + '.'); + throw new IllegalArgumentException("The fromOffset + length is beyond the end of the buffer: " + + "fromOffset = " + fromOffset + ", length = " + length + '.'); } return new ByteCursor() { final MemorySegment segment = seg; @@ -269,8 +305,8 @@ class MemSegBuf extends RcSupport implements Buf { throw new IllegalArgumentException("The fromOffset is beyond the end of the buffer: " + fromOffset + '.'); } if (fromOffset - length < -1) { - throw new IllegalArgumentException("The fromOffset-length would underflow the buffer: " + - "fromOffset=" + fromOffset + ", length=" + length + '.'); + throw new IllegalArgumentException("The fromOffset - length would underflow the buffer: " + + "fromOffset = " + fromOffset + ", length = " + length + '.'); } return new ByteCursor() { final MemorySegment segment = seg; @@ -330,6 +366,9 @@ class MemSegBuf extends RcSupport implements Buf { if (size < 0) { throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.'); } + if (seg != wseg) { + throw bufferIsReadOnly(); + } if (writableBytes() >= size) { // We already have enough space. return; @@ -366,6 +405,7 @@ class MemSegBuf extends RcSupport implements Buf { } seg = newSegment; + wseg = newSegment; drop.attach(this); } @@ -390,7 +430,12 @@ class MemSegBuf extends RcSupport implements Buf { bifurcatedBuf.woff = woff; bifurcatedBuf.roff = roff; bifurcatedBuf.order(order); + boolean readOnly = readOnly(); + bifurcatedBuf.readOnly(readOnly); seg = seg.asSlice(woff, seg.byteSize() - woff); + if (!readOnly) { + wseg = seg; + } woff = 0; roff = 0; return bifurcatedBuf; @@ -401,6 +446,9 @@ class MemSegBuf extends RcSupport implements Buf { if (!isOwned()) { throw new IllegalStateException("Buffer must be owned in order to compact."); } + if (readOnly()) { + throw new IllegalStateException("Buffer must be writable in order to compact, but was read-only."); + } int distance = roff; if (distance == 0) { return; @@ -442,7 +490,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeByte(byte value) { try { - setByteAtOffset(seg, woff, value); + setByteAtOffset(wseg, woff, value); woff += Byte.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -453,7 +501,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setByte(int woff, byte value) { try { - setByteAtOffset(seg, woff, value); + setByteAtOffset(wseg, woff, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -463,7 +511,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeUnsignedByte(int value) { try { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); woff += Byte.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -474,7 +522,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setUnsignedByte(int woff, int value) { try { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -498,7 +546,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeChar(char value) { try { - setCharAtOffset(seg, woff, order, value); + setCharAtOffset(wseg, woff, order, value); woff += 2; return this; } catch (IndexOutOfBoundsException e) { @@ -509,7 +557,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setChar(int woff, char value) { try { - setCharAtOffset(seg, woff, order, value); + setCharAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -547,7 +595,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeShort(short value) { try { - setShortAtOffset(seg, woff, order, value); + setShortAtOffset(wseg, woff, order, value); woff += Short.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -558,7 +606,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setShort(int woff, short value) { try { - setShortAtOffset(seg, woff, order, value); + setShortAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -568,7 +616,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeUnsignedShort(int value) { try { - setShortAtOffset(seg, woff, order, (short) (value & 0xFFFF)); + setShortAtOffset(wseg, woff, order, (short) (value & 0xFFFF)); woff += Short.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -579,7 +627,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setUnsignedShort(int woff, int value) { try { - setShortAtOffset(seg, woff, order, (short) (value & 0xFFFF)); + setShortAtOffset(wseg, woff, order, (short) (value & 0xFFFF)); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -642,13 +690,13 @@ class MemSegBuf extends RcSupport implements Buf { public Buf writeMedium(int value) { checkWrite(woff, 3); if (order == ByteOrder.BIG_ENDIAN) { - setByteAtOffset(seg, woff, (byte) (value >> 16)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value >> 16)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); } else { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value >> 16 & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value >> 16 & 0xFF)); } woff += 3; return this; @@ -658,13 +706,13 @@ class MemSegBuf extends RcSupport implements Buf { public Buf setMedium(int woff, int value) { checkWrite(woff, 3); if (order == ByteOrder.BIG_ENDIAN) { - setByteAtOffset(seg, woff, (byte) (value >> 16)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value >> 16)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); } else { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value >> 16 & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value >> 16 & 0xFF)); } return this; } @@ -673,13 +721,13 @@ class MemSegBuf extends RcSupport implements Buf { public Buf writeUnsignedMedium(int value) { checkWrite(woff, 3); if (order == ByteOrder.BIG_ENDIAN) { - setByteAtOffset(seg, woff, (byte) (value >> 16)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value >> 16)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); } else { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value >> 16 & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value >> 16 & 0xFF)); } woff += 3; return this; @@ -689,13 +737,13 @@ class MemSegBuf extends RcSupport implements Buf { public Buf setUnsignedMedium(int woff, int value) { checkWrite(woff, 3); if (order == ByteOrder.BIG_ENDIAN) { - setByteAtOffset(seg, woff, (byte) (value >> 16)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value >> 16)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); } else { - setByteAtOffset(seg, woff, (byte) (value & 0xFF)); - setByteAtOffset(seg, woff + 1, (byte) (value >> 8 & 0xFF)); - setByteAtOffset(seg, woff + 2, (byte) (value >> 16 & 0xFF)); + setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); + setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); + setByteAtOffset(wseg, woff + 2, (byte) (value >> 16 & 0xFF)); } return this; } @@ -731,7 +779,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeInt(int value) { try { - setIntAtOffset(seg, woff, order, value); + setIntAtOffset(wseg, woff, order, value); woff += Integer.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -742,7 +790,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setInt(int woff, int value) { try { - setIntAtOffset(seg, woff, order, value); + setIntAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -752,7 +800,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeUnsignedInt(long value) { try { - setIntAtOffset(seg, woff, order, (int) (value & 0xFFFFFFFFL)); + setIntAtOffset(wseg, woff, order, (int) (value & 0xFFFFFFFFL)); woff += Integer.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -763,7 +811,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setUnsignedInt(int woff, long value) { try { - setIntAtOffset(seg, woff, order, (int) (value & 0xFFFFFFFFL)); + setIntAtOffset(wseg, woff, order, (int) (value & 0xFFFFFFFFL)); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -787,7 +835,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeFloat(float value) { try { - setFloatAtOffset(seg, woff, order, value); + setFloatAtOffset(wseg, woff, order, value); woff += Float.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -798,7 +846,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setFloat(int woff, float value) { try { - setFloatAtOffset(seg, woff, order, value); + setFloatAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -822,7 +870,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeLong(long value) { try { - setLongAtOffset(seg, woff, order, value); + setLongAtOffset(wseg, woff, order, value); woff += Long.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -833,7 +881,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setLong(int woff, long value) { try { - setLongAtOffset(seg, woff, order, value); + setLongAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -857,7 +905,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf writeDouble(double value) { try { - setDoubleAtOffset(seg, woff, order, value); + setDoubleAtOffset(wseg, woff, order, value); woff += Double.BYTES; return this; } catch (IndexOutOfBoundsException e) { @@ -868,7 +916,7 @@ class MemSegBuf extends RcSupport implements Buf { @Override public Buf setDouble(int woff, double value) { try { - setDoubleAtOffset(seg, woff, order, value); + setDoubleAtOffset(wseg, woff, order, value); return this; } catch (IndexOutOfBoundsException e) { throw checkWriteState(e); @@ -881,6 +929,7 @@ class MemSegBuf extends RcSupport implements Buf { var order = this.order; var roff = this.roff; var woff = this.woff; + var readOnly = readOnly(); boolean isConfined = seg.ownerThread() == null; MemorySegment transferSegment = isConfined? seg : seg.share(); makeInaccessible(); @@ -891,6 +940,7 @@ class MemSegBuf extends RcSupport implements Buf { copy.order = order; copy.roff = roff; copy.woff = woff; + copy.readOnly(readOnly); return copy; } }; @@ -898,6 +948,7 @@ class MemSegBuf extends RcSupport implements Buf { void makeInaccessible() { seg = CLOSED_SEGMENT; + wseg = CLOSED_SEGMENT; roff = 0; woff = 0; } @@ -923,7 +974,7 @@ class MemSegBuf extends RcSupport implements Buf { } private void checkWrite(int index, int size) { - if (index < 0 || seg.byteSize() < index + size) { + if (index < 0 || wseg.byteSize() < index + size) { throw accessCheckException(index); } } @@ -932,6 +983,9 @@ class MemSegBuf extends RcSupport implements Buf { if (seg == CLOSED_SEGMENT) { return bufferIsClosed(); } + if (wseg != seg) { + return bufferIsReadOnly(); + } return ioobe; } @@ -939,15 +993,22 @@ class MemSegBuf extends RcSupport implements Buf { if (seg == CLOSED_SEGMENT) { throw bufferIsClosed(); } + if (wseg != seg) { + return bufferIsReadOnly(); + } return new IndexOutOfBoundsException( "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + (seg.byteSize() - 1) + "]."); } - private IllegalStateException bufferIsClosed() { + private static IllegalStateException bufferIsClosed() { return new IllegalStateException("This buffer is closed."); } + private static IllegalStateException bufferIsReadOnly() { + return new IllegalStateException("This buffer is read-only."); + } + Object recoverableMemory() { return new RecoverableMemory(seg, alloc); } diff --git a/src/test/java/io/netty/buffer/api/BufTest.java b/src/test/java/io/netty/buffer/api/BufTest.java index a158ec0..52a2186 100644 --- a/src/test/java/io/netty/buffer/api/BufTest.java +++ b/src/test/java/io/netty/buffer/api/BufTest.java @@ -33,7 +33,6 @@ import java.text.ParseException; import java.util.Arrays; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -90,6 +89,10 @@ public class BufTest { return fixtureCombinations().filter(f -> f.isDirect() && f.isCleaner() && f.isPooled()); } + static Stream pooledAllocators() { + return fixtureCombinations().filter(Fixture::isPooled); + } + private static Stream fixtureCombinations() { Fixture[] fxs = fixtures; if (fxs != null) { @@ -187,7 +190,10 @@ public class BufTest { }, COMPOSITE)); } - return builder.build().flatMap(BufTest::injectBifurcations).flatMap(BufTest::injectSlices); + var stream = builder.build(); + return stream.flatMap(BufTest::injectBifurcations) + .flatMap(BufTest::injectSlices) + .flatMap(BufTest::injectReadOnlyToggling); } private static Stream injectBifurcations(Fixture f) { @@ -251,6 +257,26 @@ public class BufTest { return builder.build(); } + private static Stream injectReadOnlyToggling(Fixture f) { + Builder builder = Stream.builder(); + builder.add(f); + builder.add(new Fixture(f + ".readOnly(true/false)", () -> { + var allocatorBase = f.get(); + return new Allocator() { + @Override + public Buf allocate(int size) { + return allocatorBase.allocate(size).readOnly(true).readOnly(false); + } + + @Override + public void close() { + allocatorBase.close(); + } + }; + }, f.getProperties())); + return builder.build(); + } + @BeforeAll static void startExecutor() throws IOException, ParseException { executor = Executors.newSingleThreadExecutor(); @@ -375,6 +401,31 @@ public class BufTest { } private static void verifyInaccessible(Buf buf) { + verifyReadInaccessible(buf); + + verifyWriteInaccessible(buf); + + try (Allocator allocator = Allocator.heap(); + Buf target = allocator.allocate(24)) { + assertThrows(IllegalStateException.class, () -> buf.copyInto(0, target, 0, 1)); + assertThrows(IllegalStateException.class, () -> buf.copyInto(0, new byte[1], 0, 1)); + assertThrows(IllegalStateException.class, () -> buf.copyInto(0, ByteBuffer.allocate(1), 0, 1)); + if (Allocator.isComposite(buf)) { + assertThrows(IllegalStateException.class, () -> Allocator.extend(buf, target)); + } + } + + assertThrows(IllegalStateException.class, () -> buf.bifurcate()); + assertThrows(IllegalStateException.class, () -> buf.send()); + assertThrows(IllegalStateException.class, () -> buf.acquire()); + assertThrows(IllegalStateException.class, () -> buf.slice()); + assertThrows(IllegalStateException.class, () -> buf.openCursor()); + assertThrows(IllegalStateException.class, () -> buf.openCursor(0, 0)); + assertThrows(IllegalStateException.class, () -> buf.openReverseCursor()); + assertThrows(IllegalStateException.class, () -> buf.openReverseCursor(0, 0)); + } + + private static void verifyReadInaccessible(Buf buf) { assertThrows(IllegalStateException.class, () -> buf.readByte()); assertThrows(IllegalStateException.class, () -> buf.readUnsignedByte()); assertThrows(IllegalStateException.class, () -> buf.readChar()); @@ -387,6 +438,7 @@ public class BufTest { assertThrows(IllegalStateException.class, () -> buf.readFloat()); assertThrows(IllegalStateException.class, () -> buf.readLong()); assertThrows(IllegalStateException.class, () -> buf.readDouble()); + assertThrows(IllegalStateException.class, () -> buf.getByte(0)); assertThrows(IllegalStateException.class, () -> buf.getUnsignedByte(0)); assertThrows(IllegalStateException.class, () -> buf.getChar(0)); @@ -399,6 +451,9 @@ public class BufTest { assertThrows(IllegalStateException.class, () -> buf.getFloat(0)); assertThrows(IllegalStateException.class, () -> buf.getLong(0)); assertThrows(IllegalStateException.class, () -> buf.getDouble(0)); + } + + private static void verifyWriteInaccessible(Buf buf) { assertThrows(IllegalStateException.class, () -> buf.writeByte((byte) 32)); assertThrows(IllegalStateException.class, () -> buf.writeUnsignedByte(32)); assertThrows(IllegalStateException.class, () -> buf.writeChar('3')); @@ -411,6 +466,7 @@ public class BufTest { assertThrows(IllegalStateException.class, () -> buf.writeFloat(3.2f)); assertThrows(IllegalStateException.class, () -> buf.writeLong(32)); assertThrows(IllegalStateException.class, () -> buf.writeDouble(32)); + assertThrows(IllegalStateException.class, () -> buf.setByte(0, (byte) 32)); assertThrows(IllegalStateException.class, () -> buf.setUnsignedByte(0, 32)); assertThrows(IllegalStateException.class, () -> buf.setChar(0, '3')); @@ -425,22 +481,11 @@ public class BufTest { assertThrows(IllegalStateException.class, () -> buf.setDouble(0, 32)); assertThrows(IllegalStateException.class, () -> buf.ensureWritable(1)); - try (Allocator allocator = Allocator.heap(); - Buf target = allocator.allocate(24)) { - assertThrows(IllegalStateException.class, () -> buf.copyInto(0, target, 0, 1)); - if (Allocator.isComposite(buf)) { - assertThrows(IllegalStateException.class, () -> Allocator.extend(buf, target)); - } - } - assertThrows(IllegalStateException.class, () -> buf.bifurcate()); - assertThrows(IllegalStateException.class, () -> buf.send()); - assertThrows(IllegalStateException.class, () -> buf.acquire()); - assertThrows(IllegalStateException.class, () -> buf.slice()); assertThrows(IllegalStateException.class, () -> buf.fill((byte) 0)); - assertThrows(IllegalStateException.class, () -> buf.openCursor()); - assertThrows(IllegalStateException.class, () -> buf.openCursor(0, 0)); - assertThrows(IllegalStateException.class, () -> buf.openReverseCursor()); - assertThrows(IllegalStateException.class, () -> buf.openReverseCursor(0, 0)); + try (Allocator allocator = Allocator.heap(); + Buf source = allocator.allocate(8)) { + assertThrows(IllegalStateException.class, () -> source.copyInto(0, buf, 0, 1)); + } } @ParameterizedTest @@ -1893,6 +1938,18 @@ public class BufTest { } } + @Test + public void emptyCompositeBufferMustAllowExtendingWithReadOnlyBuffer() { + try (Allocator allocator = Allocator.heap()) { + try (Buf composite = allocator.compose()) { + try (Buf b = allocator.allocate(8).readOnly(true)) { + Allocator.extend(composite, b); + assertTrue(composite.readOnly()); + } + } + } + } + @Test public void whenExtendingCompositeBufferWithWriteOffsetAtCapacityExtensionWriteOffsetCanBeNonZero() { try (Allocator allocator = Allocator.heap()) { @@ -2262,6 +2319,255 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("allocators") + public void readOnlyBufferMustPreventWriteAccess(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + var b = buf.readOnly(true); + assertThat(b).isSameAs(buf); + verifyWriteInaccessible(buf); + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void readOnlyBufferMustBecomeWritableAgainAfterTogglingReadOnlyOff(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + assertFalse(buf.readOnly()); + buf.readOnly(true); + assertTrue(buf.readOnly()); + verifyWriteInaccessible(buf); + + buf.readOnly(false); + assertFalse(buf.readOnly()); + + verifyWriteAccessible(buf); + } + } + + private static void verifyWriteAccessible(Buf buf) { + buf.writerOffset(0).writeByte((byte) 32); + assertThat(buf.readerOffset(0).readByte()).isEqualTo((byte) 32); + buf.writerOffset(0).writeUnsignedByte(32); + assertThat(buf.readerOffset(0).readUnsignedByte()).isEqualTo(32); + buf.writerOffset(0).writeChar('3'); + assertThat(buf.readerOffset(0).readChar()).isEqualTo('3'); + buf.writerOffset(0).writeShort((short) 32); + assertThat(buf.readerOffset(0).readShort()).isEqualTo((short) 32); + buf.writerOffset(0).writeUnsignedShort(32); + assertThat(buf.readerOffset(0).readUnsignedShort()).isEqualTo(32); + buf.writerOffset(0).writeMedium(32); + assertThat(buf.readerOffset(0).readMedium()).isEqualTo(32); + buf.writerOffset(0).writeUnsignedMedium(32); + assertThat(buf.readerOffset(0).readUnsignedMedium()).isEqualTo(32); + buf.writerOffset(0).writeInt(32); + assertThat(buf.readerOffset(0).readInt()).isEqualTo(32); + buf.writerOffset(0).writeUnsignedInt(32); + assertThat(buf.readerOffset(0).readUnsignedInt()).isEqualTo(32L); + buf.writerOffset(0).writeFloat(3.2f); + assertThat(buf.readerOffset(0).readFloat()).isEqualTo(3.2f); + buf.writerOffset(0).writeLong(32); + assertThat(buf.readerOffset(0).readLong()).isEqualTo(32L); + buf.writerOffset(0).writeDouble(3.2); + assertThat(buf.readerOffset(0).readDouble()).isEqualTo(3.2); + + buf.setByte(0, (byte) 32); + assertThat(buf.getByte(0)).isEqualTo((byte) 32); + buf.setUnsignedByte(0, 32); + assertThat(buf.getUnsignedByte(0)).isEqualTo(32); + buf.setChar(0, '3'); + assertThat(buf.getChar(0)).isEqualTo('3'); + buf.setShort(0, (short) 32); + assertThat(buf.getShort(0)).isEqualTo((short) 32); + buf.setUnsignedShort(0, 32); + assertThat(buf.getUnsignedShort(0)).isEqualTo(32); + buf.setMedium(0, 32); + assertThat(buf.getMedium(0)).isEqualTo(32); + buf.setUnsignedMedium(0, 32); + assertThat(buf.getUnsignedMedium(0)).isEqualTo(32); + buf.setInt(0, 32); + assertThat(buf.getInt(0)).isEqualTo(32); + buf.setUnsignedInt(0, 32); + assertThat(buf.getUnsignedInt(0)).isEqualTo(32L); + buf.setFloat(0, 3.2f); + assertThat(buf.getFloat(0)).isEqualTo(3.2f); + buf.setLong(0, 32); + assertThat(buf.getLong(0)).isEqualTo(32L); + buf.setDouble(0, 3.2); + assertThat(buf.getDouble(0)).isEqualTo(3.2); + + if (buf.isOwned()) { + buf.ensureWritable(1); + } + buf.fill((byte) 0); + try (Allocator allocator = Allocator.heap(); + Buf source = allocator.allocate(8)) { + source.copyInto(0, buf, 0, 1); + } + } + + @Test + public void composingReadOnlyBuffersMustCreateReadOnlyCompositeBuffer() { + try (Allocator allocator = Allocator.heap(); + Buf a = allocator.allocate(4).readOnly(true); + Buf b = allocator.allocate(4).readOnly(true); + Buf composite = allocator.compose(a, b)) { + assertTrue(composite.readOnly()); + verifyWriteInaccessible(composite); + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void readOnlyBufferMustRemainReadOnlyAfterSend(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.readOnly(true); + var send = buf.send(); + try (Buf receive = send.receive()) { + assertTrue(receive.readOnly()); + verifyWriteInaccessible(receive); + } + } + } + + @Test + public void readOnlyBufferMustRemainReadOnlyAfterSendForEmptyCompositeBuffer() { + try (Allocator allocator = Allocator.heap(); + Buf buf = allocator.compose()) { + buf.readOnly(true); + var send = buf.send(); + try (Buf receive = send.receive()) { + assertTrue(receive.readOnly()); + } + } + } + + @ParameterizedTest + @MethodSource("pooledAllocators") + public void readOnlyBufferMustNotBeReadOnlyAfterBeingReusedFromPool(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator()) { + for (int i = 0; i < 1000; i++) { + try (Buf buf = allocator.allocate(8)) { + assertFalse(buf.readOnly()); + buf.readOnly(true); + assertTrue(buf.readOnly()); + } + } + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void acquireOfReadOnlyBufferMustBeReadOnly(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.readOnly(true); + try (Buf acquire = buf.acquire()) { + assertTrue(acquire.readOnly()); + } + } + } + + @ParameterizedTest + @MethodSource("allocators") + public void sliceOfReadOnlyBufferMustBeReadOnly(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.writeLong(0x0102030405060708L); + buf.readOnly(true); + try (Buf slice = buf.slice()) { + assertTrue(slice.readOnly()); + } + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void bifurcateOfReadOnlyBufferMustBeReadOnly(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(16)) { + buf.writeLong(0x0102030405060708L); + buf.readOnly(true); + try (Buf bifurcate = buf.bifurcate()) { + assertTrue(bifurcate.readOnly()); + assertTrue(buf.readOnly()); + } + } + } + + @Test + public void composingReadOnlyAndWritableBuffersMustThrow() { + try (Allocator allocator = Allocator.heap(); + Buf a = allocator.allocate(8).readOnly(true); + Buf b = allocator.allocate(8)) { + assertThrows(IllegalArgumentException.class, () -> allocator.compose(a, b)); + assertThrows(IllegalArgumentException.class, () -> allocator.compose(b, a)); + assertThrows(IllegalArgumentException.class, () -> allocator.compose(a, b, a)); + assertThrows(IllegalArgumentException.class, () -> allocator.compose(b, a, b)); + } + } + + @Test + public void compositeWritableBufferCannotBeExtendedWithReadOnlyBuffer() { + try (Allocator allocator = Allocator.heap()) { + Buf composite; + try (Buf a = allocator.allocate(8)) { + composite = allocator.compose(a); + } + try (composite; Buf b = allocator.allocate(8).readOnly(true)) { + assertThrows(IllegalArgumentException.class, () -> Allocator.extend(composite, b)); + } + } + } + + @Test + public void compositeReadOnlyBufferCannotBeExtendedWithWritableBuffer() { + try (Allocator allocator = Allocator.heap()) { + Buf composite; + try (Buf a = allocator.allocate(8).readOnly(true)) { + composite = allocator.compose(a); + } + try (composite; Buf b = allocator.allocate(8)) { + assertThrows(IllegalArgumentException.class, () -> Allocator.extend(composite, b)); + } + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void compactOnReadOnlyBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.readOnly(true); + assertThrows(IllegalStateException.class, () -> buf.compact()); + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void ensureWritableOnReadOnlyBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8)) { + buf.readOnly(true); + assertThrows(IllegalStateException.class, () -> buf.ensureWritable(1)); + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void copyIntoOnReadOnlyBufferMustThrow(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf dest = allocator.allocate(8)) { + dest.readOnly(true); + try (Buf src = allocator.allocate(8)) { + assertThrows(IllegalStateException.class, () -> src.copyInto(0, dest, 0, 1)); + } + } + } + // @ParameterizedTest @MethodSource("allocators")