blob: b7c09d69d7562a827c0ddb488aaaaa965faa90dd [file] [log] [blame]
// 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;
import com.android.tools.r8.ArchiveProgramResourceProvider;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.timing.Timing;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
public class ReflectiveCallExtractor {
public static Map<DexType, Collection<DexMethod>> extractReflectiveCalls(
Path jar, DexItemFactory factory) throws IOException {
AndroidApp app =
AndroidApp.builder()
.addProgramResourceProvider(ArchiveProgramResourceProvider.fromArchive(jar))
.addLibraryFile(ToolHelper.getAndroidJar(AndroidApiLevel.V))
.build();
InternalOptions options = new InternalOptions(factory, new Reporter());
ApplicationReader applicationReader = new ApplicationReader(app, options, Timing.empty());
LazyLoadedDexApplication dexApplication =
applicationReader.read(ThreadUtils.getExecutorService(options));
AppInfo appInfo =
AppInfo.createInitialAppInfo(dexApplication, GlobalSyntheticsStrategy.forNonSynthesizing());
Map<DexType, Collection<DexMethod>> methods = new IdentityHashMap<>();
for (DexProgramClass programClass : appInfo.classes()) {
for (DexEncodedMethod method : programClass.methods()) {
if (method.hasCode()) {
for (CfInstruction instruction : method.getCode().asCfCode().getInstructions()) {
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
DexMethod theMethod = cfInvoke.getMethod();
DexType holderType = theMethod.getHolderType();
DexClass def = appInfo.definitionFor(holderType);
if (def != null && def.isLibraryClass()) {
if (isReflective(theMethod, factory)) {
methods.computeIfAbsent(holderType, t -> new TreeSet<>()).add(theMethod);
}
}
}
}
}
}
}
return methods;
}
private static boolean isReflective(DexMethod method, DexItemFactory factory) {
if (factory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
return true;
}
if (factory.serviceLoaderMethods.isLoadMethod(method)) {
return true;
}
if (factory.proxyMethods.newProxyInstance.isIdenticalTo(method)) {
return true;
}
DexType type = method.getHolderType();
String name = method.getName().toString();
if (type.isIdenticalTo(factory.classType)) {
if (name.equals("getResource")
|| name.equals("getResourceAsStream")
|| name.equals("getProtectionDomain")
|| name.equals("getClassLoader")
|| name.equals("hashCode")
|| name.equals("equals")
|| name.equals("toString")) {
return false;
}
return true;
}
if (type.isIdenticalTo(factory.unsafeType)) {
// We assume all offset based methods are called using the right method below to compute the
// offset in the object, we do not support cases where programmers would compute the
// offset on their own with their own knowledge of how the vm writes objects in memory.
if (name.equals("fieldOffset")
|| name.equals("staticFieldBase") // This can be called on a field or a class.
|| name.equals("staticFieldOffset")
|| name.equals("objectFieldOffset")
|| name.equals("shouldBeInitialized")
|| name.equals("ensureClassInitialized")
|| name.equals("allocateInstance")) {
return true;
}
}
return false;
}
public static String printMethods(Map<DexType, Collection<DexMethod>> methods) {
StringBuilder sb = new StringBuilder();
List<DexType> types = new ArrayList<>(methods.keySet());
types.sort(Comparator.naturalOrder());
for (DexType type : types) {
Collection<DexMethod> typeMethods = methods.get(type);
if (!typeMethods.isEmpty()) {
sb.append("+++ ").append(type).append(" +++").append("\n");
for (DexMethod dexMethod : typeMethods) {
sb.append(dexMethod).append("\n");
}
}
}
return sb.toString();
}
}