| // Copyright (c) 2019, 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.ir.optimize; |
| |
| import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; |
| import com.android.tools.r8.ir.code.ArrayPut; |
| import com.android.tools.r8.ir.code.ConstClass; |
| import com.android.tools.r8.ir.code.ConstNumber; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionIterator; |
| import com.android.tools.r8.ir.code.InvokeDirect; |
| import com.android.tools.r8.ir.code.InvokeInterface; |
| import com.android.tools.r8.ir.code.InvokeStatic; |
| import com.android.tools.r8.ir.code.InvokeVirtual; |
| import com.android.tools.r8.ir.code.MemberType; |
| import com.android.tools.r8.ir.code.NewArrayEmpty; |
| import com.android.tools.r8.ir.code.NewInstance; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.google.common.collect.ImmutableList; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class, |
| * X.class.getClassLoader()).iterator() ... to Arrays.asList(new X[] { new Y(), ..., new Z() |
| * }).iterator() for classes Y..Z specified in the META-INF/services/X. |
| * |
| * <p>The reason for this optimization is to not have the ServiceLoader.load on the distributed R8 |
| * in AGP, since this can potentially conflict with debug versions added to a build.gradle file as: |
| * classpath 'com.android.tools:r8:a.b.c' Additionally, it might also result in improved performance |
| * because ServiceLoader.load is really slow on Android because it has to do a reflective lookup. |
| * |
| * <p>A call to ServiceLoader.load(X.class) is implicitly the same as ServiceLoader.load(X.class, |
| * Thread.getContextClassLoader()) which can have different behavior in Android if a process host |
| * multiple applications: |
| * |
| * <pre> |
| * See <a href="https://stackoverflow.com/questions/13407006/android-class-loader-may-fail-for- |
| * processes-that-host-multiple-applications">https://stackoverflow.com/questions/13407006/ |
| * android-class-loader-may-fail-for-processes-that-host-multiple-applications</a> |
| * </pre> |
| * |
| * We therefore only conservatively rewrite if the invoke is on is on the form |
| * ServiceLoader.load(X.class, X.class.getClassLoader()) or ServiceLoader.load(X.class, null). |
| * |
| * Android Nougat do not use ClassLoader.getSystemClassLoader() when passing null and will |
| * almost certainly fail when trying to find the service. It seems unlikely that programs rely on |
| * this behaviour. |
| */ |
| public class ServiceLoaderRewriter { |
| |
| public static void rewrite(IRCode code, AppView<? extends AppInfoWithLiveness> appView) { |
| DexItemFactory factory = appView.dexItemFactory(); |
| InstructionIterator instructionIterator = code.instructionIterator(); |
| while (instructionIterator.hasNext()) { |
| Instruction instruction = instructionIterator.next(); |
| |
| // Check if instruction is an invoke static on the desired form of ServiceLoader.load. |
| if (!instruction.isInvokeStatic() |
| || instruction.asInvokeStatic().getInvokedMethod() |
| != factory.serviceLoaderMethods.loadWithClassLoader) { |
| continue; |
| } |
| |
| InvokeStatic serviceLoaderLoad = instruction.asInvokeStatic(); |
| Value serviceLoaderLoadOut = serviceLoaderLoad.outValue(); |
| if (serviceLoaderLoadOut.numberOfAllUsers() != 1 |
| || serviceLoaderLoadOut.numberOfPhiUsers() != 0) { |
| continue; |
| } |
| |
| // Check that the only user is a call to iterator(). |
| if (!serviceLoaderLoadOut.singleUniqueUser().isInvokeVirtual() |
| || serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual().getInvokedMethod() |
| != factory.serviceLoaderMethods.iterator) { |
| continue; |
| } |
| |
| // Check that the first argument is a const class. |
| Value argument = serviceLoaderLoad.inValues().get(0).getAliasedValue(); |
| if (argument.isPhi() || !argument.definition.isConstClass()) { |
| continue; |
| } |
| |
| ConstClass constClass = |
| serviceLoaderLoad |
| .inValues() |
| .get(0) |
| .getAliasedValue() |
| .getConstInstruction() |
| .asConstClass(); |
| |
| // Check that the service is not kept. |
| if (appView.appInfo().isPinned(constClass.getValue())) { |
| continue; |
| } |
| |
| // Check that the service is configured in the META-INF/services. |
| if (!appView.appServices().allServiceTypes().contains(constClass.getValue())) { |
| // Error already reported in the Enqueuer. |
| continue; |
| } |
| |
| // Check that ClassLoader used is the ClassLoader defined for the the service configuration |
| // that we are instantiating or NULL. |
| InvokeVirtual classLoaderInvoke = |
| serviceLoaderLoad.inValues().get(1).definition.asInvokeVirtual(); |
| boolean isGetClassLoaderOnConstClassOrNull = |
| serviceLoaderLoad.inValues().get(1).getTypeLattice().isNullType() |
| || (classLoaderInvoke != null |
| && classLoaderInvoke.inValues().size() == 1 |
| && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass() |
| && classLoaderInvoke |
| .getReceiver() |
| .getAliasedValue() |
| .getConstInstruction() |
| .asConstClass() |
| .getValue() |
| == constClass.getValue()); |
| if (!isGetClassLoaderOnConstClassOrNull) { |
| continue; |
| } |
| |
| List<DexType> dexTypes = |
| appView.appServices().serviceImplementationsFor(constClass.getValue()); |
| List<DexClass> classes = new ArrayList<>(dexTypes.size()); |
| boolean seenNull = false; |
| for (DexType serviceImpl : dexTypes) { |
| DexClass serviceImplClazz = appView.definitionFor(serviceImpl); |
| if (serviceImplClazz == null) { |
| seenNull = true; |
| } |
| classes.add(serviceImplClazz); |
| } |
| |
| if (seenNull) { |
| continue; |
| } |
| |
| // We can perform the rewrite of the ServiceLoader.load call. |
| new Rewriter(appView, code, instructionIterator, serviceLoaderLoad) |
| .perform(classLoaderInvoke, constClass.getValue(), classes); |
| } |
| } |
| |
| /** |
| * Rewriter will look assume that code is on the form: |
| * |
| * <pre> |
| * ConstClass v1 <- X |
| * ConstClass v2 <- X |
| * Invoke-Virtual v3 <- v2; method: java.lang.ClassLoader java.lang.Class.getClassLoader() |
| * Invoke-Static v4 <- v1, v3; method: java.util.ServiceLoader java.util.ServiceLoader |
| * .load(java.lang.Class, java.lang.ClassLoader) |
| * Invoke-Virtual v5 <- v4; method: java.util.Iterator java.util.ServiceLoader.iterator() |
| * </pre> |
| * |
| * and rewrites it to for classes impl(X) defined in META-INF/services/X: |
| * |
| * <pre> |
| * ConstClass v1 <- X |
| * ConstClass v2 <- X |
| * ConstNumber va <- impl(X).size() (INT) |
| * NewArrayEmpty vb <- va X[] |
| * for i = 0 to C - 1: |
| * ConstNumber vc(i) <- i (INT) |
| * NewInstance vd <- impl(X).get(i) |
| * Invoke-Direct vd; method: void impl(X).get(i).<init>() |
| * ArrayPut vb, vc(i), vd |
| * end for |
| * Invoke-Static ve <- vb; method: java.util.List java.util.Arrays.asList(java.lang.Object[]) |
| * Invoke-Interface v5 <- ve; method: java.util.Iterator java.util.List.iterator() |
| * </pre> |
| * |
| * We rely on the DeadCodeRemover to remove the ConstClasses and any aliased values no longer |
| * used. |
| */ |
| private static class Rewriter { |
| |
| private final AppView appView; |
| private final DexItemFactory factory; |
| private final IRCode code; |
| private final InvokeStatic serviceLoaderLoad; |
| |
| private InstructionIterator iterator; |
| private MemberType memberType; |
| private Value valueArray; |
| private int index = 0; |
| |
| public Rewriter( |
| AppView appView, |
| IRCode code, |
| InstructionIterator iterator, |
| InvokeStatic serviceLoaderLoad) { |
| this.appView = appView; |
| this.factory = appView.dexItemFactory(); |
| this.iterator = iterator; |
| this.code = code; |
| this.serviceLoaderLoad = serviceLoaderLoad; |
| } |
| |
| public void perform(InvokeVirtual classLoaderInvoke, DexType dexType, List<DexClass> classes) { |
| assert valueArray == null; |
| assert memberType == null; |
| |
| // Remove the ClassLoader call since this can throw and will not be removed otherwise. |
| if (classLoaderInvoke != null) { |
| clearGetClassLoader(classLoaderInvoke); |
| iterator.nextUntil(i -> i == serviceLoaderLoad); |
| } |
| |
| // Remove the ServiceLoader.load call. |
| InvokeVirtual serviceLoaderIterator = |
| serviceLoaderLoad.outValue().singleUniqueUser().asInvokeVirtual(); |
| iterator.replaceCurrentInstruction(code.createConstNull()); |
| |
| // Build the array for the "loaded" classes. |
| ConstNumber arrayLength = code.createIntConstant(classes.size()); |
| arrayLength.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(arrayLength); |
| |
| DexType arrayType = factory.createArrayType(1, dexType); |
| TypeLatticeElement arrayLatticeElement = |
| TypeLatticeElement.fromDexType(arrayType, definitelyNotNull(), appView); |
| valueArray = code.createValue(arrayLatticeElement); |
| NewArrayEmpty newArrayEmpty = |
| new NewArrayEmpty(valueArray, arrayLength.outValue(), arrayType); |
| newArrayEmpty.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(newArrayEmpty); |
| |
| this.memberType = MemberType.fromDexType(dexType); |
| |
| // Add all new instances to the array. |
| classes.forEach(this::addNewServiceAndPutInArray); |
| |
| // Build the Arrays.asList(...) instruction. |
| Value vArrayAsList = |
| code.createValue( |
| TypeLatticeElement.fromDexType(factory.listType, definitelyNotNull(), appView)); |
| InvokeStatic arraysAsList = |
| new InvokeStatic( |
| factory.utilArraysMethods.asList, vArrayAsList, ImmutableList.of(valueArray)); |
| arraysAsList.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(arraysAsList); |
| |
| // Find the iterator instruction and replace it. |
| iterator.nextUntil(x -> x == serviceLoaderIterator); |
| |
| DexMethod method = |
| factory.createMethod( |
| factory.listType, factory.createProto(factory.iteratorType), "iterator"); |
| InvokeInterface arrayIterator = |
| new InvokeInterface( |
| method, serviceLoaderIterator.outValue(), ImmutableList.of(vArrayAsList)); |
| iterator.replaceCurrentInstruction(arrayIterator); |
| } |
| |
| private void addNewServiceAndPutInArray(DexClass clazz) { |
| ConstNumber indexInArray = code.createIntConstant(index++); |
| indexInArray.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(indexInArray); |
| |
| TypeLatticeElement clazzLatticeElement = |
| TypeLatticeElement.fromDexType(clazz.type, definitelyNotNull(), appView); |
| Value vInstance = code.createValue(clazzLatticeElement); |
| NewInstance newInstance = new NewInstance(clazz.type, vInstance); |
| newInstance.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(newInstance); |
| |
| DexMethod method = clazz.getDefaultInitializer().method; |
| assert method.getArity() == 0; |
| InvokeDirect invokeDirect = |
| new InvokeDirect(method, null, Collections.singletonList(vInstance)); |
| invokeDirect.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(invokeDirect); |
| |
| ArrayPut put = new ArrayPut(memberType, valueArray, indexInArray.outValue(), vInstance); |
| put.setPosition(serviceLoaderLoad.getPosition()); |
| iterator.add(put); |
| } |
| |
| private void clearGetClassLoader(InvokeVirtual classLoaderInvoke) { |
| while (iterator.hasPrevious()) { |
| Instruction instruction = iterator.previous(); |
| if (instruction == classLoaderInvoke) { |
| iterator.replaceCurrentInstruction(code.createConstNull()); |
| break; |
| } |
| } |
| } |
| } |
| } |