blob: 1c6dec0ba43b20614a222cd523c3732e5ab0df6c [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.unsafeInterface;
import static org.junit.Assert.assertArrayEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.dex.SunMiscUnsafeResourceProvider;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Base64;
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;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.TraceClassVisitor;
@RunWith(Parameterized.class)
public class GenerateSunMiscUnsafe extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
@Test
public void verifyBytes() {
byte[] generatedBytes = getUnsafeClassBytes();
byte[] bytes = SunMiscUnsafeResourceProvider.sunMiscUnsafeBytes();
assertArrayEquals(
"Did you forget to run the main method in this class after making changes to"
+ " sun.misc.Unsafe?",
generatedBytes,
bytes);
}
private static void publicNativeMethod(ClassWriter cw, String name, String descriptor) {
cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_NATIVE, name, descriptor, null, null)
.visitEnd();
}
private static byte[] getUnsafeClassBytes() {
// If this assert fails then check these things before updating the assert:
// * Check if libcore/ojluni/src/main/java/sun/misc/Unsafe.java has new public methods,
// including new overloads.
// * If so, add the new methods here
// (and to SunMiscUnsafeApiTest but it will fail if you don't).
// * Verify that no existing methods have been removed.
assert AndroidApiLevel.LATEST.isEqualTo(AndroidApiLevel.BAKLAVA_1);
// Public signatures of sun.misc.Unsafe.
ClassWriter cw = new ClassWriter(0);
cw.visit(
Opcodes.V1_8,
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"sun/misc/Unsafe",
null,
"java/lang/Object",
null);
cw.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
"INVALID_FIELD_OFFSET",
"I",
null,
0)
.visitEnd();
MethodVisitor mv;
publicNativeMethod(cw, "addressSize", "()I");
publicNativeMethod(cw, "allocateInstance", "(Ljava/lang/Class;)Ljava/lang/Object;");
publicNativeMethod(cw, "allocateMemory", "(J)J");
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "arrayBaseOffset", "(Ljava/lang/Class;)I", null, null);
returnZeroAndEnd(mv);
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "arrayIndexScale", "(Ljava/lang/Class;)I", null, null);
returnZeroAndEnd(mv);
publicNativeMethod(cw, "compareAndSwapInt", "(Ljava/lang/Object;JII)Z");
publicNativeMethod(cw, "compareAndSwapLong", "(Ljava/lang/Object;JJJ)Z");
publicNativeMethod(
cw, "compareAndSwapObject", "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z");
publicNativeMethod(cw, "copyMemory", "(JJJ)V");
publicNativeMethod(cw, "copyMemoryFromPrimitiveArray", "(Ljava/lang/Object;JJJ)V");
publicNativeMethod(cw, "copyMemoryToPrimitiveArray", "(JLjava/lang/Object;JJ)V");
publicNativeMethod(cw, "freeMemory", "(J)V");
publicNativeMethod(cw, "fullFence", "()V");
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"getAndAddInt",
"(Ljava/lang/Object;JI)I",
null,
null);
returnZeroAndEnd(mv);
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"getAndAddLong",
"(Ljava/lang/Object;JJ)J",
null,
null);
returnZeroLongAndEnd(mv);
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"getAndSetInt",
"(Ljava/lang/Object;JI)I",
null,
null);
returnZeroAndEnd(mv);
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"getAndSetLong",
"(Ljava/lang/Object;JJ)J",
null,
null);
returnZeroLongAndEnd(mv);
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
"getAndSetObject",
"(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;",
null,
null);
returnNullAndEnd(mv);
publicNativeMethod(cw, "getBoolean", "(Ljava/lang/Object;J)Z");
publicNativeMethod(cw, "getByte", "(Ljava/lang/Object;J)B");
publicNativeMethod(cw, "getByte", "(J)B");
publicNativeMethod(cw, "getChar", "(Ljava/lang/Object;J)C");
publicNativeMethod(cw, "getChar", "(J)C");
publicNativeMethod(cw, "getDouble", "(Ljava/lang/Object;J)D");
publicNativeMethod(cw, "getDouble", "(J)D");
publicNativeMethod(cw, "getFloat", "(Ljava/lang/Object;J)F");
publicNativeMethod(cw, "getFloat", "(J)F");
publicNativeMethod(cw, "getInt", "(Ljava/lang/Object;J)I");
publicNativeMethod(cw, "getInt", "(J)I");
publicNativeMethod(cw, "getIntVolatile", "(Ljava/lang/Object;J)I");
publicNativeMethod(cw, "getLong", "(Ljava/lang/Object;J)J");
publicNativeMethod(cw, "getLong", "(J)J");
publicNativeMethod(cw, "getLongVolatile", "(Ljava/lang/Object;J)J");
publicNativeMethod(cw, "getObject", "(Ljava/lang/Object;J)Ljava/lang/Object;");
publicNativeMethod(cw, "getObjectVolatile", "(Ljava/lang/Object;J)Ljava/lang/Object;");
publicNativeMethod(cw, "getShort", "(Ljava/lang/Object;J)S");
publicNativeMethod(cw, "getShort", "(J)S");
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"getUnsafe",
"()Lsun/misc/Unsafe;",
null,
null);
returnNullAndEnd(mv);
publicNativeMethod(cw, "loadFence", "()V");
mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC, "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", null, null);
returnZeroLongAndEnd(mv);
publicNativeMethod(cw, "pageSize", "()I");
publicNativeMethod(cw, "park", "(ZJ)V");
publicNativeMethod(cw, "putBoolean", "(Ljava/lang/Object;JZ)V");
publicNativeMethod(cw, "putByte", "(Ljava/lang/Object;JB)V");
publicNativeMethod(cw, "putByte", "(JB)V");
publicNativeMethod(cw, "putChar", "(Ljava/lang/Object;JC)V");
publicNativeMethod(cw, "putChar", "(JC)V");
publicNativeMethod(cw, "putDouble", "(Ljava/lang/Object;JD)V");
publicNativeMethod(cw, "putDouble", "(JD)V");
publicNativeMethod(cw, "putFloat", "(Ljava/lang/Object;JF)V");
publicNativeMethod(cw, "putFloat", "(JF)V");
publicNativeMethod(cw, "putInt", "(Ljava/lang/Object;JI)V");
publicNativeMethod(cw, "putInt", "(JI)V");
publicNativeMethod(cw, "putIntVolatile", "(Ljava/lang/Object;JI)V");
publicNativeMethod(cw, "putLong", "(Ljava/lang/Object;JJ)V");
publicNativeMethod(cw, "putLong", "(JJ)V");
publicNativeMethod(cw, "putLongVolatile", "(Ljava/lang/Object;JJ)V");
publicNativeMethod(cw, "putObject", "(Ljava/lang/Object;JLjava/lang/Object;)V");
publicNativeMethod(cw, "putObjectVolatile", "(Ljava/lang/Object;JLjava/lang/Object;)V");
publicNativeMethod(cw, "putOrderedInt", "(Ljava/lang/Object;JI)V");
publicNativeMethod(cw, "putOrderedLong", "(Ljava/lang/Object;JJ)V");
publicNativeMethod(cw, "putOrderedObject", "(Ljava/lang/Object;JLjava/lang/Object;)V");
publicNativeMethod(cw, "putShort", "(Ljava/lang/Object;JS)V");
publicNativeMethod(cw, "putShort", "(JS)V");
publicNativeMethod(cw, "setMemory", "(JJB)V");
publicNativeMethod(cw, "storeFence", "()V");
publicNativeMethod(cw, "unpark", "(Ljava/lang/Object;)V");
cw.visitEnd();
return cw.toByteArray();
}
private static void returnZeroLongAndEnd(MethodVisitor mv) {
mv.visitCode();
mv.visitInsn(Opcodes.LCONST_0);
mv.visitInsn(Opcodes.LRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void returnZeroAndEnd(MethodVisitor mv) {
mv.visitCode();
mv.visitInsn(Opcodes.ICONST_0);
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void returnNullAndEnd(MethodVisitor mv) {
mv.visitCode();
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static String readableBytecode(byte[] bytes) {
ClassReader reader = new ClassReader(bytes);
StringWriter writer = new StringWriter();
reader.accept(new TraceClassVisitor(new PrintWriter(writer)), 0);
return writer.toString();
}
public static void main(String[] args) {
byte[] bytes = getUnsafeClassBytes();
String encodedClass = Base64.getEncoder().encodeToString(bytes);
// Print readable bytecode.
System.out.println(readableBytecode(bytes));
// Print source code.
System.out.println("java.util.Base64.getDecoder().decode(\"" + encodedClass + "\")");
}
}