| // Copyright (c) 2025, 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.assistant.runtime; |
| |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.ATOMIC_FIELD_UPDATER_NEW_UPDATER; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_AS_SUBCLASS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_CAST; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_FLAG; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_FOR_NAME; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_COMPONENT_TYPE; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_CONSTRUCTOR; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_CONSTRUCTORS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_CONSTRUCTOR; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_CONSTRUCTORS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_FIELD; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_FIELDS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_METHOD; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_DECLARED_METHODS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_FIELD; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_FIELDS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_METHOD; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_METHODS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_NAME; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_PACKAGE; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_GET_SUPERCLASS; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_IS_ASSIGNABLE_FROM; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_IS_INSTANCE; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.CLASS_NEW_INSTANCE; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.PROXY_NEW_PROXY_INSTANCE; |
| import static com.android.tools.r8.assistant.runtime.ReflectiveEventType.SERVICE_LOADER_LOAD; |
| |
| import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| |
| // This logs the information in JSON-like format, |
| // manually encoded to avoid loading gson on the mobile. |
| @KeepForApi |
| public class ReflectiveOperationJsonLogger implements ReflectiveOperationReceiver { |
| |
| private final FileWriter output; |
| |
| // TODO(b/486090382): Consider injecting the app id as part of instrumentation. |
| public String getApplicationId() { |
| try { |
| Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); |
| Method currentAppMethod = activityThreadClass.getDeclaredMethod("currentApplication"); |
| currentAppMethod.setAccessible(true); |
| Object applicationContext = currentAppMethod.invoke(null); |
| if (applicationContext == null) { |
| return null; |
| } |
| Method getPackageNameMethod = applicationContext.getClass().getMethod("getPackageName"); |
| Object packageName = getPackageNameMethod.invoke(applicationContext); |
| return (String) packageName; |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| |
| public ReflectiveOperationJsonLogger() throws IOException { |
| String outputFileName = System.getProperty("com.android.tools.r8.reflectiveJsonLogger"); |
| File file; |
| if (outputFileName == null) { |
| String tmpDir = "/sdcard/Android/media/" + getApplicationId() + "/additional_test_output"; |
| file = new File(tmpDir, "reflection_log.json"); |
| } else { |
| file = new File(outputFileName); |
| } |
| this.output = new FileWriter(file); |
| output.write("["); |
| Runtime.getRuntime().addShutdownHook(new Thread(this::onShutdown)); |
| } |
| |
| private void onShutdown() { |
| try { |
| finished(); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| public void finished() throws IOException { |
| output.write("]"); |
| output.close(); |
| } |
| |
| private String[] methodToString( |
| Class<?> returnType, Class<?> holder, String method, Class<?>... parameters) { |
| String[] methodStrings = new String[parameters.length + 3]; |
| methodStrings[0] = printClass(returnType); |
| methodStrings[1] = printClass(holder); |
| methodStrings[2] = method; |
| for (int i = 0; i < parameters.length; i++) { |
| methodStrings[i + 3] = printClass(parameters[i]); |
| } |
| return methodStrings; |
| } |
| |
| private String[] constructorToString(Class<?> holder, Class<?>... parameters) { |
| return methodToString(Void.TYPE, holder, "<init>", parameters); |
| } |
| |
| private String printClass(Class<?> clazz) { |
| return clazz == null ? "null" : clazz.getName(); |
| } |
| |
| private String printClassLoader(ClassLoader classLoader) { |
| return classLoader == null ? "null" : printClass(classLoader.getClass()); |
| } |
| |
| private boolean isIgnored(Class<?> clazz) { |
| return clazz != null && isIgnored(clazz.getName()); |
| } |
| |
| private boolean isIgnored(String name) { |
| return name != null |
| && (name.startsWith("java.") |
| || name.startsWith("android.") |
| || name.startsWith("androidx.") |
| || name.startsWith("kotlin.") |
| || name.startsWith("kotlinx.")); |
| } |
| |
| private synchronized void output(ReflectiveEventType event, Stack stack, String... args) { |
| try { |
| output.write("{\"event\": \""); |
| output.write(event.name()); |
| output.write("\""); |
| if (stack != null) { |
| output.write(", \"stack\": "); |
| printArray(stack.stackTraceElementsAsString()); |
| } |
| assert args != null; |
| output.write(", \"args\": "); |
| printArray(args); |
| output.write("},\n"); |
| output.flush(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private void printArray(String... args) throws IOException { |
| output.write("["); |
| for (int i = 0; i < args.length; i++) { |
| output.write("\""); |
| output.write(args[i]); |
| output.write("\""); |
| if (i != args.length - 1) { |
| output.write(", "); |
| } |
| } |
| output.write("]"); |
| } |
| |
| @Override |
| public void onClassNewInstance(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_NEW_INSTANCE, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetDeclaredMethod( |
| Stack stack, Class<?> returnType, Class<?> clazz, String method, Class<?>... parameters) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_METHOD, stack, methodToString(returnType, clazz, method, parameters)); |
| } |
| |
| @Override |
| public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_METHODS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetDeclaredField( |
| Stack stack, Class<?> fieldType, Class<?> clazz, String fieldName) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_FIELD, stack, printClass(fieldType), printClass(clazz), fieldName); |
| } |
| |
| @Override |
| public void onClassGetDeclaredFields(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_FIELDS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetDeclaredConstructor(Stack stack, Class<?> clazz, Class<?>... parameters) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_CONSTRUCTOR, stack, constructorToString(clazz, parameters)); |
| } |
| |
| @Override |
| public void onClassGetDeclaredConstructors(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_DECLARED_CONSTRUCTORS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetMethod( |
| Stack stack, Class<?> returnType, Class<?> clazz, String method, Class<?>... parameters) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_METHOD, stack, methodToString(returnType, clazz, method, parameters)); |
| } |
| |
| @Override |
| public void onClassGetMethods(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_METHODS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetField(Stack stack, Class<?> fieldType, Class<?> clazz, String fieldName) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_FIELD, stack, printClass(fieldType), printClass(clazz), fieldName); |
| } |
| |
| @Override |
| public void onClassGetFields(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_FIELDS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetConstructor(Stack stack, Class<?> clazz, Class<?>... parameters) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_CONSTRUCTOR, stack, constructorToString(clazz, parameters)); |
| } |
| |
| @Override |
| public void onClassGetConstructors(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_CONSTRUCTORS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_NAME, stack, printClass(clazz), lookupType.name()); |
| } |
| |
| @Override |
| public void onClassForName( |
| Stack stack, String className, boolean initialize, ClassLoader classLoader) { |
| if (isIgnored(className)) { |
| return; |
| } |
| output( |
| CLASS_FOR_NAME, |
| stack, |
| className, |
| Boolean.toString(initialize), |
| printClassLoader(classLoader)); |
| } |
| |
| @Override |
| public void onClassGetComponentType(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_COMPONENT_TYPE, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassGetPackage(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_PACKAGE, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassIsAssignableFrom(Stack stack, Class<?> clazz, Class<?> sup) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_IS_ASSIGNABLE_FROM, stack, printClass(clazz), printClass(sup)); |
| } |
| |
| @Override |
| public void onClassGetSuperclass(Stack stack, Class<?> clazz) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_GET_SUPERCLASS, stack, printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassAsSubclass(Stack stack, Class<?> holder, Class<?> clazz) { |
| if (isIgnored(holder)) { |
| return; |
| } |
| output(CLASS_AS_SUBCLASS, stack, printClass(holder), printClass(clazz)); |
| } |
| |
| @Override |
| public void onClassIsInstance(Stack stack, Class<?> holder, Object object) { |
| if (isIgnored(holder)) { |
| return; |
| } |
| output( |
| CLASS_IS_INSTANCE, |
| stack, |
| printClass(holder), |
| printClass(object != null ? object.getClass() : null)); |
| } |
| |
| @Override |
| public void onClassCast(Stack stack, Class<?> holder, Object object) { |
| if (isIgnored(holder)) { |
| return; |
| } |
| output(CLASS_CAST, stack, printClass(holder), printClass(object.getClass())); |
| } |
| |
| @Override |
| public void onClassFlag(Stack stack, Class<?> clazz, ClassFlag classFlag) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(CLASS_FLAG, stack, printClass(clazz), classFlag.name()); |
| } |
| |
| @Override |
| public void onAtomicFieldUpdaterNewUpdater( |
| Stack stack, Class<?> fieldClass, Class<?> clazz, String name) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output( |
| ATOMIC_FIELD_UPDATER_NEW_UPDATER, stack, printClass(fieldClass), printClass(clazz), name); |
| } |
| |
| @Override |
| public void onServiceLoaderLoad(Stack stack, Class<?> clazz, ClassLoader classLoader) { |
| if (isIgnored(clazz)) { |
| return; |
| } |
| output(SERVICE_LOADER_LOAD, stack, printClass(clazz), printClassLoader(classLoader)); |
| } |
| |
| @Override |
| public void onProxyNewProxyInstance( |
| Stack stack, |
| ClassLoader classLoader, |
| Class<?>[] interfaces, |
| InvocationHandler invocationHandler) { |
| boolean allIgnored = true; |
| for (Class<?> itf : interfaces) { |
| if (!isIgnored(itf)) { |
| allIgnored = false; |
| break; |
| } |
| } |
| if (allIgnored) { |
| return; |
| } |
| String[] methodStrings = new String[interfaces.length + 2]; |
| methodStrings[0] = printClassLoader(classLoader); |
| methodStrings[1] = invocationHandler.toString(); |
| for (int i = 0; i < interfaces.length; i++) { |
| methodStrings[i + 2] = printClass(interfaces[i]); |
| } |
| output(PROXY_NEW_PROXY_INSTANCE, stack, methodStrings); |
| } |
| |
| public boolean requiresStackInformation() { |
| return true; |
| } |
| } |