blob: 30bb8c33d1b2eda3de8d7ba9b15bbbc5213932ba [file]
// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.androidapi;
import static com.android.tools.r8.references.Reference.BOOL;
import static com.android.tools.r8.references.Reference.BYTE;
import static com.android.tools.r8.references.Reference.CHAR;
import static com.android.tools.r8.references.Reference.DOUBLE;
import static com.android.tools.r8.references.Reference.FLOAT;
import static com.android.tools.r8.references.Reference.INT;
import static com.android.tools.r8.references.Reference.LONG;
import static com.android.tools.r8.references.Reference.SHORT;
import static org.hamcrest.CoreMatchers.equalTo;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/** This class verifies which sun.misc.Unsafe methods exist at which API levels. */
@RunWith(Parameterized.class)
public class SunMiscUnsafeApiTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return TestParameters.builder().withDexRuntimes().withMaximumApiLevel().build();
}
public static final Map<ClassReference, AndroidApiLevel> classApiMap = new HashMap<>();
public static final Map<FieldReference, AndroidApiLevel> fieldApiMap = new HashMap<>();
public static final Map<MethodReference, AndroidApiLevel> methodApiMap = new HashMap<>();
private static final ClassReference CLASS = Reference.classFromClass(Class.class);
private static final ClassReference OBJECT = Reference.classFromClass(Object.class);
private static final ClassReference FIELD = Reference.classFromClass(Field.class);
private static final TypeReference VOID = Reference.returnTypeFromDescriptor("V");
private static void addClass(ClassReference holder, AndroidApiLevel introducedAtApi) {
classApiMap.put(holder, introducedAtApi);
}
private static void addField(
ClassReference holder, String name, AndroidApiLevel introducedAtApi, TypeReference type) {
fieldApiMap.put(Reference.field(holder, name, type), introducedAtApi);
}
private static void addMethod(
ClassReference holder,
String name,
AndroidApiLevel introducedAtApi,
TypeReference returnType,
TypeReference... parameters) {
methodApiMap.put(
Reference.method(holder, name, ImmutableList.copyOf(parameters), returnType),
introducedAtApi);
}
@Test
public void test() throws Exception {
parameters.assumeDexRuntime();
Class<?> testClass = TestClass.class;
testForD8(parameters)
.addProgramClasses(testClass)
.compile()
.run(parameters.getRuntime(), testClass)
.assertSuccess()
.assertStdoutLinesMatchesUnordered(expectedOutput());
}
private List<Matcher<String>> expectedOutput() {
List<Matcher<String>> expectedMatchers = new ArrayList<>();
AndroidApiLevel apiLevel = parameters.getApiLevel();
classApiMap.forEach(
(clazz, api) -> {
boolean shouldBeDefined = apiLevel.isGreaterThanOrEqualTo(api);
expectedMatchers.add(equalTo(clazz.getTypeName() + ": " + shouldBeDefined));
});
fieldApiMap.forEach(
(field, api) -> {
boolean shouldBeDefined = apiLevel.isGreaterThanOrEqualTo(api);
expectedMatchers.add(equalTo(field.toSourceString() + ": " + shouldBeDefined));
});
methodApiMap.forEach(
(method, api) -> {
boolean shouldBeDefined = apiLevel.isGreaterThanOrEqualTo(api);
expectedMatchers.add(equalTo(method.toSourceString() + ": " + shouldBeDefined));
});
return expectedMatchers;
}
static {
ClassReference sunUnsafe = Reference.classFromBinaryName("sun/misc/Unsafe");
TypeReference sunUnsafeType = Reference.typeFromClassReference(sunUnsafe);
ClassReference jdkUnsafe = Reference.classFromBinaryName("jdk/internal/misc/Unsafe");
TypeReference jdkUnsafeType = Reference.typeFromClassReference(jdkUnsafe);
// This should be set to the lowest API tested NOT the lowest possible API.
AndroidApiLevel always = AndroidApiLevel.I_MR1;
addClass(sunUnsafe, always);
addField(sunUnsafe, "INVALID_FIELD_OFFSET", AndroidApiLevel.N, Reference.INT);
addField(sunUnsafe, "THE_ONE", always, sunUnsafeType);
addField(sunUnsafe, "theInternalUnsafe", AndroidApiLevel.MAIN, jdkUnsafeType);
addField(sunUnsafe, "theUnsafe", AndroidApiLevel.K, sunUnsafeType);
addMethod(sunUnsafe, "addressSize", AndroidApiLevel.N, INT);
addMethod(sunUnsafe, "allocateInstance", AndroidApiLevel.J, OBJECT, CLASS);
addMethod(sunUnsafe, "allocateMemory", AndroidApiLevel.N, LONG, LONG);
addMethod(sunUnsafe, "arrayBaseOffset", always, INT, CLASS);
addMethod(sunUnsafe, "arrayIndexScale", always, INT, CLASS);
addMethod(sunUnsafe, "compareAndSwapInt", always, BOOL, OBJECT, LONG, INT, INT);
addMethod(sunUnsafe, "compareAndSwapLong", always, BOOL, OBJECT, LONG, LONG, LONG);
addMethod(sunUnsafe, "compareAndSwapObject", always, BOOL, OBJECT, LONG, OBJECT, OBJECT);
addMethod(sunUnsafe, "copyMemory", AndroidApiLevel.N, VOID, LONG, LONG, LONG);
addMethod(
sunUnsafe,
"copyMemoryFromPrimitiveArray",
AndroidApiLevel.N,
VOID,
OBJECT,
LONG,
LONG,
LONG);
addMethod(
sunUnsafe, "copyMemoryToPrimitiveArray", AndroidApiLevel.N, VOID, LONG, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "forbidObtainingRecordFieldOffsets", AndroidApiLevel.MAIN, BOOL);
addMethod(sunUnsafe, "freeMemory", AndroidApiLevel.N, VOID, LONG);
addMethod(sunUnsafe, "fullFence", AndroidApiLevel.N, VOID);
addMethod(sunUnsafe, "getAndAddInt", AndroidApiLevel.N, INT, OBJECT, LONG, INT);
addMethod(sunUnsafe, "getAndAddLong", AndroidApiLevel.N, LONG, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "getAndSetInt", AndroidApiLevel.N, INT, OBJECT, LONG, INT);
addMethod(sunUnsafe, "getAndSetLong", AndroidApiLevel.N, LONG, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "getAndSetObject", AndroidApiLevel.N, OBJECT, OBJECT, LONG, OBJECT);
addMethod(sunUnsafe, "getArrayBaseOffsetForComponentType", AndroidApiLevel.L_MR1, INT, CLASS);
addMethod(sunUnsafe, "getArrayIndexScaleForComponentType", AndroidApiLevel.L_MR1, INT, CLASS);
addMethod(sunUnsafe, "getBoolean", AndroidApiLevel.N, BOOL, OBJECT, LONG);
addMethod(sunUnsafe, "getByte", AndroidApiLevel.N, BYTE, LONG);
addMethod(sunUnsafe, "getByte", AndroidApiLevel.N, BYTE, OBJECT, LONG);
addMethod(sunUnsafe, "getChar", AndroidApiLevel.N, CHAR, LONG);
addMethod(sunUnsafe, "getChar", AndroidApiLevel.N, CHAR, OBJECT, LONG);
addMethod(sunUnsafe, "getDouble", AndroidApiLevel.N, DOUBLE, LONG);
addMethod(sunUnsafe, "getDouble", AndroidApiLevel.N, DOUBLE, OBJECT, LONG);
addMethod(sunUnsafe, "getFloat", AndroidApiLevel.N, FLOAT, LONG);
addMethod(sunUnsafe, "getFloat", AndroidApiLevel.N, FLOAT, OBJECT, LONG);
addMethod(sunUnsafe, "getInt", AndroidApiLevel.N, INT, LONG);
addMethod(sunUnsafe, "getInt", always, INT, OBJECT, LONG);
addMethod(sunUnsafe, "getIntVolatile", always, INT, OBJECT, LONG);
addMethod(sunUnsafe, "getLong", AndroidApiLevel.N, LONG, LONG);
addMethod(sunUnsafe, "getLong", always, LONG, OBJECT, LONG);
addMethod(sunUnsafe, "getLongVolatile", always, LONG, OBJECT, LONG);
addMethod(sunUnsafe, "getObject", always, OBJECT, OBJECT, LONG);
addMethod(sunUnsafe, "getObjectVolatile", always, OBJECT, OBJECT, LONG);
addMethod(sunUnsafe, "getShort", AndroidApiLevel.N, SHORT, LONG);
addMethod(sunUnsafe, "getShort", AndroidApiLevel.N, SHORT, OBJECT, LONG);
addMethod(sunUnsafe, "getUnsafe", always, sunUnsafe);
addMethod(sunUnsafe, "loadFence", AndroidApiLevel.N, VOID);
addMethod(sunUnsafe, "objectFieldOffset", always, LONG, FIELD);
addMethod(sunUnsafe, "pageSize", AndroidApiLevel.N, INT);
addMethod(sunUnsafe, "park", always, VOID, BOOL, LONG);
addMethod(sunUnsafe, "putBoolean", AndroidApiLevel.N, VOID, OBJECT, LONG, BOOL);
addMethod(sunUnsafe, "putByte", AndroidApiLevel.N, VOID, LONG, BYTE);
addMethod(sunUnsafe, "putByte", AndroidApiLevel.N, VOID, OBJECT, LONG, BYTE);
addMethod(sunUnsafe, "putChar", AndroidApiLevel.N, VOID, LONG, CHAR);
addMethod(sunUnsafe, "putChar", AndroidApiLevel.N, VOID, OBJECT, LONG, CHAR);
addMethod(sunUnsafe, "putDouble", AndroidApiLevel.N, VOID, LONG, DOUBLE);
addMethod(sunUnsafe, "putDouble", AndroidApiLevel.N, VOID, OBJECT, LONG, DOUBLE);
addMethod(sunUnsafe, "putFloat", AndroidApiLevel.N, VOID, LONG, FLOAT);
addMethod(sunUnsafe, "putFloat", AndroidApiLevel.N, VOID, OBJECT, LONG, FLOAT);
addMethod(sunUnsafe, "putInt", AndroidApiLevel.N, VOID, LONG, INT);
addMethod(sunUnsafe, "putInt", always, VOID, OBJECT, LONG, INT);
addMethod(sunUnsafe, "putIntVolatile", always, VOID, OBJECT, LONG, INT);
addMethod(sunUnsafe, "putLong", AndroidApiLevel.N, VOID, LONG, LONG);
addMethod(sunUnsafe, "putLong", always, VOID, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "putLongVolatile", always, VOID, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "putObject", always, VOID, OBJECT, LONG, OBJECT);
addMethod(sunUnsafe, "putObjectVolatile", always, VOID, OBJECT, LONG, OBJECT);
addMethod(sunUnsafe, "putOrderedInt", always, VOID, OBJECT, LONG, INT);
addMethod(sunUnsafe, "putOrderedLong", always, VOID, OBJECT, LONG, LONG);
addMethod(sunUnsafe, "putOrderedObject", always, VOID, OBJECT, LONG, OBJECT);
addMethod(sunUnsafe, "putShort", AndroidApiLevel.N, VOID, LONG, SHORT);
addMethod(sunUnsafe, "putShort", AndroidApiLevel.N, VOID, OBJECT, LONG, SHORT);
addMethod(sunUnsafe, "setMemory", AndroidApiLevel.N, VOID, LONG, LONG, BYTE);
addMethod(sunUnsafe, "storeFence", AndroidApiLevel.N, VOID);
addMethod(sunUnsafe, "unpark", always, VOID, OBJECT);
}
static class TestClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
String UNSAFE = "sun.misc.Unsafe";
checkClass(UNSAFE);
checkField(UNSAFE, "INVALID_FIELD_OFFSET", int.class);
checkField(UNSAFE, "THE_ONE", UNSAFE);
checkField(UNSAFE, "theInternalUnsafe", "jdk.internal.misc.Unsafe");
checkField(UNSAFE, "theUnsafe", UNSAFE);
checkMethod(UNSAFE, "addressSize", int.class);
checkMethod(UNSAFE, "allocateInstance", Object.class, Class.class);
checkMethod(UNSAFE, "allocateMemory", long.class, long.class);
checkMethod(UNSAFE, "arrayBaseOffset", int.class, Class.class);
checkMethod(UNSAFE, "arrayIndexScale", int.class, Class.class);
checkMethod(
UNSAFE,
"compareAndSwapInt",
boolean.class,
Object.class,
long.class,
int.class,
int.class);
checkMethod(
UNSAFE,
"compareAndSwapLong",
boolean.class,
Object.class,
long.class,
long.class,
long.class);
checkMethod(
UNSAFE,
"compareAndSwapObject",
boolean.class,
Object.class,
long.class,
Object.class,
Object.class);
checkMethod(UNSAFE, "copyMemory", void.class, long.class, long.class, long.class);
checkMethod(
UNSAFE,
"copyMemoryFromPrimitiveArray",
void.class,
Object.class,
long.class,
long.class,
long.class);
checkMethod(
UNSAFE,
"copyMemoryToPrimitiveArray",
void.class,
long.class,
Object.class,
long.class,
long.class);
checkMethod(UNSAFE, "forbidObtainingRecordFieldOffsets", boolean.class);
checkMethod(UNSAFE, "freeMemory", void.class, long.class);
checkMethod(UNSAFE, "fullFence", void.class);
checkMethod(UNSAFE, "getAndAddInt", int.class, Object.class, long.class, int.class);
checkMethod(UNSAFE, "getAndAddLong", long.class, Object.class, long.class, long.class);
checkMethod(UNSAFE, "getAndSetInt", int.class, Object.class, long.class, int.class);
checkMethod(UNSAFE, "getAndSetLong", long.class, Object.class, long.class, long.class);
checkMethod(UNSAFE, "getAndSetObject", Object.class, Object.class, long.class, Object.class);
checkMethod(UNSAFE, "getArrayBaseOffsetForComponentType", int.class, Class.class);
checkMethod(UNSAFE, "getArrayIndexScaleForComponentType", int.class, Class.class);
checkMethod(UNSAFE, "getBoolean", boolean.class, Object.class, long.class);
checkMethod(UNSAFE, "getByte", byte.class, Object.class, long.class);
checkMethod(UNSAFE, "getByte", byte.class, long.class);
checkMethod(UNSAFE, "getChar", char.class, Object.class, long.class);
checkMethod(UNSAFE, "getChar", char.class, long.class);
checkMethod(UNSAFE, "getDouble", double.class, Object.class, long.class);
checkMethod(UNSAFE, "getDouble", double.class, long.class);
checkMethod(UNSAFE, "getFloat", float.class, Object.class, long.class);
checkMethod(UNSAFE, "getFloat", float.class, long.class);
checkMethod(UNSAFE, "getInt", int.class, Object.class, long.class);
checkMethod(UNSAFE, "getInt", int.class, long.class);
checkMethod(UNSAFE, "getIntVolatile", int.class, Object.class, long.class);
checkMethod(UNSAFE, "getLong", long.class, Object.class, long.class);
checkMethod(UNSAFE, "getLong", long.class, long.class);
checkMethod(UNSAFE, "getLongVolatile", long.class, Object.class, long.class);
checkMethod(UNSAFE, "getObject", Object.class, Object.class, long.class);
checkMethod(UNSAFE, "getObjectVolatile", Object.class, Object.class, long.class);
checkMethod(UNSAFE, "getShort", short.class, Object.class, long.class);
checkMethod(UNSAFE, "getShort", short.class, long.class);
checkMethod(UNSAFE, "getUnsafe", UNSAFE);
checkMethod(UNSAFE, "loadFence", void.class);
checkMethod(UNSAFE, "objectFieldOffset", long.class, Field.class);
checkMethod(UNSAFE, "pageSize", int.class);
checkMethod(UNSAFE, "park", void.class, boolean.class, long.class);
checkMethod(UNSAFE, "putBoolean", void.class, Object.class, long.class, boolean.class);
checkMethod(UNSAFE, "putByte", void.class, Object.class, long.class, byte.class);
checkMethod(UNSAFE, "putByte", void.class, long.class, byte.class);
checkMethod(UNSAFE, "putChar", void.class, Object.class, long.class, char.class);
checkMethod(UNSAFE, "putChar", void.class, long.class, char.class);
checkMethod(UNSAFE, "putDouble", void.class, Object.class, long.class, double.class);
checkMethod(UNSAFE, "putDouble", void.class, long.class, double.class);
checkMethod(UNSAFE, "putFloat", void.class, Object.class, long.class, float.class);
checkMethod(UNSAFE, "putFloat", void.class, long.class, float.class);
checkMethod(UNSAFE, "putInt", void.class, Object.class, long.class, int.class);
checkMethod(UNSAFE, "putInt", void.class, long.class, int.class);
checkMethod(UNSAFE, "putIntVolatile", void.class, Object.class, long.class, int.class);
checkMethod(UNSAFE, "putLong", void.class, Object.class, long.class, long.class);
checkMethod(UNSAFE, "putLong", void.class, long.class, long.class);
checkMethod(UNSAFE, "putLongVolatile", void.class, Object.class, long.class, long.class);
checkMethod(UNSAFE, "putObject", void.class, Object.class, long.class, Object.class);
checkMethod(UNSAFE, "putObjectVolatile", void.class, Object.class, long.class, Object.class);
checkMethod(UNSAFE, "putOrderedInt", void.class, Object.class, long.class, int.class);
checkMethod(UNSAFE, "putOrderedLong", void.class, Object.class, long.class, long.class);
checkMethod(UNSAFE, "putOrderedObject", void.class, Object.class, long.class, Object.class);
checkMethod(UNSAFE, "putShort", void.class, Object.class, long.class, short.class);
checkMethod(UNSAFE, "putShort", void.class, long.class, short.class);
checkMethod(UNSAFE, "setMemory", void.class, long.class, long.class, byte.class);
checkMethod(UNSAFE, "storeFence", void.class);
checkMethod(UNSAFE, "unpark", void.class, Object.class);
}
private static void checkClass(String name) {
boolean doesClassExit = getClass(name) != null;
System.out.println(name + ": " + doesClassExit);
}
private static Class<?> getClass(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
return null;
}
}
private static void checkField(String holder, String name, Class<?> type) {
printField(holder, name, typeString(type), doesUnsafeFieldExist(holder, name, type));
}
private static void checkField(String holder, String name, String type) {
Class<?> fieldType = getClass(type);
if (fieldType == null) {
printField(holder, name, type, false);
} else {
checkField(holder, name, fieldType);
}
}
private static String typeString(Class<?> type) {
return type.getCanonicalName();
}
private static boolean doesUnsafeFieldExist(String holder, String name, Class<?> type) {
Class<?> holderClass = getClass(holder);
if (holderClass == null) {
return false;
}
try {
Field field = holderClass.getDeclaredField(name);
return type.equals(field.getType());
} catch (NoSuchFieldException e) {
return false;
}
}
private static void printField(String holder, String name, String type, boolean found) {
System.out.println(type + " " + holder + "." + name + ": " + found);
}
private static void checkMethod(
String holder, String name, Class<?> returnType, Class<?>... parameterTypes) {
boolean doesExist = doesMethodExist(holder, name, returnType, parameterTypes);
String[] params = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
params[i] = typeString(parameterTypes[i]);
}
printMethod(holder, name, typeString(returnType), params, doesExist);
}
private static void checkMethod(String holder, String name, String returnType) {
Class<?> returnTypeClass = getClass(returnType);
if (returnTypeClass == null) {
printMethod(holder, name, returnType, new String[0], false);
} else {
checkMethod(holder, name, returnTypeClass);
}
}
private static void printMethod(
String holder, String name, String returnType, String[] parameterTypes, boolean found) {
String output =
returnType
+ " "
+ holder
+ "."
+ name
+ "("
+ String.join(", ", parameterTypes)
+ "): "
+ found;
System.out.println(output);
}
private static boolean doesMethodExist(
String holder, String name, Class<?> returnType, Class<?>... parameterTypes) {
Class<?> holderClass = getClass(holder);
if (holderClass == null) {
return false;
}
try {
Method method = holderClass.getDeclaredMethod(name, parameterTypes);
return returnType.equals(method.getReturnType());
} catch (NoSuchMethodException e) {
return false;
}
}
}
}