Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
* {@link MappedMemorySegmentImpl}.
*/
public abstract sealed class AbstractMemorySegmentImpl
implements MemorySegment, SegmentAllocator, BiFunction<String, List<Number>, RuntimeException>
implements MemorySegment, SegmentAllocator
permits HeapMemorySegmentImpl, NativeMemorySegmentImpl {

static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess();
Expand Down Expand Up @@ -100,19 +100,19 @@ public boolean isReadOnly() {

@Override
public AbstractMemorySegmentImpl asSlice(long offset, long newSize) {
checkBounds(offset, newSize);
checkSliceBounds(offset, newSize);
return asSliceNoCheck(offset, newSize);
}

@Override
public AbstractMemorySegmentImpl asSlice(long offset) {
checkBounds(offset, 0);
checkSliceBounds(offset, 0);
return asSliceNoCheck(offset, length - offset);
}

@Override
public MemorySegment asSlice(long offset, long newSize, long byteAlignment) {
checkBounds(offset, newSize);
checkSliceBounds(offset, newSize);
Utils.checkAlign(byteAlignment);

if (!isAlignedForElement(offset, byteAlignment)) {
Expand Down Expand Up @@ -354,7 +354,7 @@ public void checkReadOnly(boolean readOnly) {
@ForceInline
public void checkAccess(long offset, long length, boolean readOnly) {
checkReadOnly(readOnly);
checkBounds(offset, length);
checkAccessBounds(offset, length);
}

@ForceInline
Expand Down Expand Up @@ -398,20 +398,13 @@ private int checkArraySize(String typeName, int elemSize) {
}

@ForceInline
void checkBounds(long offset, long length) {
if (length > 0) {
Preconditions.checkIndex(offset, this.length - length + 1, this);
} else if (length < 0 || offset < 0 ||
offset > this.length - length) {
throw outOfBoundException(offset, length);
}
void checkSliceBounds(long offset, long length) {
Preconditions.checkFromIndexSize(offset, length, this.length, this::sliceOutOfBoundException);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this performance-wise neutral? This is on a hot path and captures a lambda every time, need to verify compiler can eliminate this, which I doubt.

}

@Override
public RuntimeException apply(String s, List<Number> numbers) {
long offset = numbers.get(0).longValue();
long length = byteSize() - numbers.get(1).longValue() + 1;
return outOfBoundException(offset, length);
@ForceInline
void checkAccessBounds(long offset, long length) {
Preconditions.checkFromIndexSize(offset, length, this.length, this::accessOutOfBoundException);
}

@Override
Expand All @@ -429,9 +422,28 @@ public final MemorySessionImpl sessionImpl() {
return scope;
}

private IndexOutOfBoundsException outOfBoundException(long offset, long length) {
return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; new offset = %d; new length = %d",
this, offset, length));
private IndexOutOfBoundsException sliceOutOfBoundException(String s, List<Number> numbers) {
return outOfBoundException(BoundPolicy.SLICE, numbers);
}

private IndexOutOfBoundsException accessOutOfBoundException(String s, List<Number> numbers) {
return outOfBoundException(BoundPolicy.ACCESS, numbers);
}

private IndexOutOfBoundsException outOfBoundException(BoundPolicy policy, List<Number> numbers) {
long offset = numbers.get(0).longValue();
long length = numbers.get(1).longValue();
long totalLength = numbers.get(2).longValue();

String msg = switch (policy) {
case BoundPolicy.SLICE -> "attempting to get slice of length";
case BoundPolicy.ACCESS -> "attempting to access an element of length";
};

return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new message looks ok, but note that this message is also reused by the asSlice methods. If we want to clarify the messages, perhaps we should think about rearranging the code a bit?

Related note: this is also why the routine that check OOB access needs to worry about length being zero -- because that's a valid value for asSlice (but not for access).

"%s %d at offset %d " +
"which is outside the valid range 0 <= offset+length < byteSize (=%d)",
this, msg, length, offset, totalLength));
}

static class SegmentSplitter implements Spliterator<MemorySegment> {
Expand Down Expand Up @@ -508,6 +520,10 @@ public int characteristics() {
}
}

private enum BoundPolicy {
SLICE, ACCESS
}

// Object methods

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public static void copy(AbstractMemorySegmentImpl src, long srcOffset,
@ForceInline
public static int contentHash(AbstractMemorySegmentImpl segment, long fromOffset, long toOffset) {
final long length = toOffset - fromOffset;
segment.checkBounds(fromOffset, length);
segment.checkSliceBounds(fromOffset, length);
if (length == 0) {
// The state has to be checked explicitly for zero-length segments
segment.scope.checkValidState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public static int strlenByte(final AbstractMemorySegmentImpl segment,
final long fromOffset,
final long toOffset) {
final long length = toOffset - fromOffset;
segment.checkBounds(fromOffset, length);
segment.checkSliceBounds(fromOffset, length);
if (length < Byte.BYTES) {
// There can be no null terminator present
segment.scope.checkValidState();
Expand Down Expand Up @@ -179,7 +179,7 @@ public static int strlenShort(final AbstractMemorySegmentImpl segment,
final long fromOffset,
final long toOffset) {
final long length = toOffset - fromOffset;
segment.checkBounds(fromOffset, length);
segment.checkSliceBounds(fromOffset, length);
if (length < Short.BYTES) {
// There can be no null terminator present
segment.scope.checkValidState();
Expand Down Expand Up @@ -215,7 +215,7 @@ public static int strlenInt(final AbstractMemorySegmentImpl segment,
final long fromOffset,
final long toOffset) {
final long length = toOffset - fromOffset;
segment.checkBounds(fromOffset, length);
segment.checkSliceBounds(fromOffset, length);
if (length < Integer.BYTES) {
// There can be no null terminator present
segment.scope.checkValidState();
Expand Down
26 changes: 22 additions & 4 deletions test/jdk/java/foreign/TestSegments.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,32 @@ public void testSmallSegmentMin() {
}

@Test
public void testSegmentOOBMessage() {
public void testSegmentAccessOOBMessage() {
try {
var segment = Arena.global().allocate(10, 1);
segment.getAtIndex(ValueLayout.JAVA_INT, 2);
fail("Expected IndexOutOfBoundsException was not thrown");
} catch (IndexOutOfBoundsException ex) {
assertTrue(ex.getMessage().contains("Out of bound access"));
assertTrue(ex.getMessage().contains("offset = 8"));
assertTrue(ex.getMessage().contains("length = 4"));
assertTrue(ex.getMessage().startsWith("Out of bound access"));
assertTrue(ex.getMessage().endsWith("attempting to access an element of length 4 at offset 8 " +
"which is outside the valid range 0 <= offset+length < byteSize (=10)"));
} catch (Exception ex) {
fail("Unexpected exception type thrown: " + ex);
}
}

@Test
public void testSegmentSliceOOBMessage() {
try {
var segment = Arena.global().allocate(10, 1);
var slice = segment.asSlice(8, 4);
fail("Expected IndexOutOfBoundsException was not thrown");
} catch (IndexOutOfBoundsException ex) {
assertTrue(ex.getMessage().startsWith("Out of bound access"));
assertTrue(ex.getMessage().endsWith("attempting to get slice of length 4 at offset 8 " +
"which is outside the valid range 0 <= offset+length < byteSize (=10)"));
} catch (Exception ex) {
fail("Unexpected exception type thrown: " + ex);
}
}

Expand Down