Skip to content

Commit cd50d78

Browse files
committed
8361300: Document exceptions for Unsafe offset methods
Reviewed-by: jrose, vyazici
1 parent 241808e commit cd50d78

File tree

9 files changed

+192
-17
lines changed

9 files changed

+192
-17
lines changed

src/hotspot/share/prims/unsafe.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,9 @@ UNSAFE_LEAF (void, Unsafe_WriteBackPostSync0(JNIEnv *env, jobject unsafe)) {
480480

481481
////// Random queries
482482

483-
static jlong find_field_offset(jclass clazz, jstring name, TRAPS) {
483+
// Finds the object field offset of a field with the matching name, or an error code
484+
// Error code -1 is not found, -2 is static field
485+
static jlong find_known_instance_field_offset(jclass clazz, jstring name, TRAPS) {
484486
assert(clazz != nullptr, "clazz must not be null");
485487
assert(name != nullptr, "name must not be null");
486488

@@ -489,16 +491,20 @@ static jlong find_field_offset(jclass clazz, jstring name, TRAPS) {
489491

490492
InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
491493

492-
jint offset = -1;
494+
jint offset = -1; // Not found
493495
for (JavaFieldStream fs(k); !fs.done(); fs.next()) {
494496
Symbol *name = fs.name();
495497
if (name->equals(utf_name)) {
496-
offset = fs.offset();
498+
if (!fs.access_flags().is_static()) {
499+
offset = fs.offset();
500+
} else {
501+
offset = -2; // A static field
502+
}
497503
break;
498504
}
499505
}
500506
if (offset < 0) {
501-
THROW_0(vmSymbols::java_lang_InternalError());
507+
return offset; // Error code
502508
}
503509
return field_offset_from_byte_offset(offset);
504510
}
@@ -527,8 +533,8 @@ UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset0(JNIEnv *env, jobject unsafe, jobje
527533
return find_field_offset(field, 0, THREAD);
528534
} UNSAFE_END
529535

530-
UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset1(JNIEnv *env, jobject unsafe, jclass c, jstring name)) {
531-
return find_field_offset(c, name, THREAD);
536+
UNSAFE_ENTRY(jlong, Unsafe_KnownObjectFieldOffset0(JNIEnv *env, jobject unsafe, jclass c, jstring name)) {
537+
return find_known_instance_field_offset(c, name, THREAD);
532538
} UNSAFE_END
533539

534540
UNSAFE_ENTRY(jlong, Unsafe_StaticFieldOffset0(JNIEnv *env, jobject unsafe, jobject field)) {
@@ -882,7 +888,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = {
882888
{CC "freeMemory0", CC "(" ADR ")V", FN_PTR(Unsafe_FreeMemory0)},
883889

884890
{CC "objectFieldOffset0", CC "(" FLD ")J", FN_PTR(Unsafe_ObjectFieldOffset0)},
885-
{CC "objectFieldOffset1", CC "(" CLS LANG "String;)J", FN_PTR(Unsafe_ObjectFieldOffset1)},
891+
{CC "knownObjectFieldOffset0", CC "(" CLS LANG "String;)J", FN_PTR(Unsafe_KnownObjectFieldOffset0)},
886892
{CC "staticFieldOffset0", CC "(" FLD ")J", FN_PTR(Unsafe_StaticFieldOffset0)},
887893
{CC "staticFieldBase0", CC "(" FLD ")" OBJ, FN_PTR(Unsafe_StaticFieldBase0)},
888894
{CC "ensureClassInitialized0", CC "(" CLS ")V", FN_PTR(Unsafe_EnsureClassInitialized0)},

src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,9 @@ private static final class AtomicIntegerFieldUpdaterImpl<T>
403403
if (!Modifier.isVolatile(modifiers))
404404
throw new IllegalArgumentException("Must be volatile type");
405405

406+
if (Modifier.isStatic(modifiers))
407+
throw new IllegalArgumentException("Must not be a static field");
408+
406409
// Access to protected field members is restricted to receivers only
407410
// of the accessing class, or one of its subclasses, and the
408411
// accessing class must in turn be a subclass (or package sibling)

src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ private static final class CASUpdater<T> extends AtomicLongFieldUpdater<T> {
398398
if (!Modifier.isVolatile(modifiers))
399399
throw new IllegalArgumentException("Must be volatile type");
400400

401+
if (Modifier.isStatic(modifiers))
402+
throw new IllegalArgumentException("Must not be a static field");
403+
401404
// Access to protected field members is restricted to receivers only
402405
// of the accessing class, or one of its subclasses, and the
403406
// accessing class must in turn be a subclass (or package sibling)

src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ private static final class AtomicReferenceFieldUpdaterImpl<T,V>
363363
if (!Modifier.isVolatile(modifiers))
364364
throw new IllegalArgumentException("Must be volatile type");
365365

366+
if (Modifier.isStatic(modifiers))
367+
throw new IllegalArgumentException("Must not be a static field");
368+
366369
// Access to protected field members is restricted to receivers only
367370
// of the accessing class, or one of its subclasses, and the
368371
// accessing class must in turn be a subclass (or package sibling)

src/java.base/share/classes/jdk/internal/misc/Unsafe.java

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,9 @@ private void checkWritebackEnabled() {
10691069
* the field locations in a form usable by {@link #getInt(Object,long)}.
10701070
* Therefore, code which will be ported to such JVMs on 64-bit platforms
10711071
* must preserve all bits of static field offsets.
1072+
*
1073+
* @throws NullPointerException if the field is {@code null}
1074+
* @throws IllegalArgumentException if the field is static
10721075
* @see #getInt(Object, long)
10731076
*/
10741077
public long objectFieldOffset(Field f) {
@@ -1080,13 +1083,17 @@ public long objectFieldOffset(Field f) {
10801083
}
10811084

10821085
/**
1083-
* Reports the location of the field with a given name in the storage
1084-
* allocation of its class.
1086+
* (For compile-time known instance fields in JDK code only) Reports the
1087+
* location of the field with a given name in the storage allocation of its
1088+
* class.
1089+
* <p>
1090+
* This API is used to avoid creating reflective Objects in Java code at
1091+
* startup. This should not be used to find fields in non-trusted code.
1092+
* Use the {@link #objectFieldOffset(Field) Field}-accepting version for
1093+
* arbitrary fields instead.
10851094
*
10861095
* @throws NullPointerException if any parameter is {@code null}.
1087-
* @throws InternalError if there is no field named {@code name} declared
1088-
* in class {@code c}, i.e., if {@code c.getDeclaredField(name)}
1089-
* would throw {@code java.lang.NoSuchFieldException}.
1096+
* @throws InternalError if the presumably known field couldn't be found
10901097
*
10911098
* @see #objectFieldOffset(Field)
10921099
*/
@@ -1095,7 +1102,16 @@ public long objectFieldOffset(Class<?> c, String name) {
10951102
throw new NullPointerException();
10961103
}
10971104

1098-
return objectFieldOffset1(c, name);
1105+
long result = knownObjectFieldOffset0(c, name);
1106+
if (result < 0) {
1107+
String type = switch ((int) result) {
1108+
case -2 -> "a static field";
1109+
case -1 -> "not found";
1110+
default -> "unknown";
1111+
};
1112+
throw new InternalError("Field %s.%s %s".formatted(c.getTypeName(), name, type));
1113+
}
1114+
return result;
10991115
}
11001116

11011117
/**
@@ -1113,6 +1129,9 @@ public long objectFieldOffset(Class<?> c, String name) {
11131129
* a few bits to encode an offset within a non-array object,
11141130
* However, for consistency with other methods in this class,
11151131
* this method reports its result as a long value.
1132+
*
1133+
* @throws NullPointerException if the field is {@code null}
1134+
* @throws IllegalArgumentException if the field is not static
11161135
* @see #getInt(Object, long)
11171136
*/
11181137
public long staticFieldOffset(Field f) {
@@ -1132,6 +1151,9 @@ public long staticFieldOffset(Field f) {
11321151
* which is a "cookie", not guaranteed to be a real Object, and it should
11331152
* not be used in any way except as argument to the get and put routines in
11341153
* this class.
1154+
*
1155+
* @throws NullPointerException if the field is {@code null}
1156+
* @throws IllegalArgumentException if the field is not static
11351157
*/
11361158
public Object staticFieldBase(Field f) {
11371159
if (f == null) {
@@ -3848,10 +3870,10 @@ private void putShortParts(Object o, long offset, byte i0, byte i1) {
38483870
@IntrinsicCandidate
38493871
private native void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
38503872
private native void copySwapMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize);
3851-
private native long objectFieldOffset0(Field f);
3852-
private native long objectFieldOffset1(Class<?> c, String name);
3853-
private native long staticFieldOffset0(Field f);
3854-
private native Object staticFieldBase0(Field f);
3873+
private native long objectFieldOffset0(Field f); // throws IAE
3874+
private native long knownObjectFieldOffset0(Class<?> c, String name); // error code: -1 not found, -2 static
3875+
private native long staticFieldOffset0(Field f); // throws IAE
3876+
private native Object staticFieldBase0(Field f); // throws IAE
38553877
private native boolean shouldBeInitialized0(Class<?> c);
38563878
private native void ensureClassInitialized0(Class<?> c);
38573879
private native int arrayBaseOffset0(Class<?> arrayClass); // public version returns long to promote correct arithmetic

test/jdk/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase {
4444
private volatile int privateField;
4545
int w;
4646
float z;
47+
static volatile int q;
4748
public static void main(String[] args) {
4849
main(suite(), args);
4950
}
@@ -88,6 +89,16 @@ public void testConstructor3() {
8889
} catch (IllegalArgumentException success) {}
8990
}
9091

92+
/**
93+
* construction with static field throws IllegalArgumentException
94+
*/
95+
public void testConstructor4() {
96+
try {
97+
updaterFor("q");
98+
shouldThrow();
99+
} catch (IllegalArgumentException success) {}
100+
}
101+
91102
/**
92103
* construction using private field from subclass throws RuntimeException
93104
*/

test/jdk/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class AtomicLongFieldUpdaterTest extends JSR166TestCase {
4444
private volatile long privateField;
4545
long w;
4646
float z;
47+
static volatile long q;
4748
public static void main(String[] args) {
4849
main(suite(), args);
4950
}
@@ -88,6 +89,16 @@ public void testConstructor3() {
8889
} catch (IllegalArgumentException success) {}
8990
}
9091

92+
/**
93+
* construction with static field throws IllegalArgumentException
94+
*/
95+
public void testConstructor4() {
96+
try {
97+
updaterFor("q");
98+
shouldThrow();
99+
} catch (IllegalArgumentException success) {}
100+
}
101+
91102
/**
92103
* construction using private field from subclass throws RuntimeException
93104
*/

test/jdk/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase {
4545
Object z;
4646
Item w;
4747
volatile int i;
48+
static volatile Item q;
4849

4950
public static void main(String[] args) {
5051
main(suite(), args);
@@ -100,6 +101,17 @@ public void testConstructor4() {
100101
} catch (ClassCastException success) {}
101102
}
102103

104+
/**
105+
* construction with static field throws IllegalArgumentException
106+
*/
107+
public void testConstructor5() {
108+
try {
109+
updaterFor("q");
110+
shouldThrow();
111+
} catch (IllegalArgumentException success) {}
112+
}
113+
114+
103115
/**
104116
* construction using private field from subclass throws RuntimeException
105117
*/
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.lang.reflect.Field;
25+
26+
import org.junit.jupiter.api.Test;
27+
28+
import static jdk.internal.misc.Unsafe.getUnsafe;
29+
import static org.junit.jupiter.api.Assertions.*;
30+
31+
/*
32+
* @test
33+
* @bug 8361300
34+
* @summary Verify Unsafe memory address computation method contracts,
35+
* exposed via sun.misc.Unsafe
36+
* @modules java.base/jdk.internal.misc
37+
* @run junit AddressComputationContractTest
38+
*/
39+
public class AddressComputationContractTest {
40+
41+
int instanceField;
42+
static int staticField;
43+
44+
private static final Field INSTANCE_FIELD;
45+
private static final Field STATIC_FIELD;
46+
47+
static {
48+
try {
49+
INSTANCE_FIELD = AddressComputationContractTest.class.getDeclaredField("instanceField");
50+
STATIC_FIELD = AddressComputationContractTest.class.getDeclaredField("staticField");
51+
} catch (ReflectiveOperationException ex) {
52+
throw new ExceptionInInitializerError(ex);
53+
}
54+
}
55+
56+
@Test
57+
void objectFieldOffset() {
58+
assertDoesNotThrow(() -> getUnsafe().objectFieldOffset(INSTANCE_FIELD));
59+
assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(null));
60+
assertThrows(IllegalArgumentException.class, () -> getUnsafe().objectFieldOffset(STATIC_FIELD));
61+
}
62+
63+
@Test
64+
void knownObjectFieldOffset() {
65+
assertDoesNotThrow(() -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "instanceField"));
66+
assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(null, "instanceField"));
67+
assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, null));
68+
// Two conventional failure cases, not necessarily complete
69+
var dneMsg = assertThrows(InternalError.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "doesNotExist")).getMessage();
70+
assertTrue(dneMsg.contains("AddressComputationContractTest.doesNotExist") && dneMsg.contains("not found"), dneMsg);
71+
var staticMsg = assertThrows(InternalError.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "staticField")).getMessage();
72+
assertTrue(staticMsg.contains("AddressComputationContractTest.staticField") && staticMsg.contains("static field"), staticMsg);
73+
}
74+
75+
@Test
76+
void staticFieldOffset() {
77+
assertDoesNotThrow(() -> getUnsafe().staticFieldOffset(STATIC_FIELD));
78+
assertThrows(NullPointerException.class, () -> getUnsafe().staticFieldOffset(null));
79+
assertThrows(IllegalArgumentException.class, () -> getUnsafe().staticFieldOffset(INSTANCE_FIELD));
80+
}
81+
82+
@Test
83+
void staticFieldBase() {
84+
assertDoesNotThrow(() -> getUnsafe().staticFieldBase(STATIC_FIELD));
85+
assertThrows(NullPointerException.class, () -> getUnsafe().staticFieldBase(null));
86+
assertThrows(IllegalArgumentException.class, () -> getUnsafe().staticFieldBase(INSTANCE_FIELD));
87+
}
88+
89+
@Test
90+
void arrayBaseOffset() {
91+
assertDoesNotThrow(() -> getUnsafe().arrayBaseOffset(int[].class));
92+
assertThrows(NullPointerException.class, () -> getUnsafe().arrayBaseOffset(null));
93+
// Caused by VM trying to throw java.lang.InvalidClassException (there's one in java.io instead)
94+
assertThrows(NoClassDefFoundError.class, () -> getUnsafe().arrayBaseOffset(AddressComputationContractTest.class));
95+
}
96+
97+
@Test
98+
void arrayIndexScale() {
99+
assertDoesNotThrow(() -> getUnsafe().arrayIndexScale(int[].class));
100+
assertThrows(NullPointerException.class, () -> getUnsafe().arrayIndexScale(null));
101+
// Caused by VM trying to throw java.lang.InvalidClassException (there's one in java.io instead)
102+
assertThrows(NoClassDefFoundError.class, () -> getUnsafe().arrayIndexScale(AddressComputationContractTest.class));
103+
}
104+
}

0 commit comments

Comments
 (0)