remove final use of sun.misc.Unsafe (#1995)

* remove final use of sun.misc.Unsafe

* Update PekkoBuild.scala

* update exception handling

* Update Unsafe.java
This commit is contained in:
PJ Fanning 2025-08-07 09:36:46 +01:00 committed by GitHub
parent 4be8edcae4
commit c11434abb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 74 deletions

View file

@ -26,7 +26,6 @@ class AsciiStringCopySpec extends AnyWordSpec with Matchers {
// this is known to fail with JDK 11 on ARM32 (Raspberry Pi),
// and therefore algorithm 0 is selected on that architecture
Unsafe.testUSAsciiStrToBytesAlgorithm1("abc") should ===(true)
Unsafe.testUSAsciiStrToBytesAlgorithm2("abc") should ===(false)
}
"copy string internal representation successfully" in {

View file

@ -15,47 +15,37 @@ package org.apache.pekko.util;
import org.apache.pekko.annotation.InternalApi;
import java.lang.reflect.Field;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.charset.StandardCharsets;
/** INTERNAL API */
@InternalApi
public final class Unsafe {
private static final sun.misc.Unsafe instance;
private static final long stringValueFieldOffset;
private static final VarHandle stringValueFieldHandle;
private static final int copyUSAsciiStrToBytesAlgorithm;
static {
try {
sun.misc.Unsafe found = null;
for (Field field : sun.misc.Unsafe.class.getDeclaredFields()) {
if (field.getType() == sun.misc.Unsafe.class) {
field.setAccessible(true);
found = (sun.misc.Unsafe) field.get(null);
break;
}
}
if (found == null) throw new IllegalStateException("Can't find instance of sun.misc.Unsafe");
else instance = found;
long fo;
VarHandle handle;
try {
fo = instance.objectFieldOffset(String.class.getDeclaredField("value"));
} catch (NoSuchFieldException nsfe) {
// The platform's implementation of String doesn't have a 'value' field, so we have to use
// algorithm 0
fo = -1;
MethodHandles.Lookup lookup =
MethodHandles.privateLookupIn(String.class, MethodHandles.lookup());
handle = lookup.findVarHandle(String.class, "value", byte[].class);
} catch (NoSuchFieldException | IllegalAccessException e) {
// The platform's implementation of String doesn't have a 'value' field
// or the field is inaccessible, so we have to use algorithm 0.
// You need `--add-opens=java.base/java.lang=ALL-UNNAMED` or similar to access it.
handle = null;
}
stringValueFieldOffset = fo;
stringValueFieldHandle = handle;
if (stringValueFieldOffset > -1) {
// Select optimization algorithm for `copyUSAciiBytesToStr`.
if (handle != null) {
// Select optimization algorithm for `copyUSAsciiBytesToStr`.
// For example algorithm 1 will fail with JDK 11 on ARM32 (Raspberry Pi),
// and therefore algorithm 0 is selected on that architecture.
String testStr = "abc";
if (testUSAsciiStrToBytesAlgorithm1(testStr)) copyUSAsciiStrToBytesAlgorithm = 1;
else if (testUSAsciiStrToBytesAlgorithm2(testStr)) copyUSAsciiStrToBytesAlgorithm = 2;
else copyUSAsciiStrToBytesAlgorithm = 0;
} else
// We know so little about the platform's String implementation that we have
@ -70,12 +60,12 @@ public final class Unsafe {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
// copy of implementation in copyUSAsciiBytesToStr
byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(strBytes, 0, bytes, 0, str.length());
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
String result = copyUSAsciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
@ -86,38 +76,19 @@ public final class Unsafe {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
// copy of implementation in copyUSAsciiBytesToStr
final byte[] chars = (byte[]) stringValueFieldHandle.get(str);
System.arraycopy(chars, 0, bytes, 0, str.length());
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
String result = copyUSAsciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
}
}
static boolean testUSAsciiStrToBytesAlgorithm2(String str) {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
int i = 0;
while (i < str.length()) {
bytes[i] = (byte) chars[i++];
}
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
}
}
private static String copyUSAciiBytesToStr(int length, byte[] bytes) {
private static String copyUSAsciiBytesToStr(int length, byte[] bytes) {
char[] resultChars = new char[length];
int i = 0;
while (i < length) {
@ -130,14 +101,8 @@ public final class Unsafe {
public static void copyUSAsciiStrToBytes(String str, byte[] bytes) {
if (copyUSAsciiStrToBytesAlgorithm == 1) {
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
final byte[] chars = (byte[]) stringValueFieldHandle.get(str);
System.arraycopy(chars, 0, bytes, 0, str.length());
} else if (copyUSAsciiStrToBytesAlgorithm == 2) {
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
int i = 0;
while (i < str.length()) {
bytes[i] = (byte) chars[i++];
}
} else {
byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(strBytes, 0, bytes, 0, str.length());
@ -150,20 +115,7 @@ public final class Unsafe {
int i = 0;
if (copyUSAsciiStrToBytesAlgorithm == 1) {
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
while (i < str.length()) {
long x = s0 ^ (long) chars[i++]; // Mix character into PRNG state
long y = s1;
// Xorshift128+ round
s0 = y;
x ^= x << 23;
y ^= y >>> 26;
x ^= x >>> 17;
s1 = x ^ y;
}
} else if (copyUSAsciiStrToBytesAlgorithm == 2) {
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
final byte[] chars = (byte[]) stringValueFieldHandle.get(str);
while (i < str.length()) {
long x = s0 ^ (long) chars[i++]; // Mix character into PRNG state
long y = s1;

View file

@ -111,8 +111,7 @@ object PekkoBuild {
private val jvmGCLogOptions: Seq[String] = Seq("-Xlog:gc*")
// -XDignore.symbol.file suppresses sun.misc.Unsafe warnings
final val DefaultJavacOptions = Seq("-encoding", "UTF-8", "-Xlint:unchecked", "-XDignore.symbol.file")
final val DefaultJavacOptions = Seq("-encoding", "UTF-8", "-Xlint:unchecked")
lazy val defaultSettings: Seq[Setting[_]] = Def.settings(
projectInfoVersion := (if (isSnapshot.value) "snapshot" else version.value),