8346657: Improve out of bounds exception messages for MemorySegments

Reviewed-by: jvernee, liach, mcimadamore
This commit is contained in:
Igor Rudenko 2025-12-03 10:37:55 +00:00 committed by Per Minborg
parent 804ce0a239
commit 170ebdc5b7
4 changed files with 63 additions and 29 deletions

View File

@ -33,6 +33,7 @@ import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.util.ArraysSupport;
import jdk.internal.util.Preconditions;
import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.ForceInline;
import sun.nio.ch.DirectBuffer;
@ -68,7 +69,7 @@ import java.util.stream.StreamSupport;
* {@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();
@ -100,19 +101,19 @@ public abstract sealed class AbstractMemorySegmentImpl
@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)) {
@ -354,7 +355,7 @@ public abstract sealed class AbstractMemorySegmentImpl
@ForceInline
public void checkAccess(long offset, long length, boolean readOnly) {
checkReadOnly(readOnly);
checkBounds(offset, length);
checkAccessBounds(offset, length);
}
@ForceInline
@ -398,20 +399,40 @@ public abstract sealed class AbstractMemorySegmentImpl
}
@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) {
try {
checkBounds(offset, length);
} catch (IndexOutOfBoundsException e) {
throwOutOfBounds(offset, length, /* isSlice = */ true);
}
}
@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) {
try {
checkBounds(offset, length);
} catch (IndexOutOfBoundsException e) {
throwOutOfBounds(offset, length, /* isSlice = */ false);
}
}
@ForceInline
private void checkBounds(long offset, long length) {
if (length > 0) {
Preconditions.checkIndex(offset, this.length - length + 1, null);
} else if (length < 0 || offset < 0 ||
offset > this.length - length) {
throw new IndexOutOfBoundsException();
}
}
@DontInline
private void throwOutOfBounds(long offset, long length, boolean isSlice) {
String action = isSlice ? "get slice" : "access an element";
String msg = String.format("Out of bound access on segment %s; attempting to %s of length %d at offset %d " +
"which is outside the valid range 0 <= offset+length < byteSize (=%d)",
this, action, length, offset, this.length);
throw new IndexOutOfBoundsException(msg);
}
@Override
@ -429,11 +450,6 @@ public abstract sealed class AbstractMemorySegmentImpl
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));
}
static class SegmentSplitter implements Spliterator<MemorySegment> {
AbstractMemorySegmentImpl segment;
long elemCount;

View File

@ -194,7 +194,7 @@ public final class SegmentBulkOperations {
@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();

View File

@ -145,7 +145,7 @@ public final class StringSupport {
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();
@ -179,7 +179,7 @@ public final class StringSupport {
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();
@ -215,7 +215,7 @@ public final class StringSupport {
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();

View File

@ -212,14 +212,32 @@ public class TestSegments {
}
@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);
}
}