| // 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; |
| |
| import com.android.tools.r8.assistant.ClassInjectionHelper; |
| import com.android.tools.r8.assistant.runtime.EmptyReflectiveOperationReceiver; |
| import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver; |
| import com.android.tools.r8.assistant.runtime.ReflectiveOracle; |
| import com.android.tools.r8.assistant.runtime.ReflectiveOracle.ReflectiveOperationLogger; |
| import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack; |
| import com.android.tools.r8.dex.Marker; |
| import com.android.tools.r8.dex.Marker.Backend; |
| import com.android.tools.r8.dex.Marker.Tool; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.origin.SynthesizedOrigin; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.DumpInputFlags; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.InternalOptions.DesugarState; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import java.util.Collections; |
| |
| /** |
| * This is an experimental API for injecting reflective identification callbacks into dex code. This |
| * API is subject to change. |
| */ |
| @KeepForApi |
| public class R8AssistantCommand extends BaseCompilerCommand { |
| |
| private final String reflectiveReceiverDescriptor; |
| |
| public R8AssistantCommand( |
| AndroidApp app, |
| CompilationMode mode, |
| ProgramConsumer programConsumer, |
| int minApiLevel, |
| Reporter reporter, |
| String reflectiveReceiverDescriptor) { |
| super( |
| app, |
| mode, |
| programConsumer, |
| StringConsumer.emptyConsumer(), |
| minApiLevel, |
| reporter, |
| DesugarState.ON, |
| false, |
| false, |
| (a, b) -> true, |
| Collections.emptyList(), |
| Collections.emptyList(), |
| ThreadUtils.NOT_SPECIFIED, |
| DumpInputFlags.noDump(), |
| null, |
| null, |
| false, |
| Collections.emptyList(), |
| Collections.emptyList(), |
| null, |
| null); |
| this.reflectiveReceiverDescriptor = reflectiveReceiverDescriptor; |
| } |
| |
| public static Builder builder(DiagnosticsHandler reporter) { |
| return new Builder(reporter); |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| @Override |
| InternalOptions getInternalOptions() { |
| DexItemFactory factory = new DexItemFactory(); |
| InternalOptions options = new InternalOptions(factory, getReporter()); |
| options.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(getMinApiLevel())); |
| options.passthroughDexCode = true; |
| options.tool = Tool.R8Assistant; |
| Marker marker = new Marker(Tool.R8Assistant); |
| marker.setBackend(Backend.DEX); |
| marker.setMinApi(getMinApiLevel()); |
| options.setMarker(marker); |
| options.programConsumer = getProgramConsumer(); |
| return options; |
| } |
| |
| public String getReflectiveReceiverDescriptor() { |
| return reflectiveReceiverDescriptor; |
| } |
| |
| /** |
| * This is an experimental API for injecting reflective identification callbacks into dex code. |
| * This API is subject to change. |
| */ |
| @KeepForApi |
| public static class Builder extends BaseCompilerCommand.Builder<R8AssistantCommand, Builder> { |
| |
| private String reflectiveReceiverDescriptor; |
| |
| private Builder() { |
| this(new DiagnosticsHandler() {}); |
| } |
| |
| @Override |
| void validate() { |
| if (!(getProgramConsumer() instanceof DexIndexedConsumer)) { |
| getReporter().error("R8 assistant does not support CF output."); |
| } |
| if (!hasNativeMultidex()) { |
| getReporter().error("R8 assistant requires min api >= 21"); |
| } |
| } |
| |
| public Builder addReflectiveOperationReceiverInput(ProgramResourceProvider provider) { |
| // This code is simply added to the program input, will be called by the ReflectiveOracle. |
| addProgramResourceProvider(provider); |
| return self(); |
| } |
| |
| public Builder setReflectiveReceiverClassDescriptor(String descriptor) { |
| if (!DescriptorUtils.isClassDescriptor(descriptor)) { |
| getReporter().error("Not a valid descriptor " + descriptor); |
| } |
| this.reflectiveReceiverDescriptor = descriptor; |
| return self(); |
| } |
| |
| @Override |
| CompilationMode defaultCompilationMode() { |
| return CompilationMode.RELEASE; |
| } |
| |
| private Builder(DiagnosticsHandler diagnosticsHandler) { |
| super(diagnosticsHandler); |
| } |
| |
| @Override |
| Builder self() { |
| return this; |
| } |
| |
| @Override |
| R8AssistantCommand makeCommand() { |
| injectClasses( |
| EmptyReflectiveOperationReceiver.class, |
| ReflectiveOperationLogger.class, |
| ReflectiveOperationReceiver.NameLookupType.class, |
| ReflectiveOperationReceiver.class, |
| ReflectiveOracle.class, |
| Stack.class); |
| return new R8AssistantCommand( |
| getAppBuilder().build(), |
| getMode(), |
| getProgramConsumer(), |
| getMinApiLevel(), |
| getReporter(), |
| reflectiveReceiverDescriptor); |
| } |
| |
| private void injectClasses(Class<?>... classes) { |
| ClassInjectionHelper injectionHelper = new ClassInjectionHelper(getReporter()); |
| String reason = "Reflective instrumentation"; |
| for (Class<?> clazz : classes) { |
| addClassProgramData( |
| injectionHelper.getClassBytes(clazz), new SynthesizedOrigin(reason, clazz)); |
| } |
| } |
| } |
| } |