blob: badc7ea2c03d165ff3976afa6718fcbf42f73cbf [file] [log] [blame]
// 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 com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory.IteratorMethods;
import com.android.tools.r8.graph.DexItemFactory.ServiceLoaderMethods;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRCodeInstructionListIterator;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DominatorChecker;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.WorkList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* 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).
*
* <p>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 extends CodeRewriterPass<AppInfoWithLiveness> {
private final AndroidApiLevelCompute apiLevelCompute;
private final ServiceLoaderMethods serviceLoaderMethods;
private final IteratorMethods iteratorMethods;
private final boolean hasAssumeNoSideEffects;
public ServiceLoaderRewriter(AppView<?> appView) {
super(appView);
this.apiLevelCompute = appView.apiLevelCompute();
this.serviceLoaderMethods = appView.dexItemFactory().serviceLoaderMethods;
this.iteratorMethods = appView.dexItemFactory().iteratorMethods;
hasAssumeNoSideEffects =
appView.getAssumeInfoCollection().isSideEffectFree(serviceLoaderMethods.load);
}
@Override
protected String getRewriterId() {
return "ServiceLoaderRewriter";
}
@Override
protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return appView.hasLiveness()
&& methodProcessor.isPrimaryMethodProcessor()
&& options.enableServiceLoaderRewriting
&& code.metadata().mayHaveConstClass()
&& code.metadata().mayHaveInvokeStatic()
&& code.metadata().mayHaveInvokeVirtual();
}
private boolean shouldReportWhyAreYouNotInliningServiceLoaderLoad() {
AppInfoWithLiveness appInfo = appView().appInfo();
return appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.load)
|| appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.loadWithClassLoader);
}
@Override
protected CodeRewriterResult rewriteCode(
IRCode code,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
// Create a map from service type to loader methods local to this context since two
// service loader calls to the same type in different methods and in the same wave can race.
Map<DexType, DexEncodedMethod> synthesizedServiceLoaders = new IdentityHashMap<>();
IRCodeInstructionListIterator iterator = code.instructionListIterator();
Map<Instruction, Instruction> replacements = new HashMap<>();
Map<Instruction, List<Instruction>> replacementExtras = new HashMap<>();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
// Find ServiceLoader.load() calls.
InvokeStatic invokeInstr = instruction.asInvokeStatic();
if (invokeInstr == null
|| !serviceLoaderMethods.isLoadMethod(invokeInstr.getInvokedMethod())) {
continue;
}
// See if it can be optimized.
ServiceLoaderLoadResult loadResult = analyzeServiceLoaderLoad(code, invokeInstr);
if (loadResult == null) {
continue;
}
// See if there is a subsequent iterator() call that can be optimized.
DirectRewriteResult directRewriteResult = analyzeForDirectRewrite(loadResult);
// Remove ServiceLoader.load()
replacements.put(loadResult.loadInvoke, code.createConstNull());
// Remove getClassLoader() if we are about to remove all users.
if (loadResult.classLoaderInvoke != null
&& loadResult.classLoaderInvoke.outValue().aliasedUsers().stream()
.allMatch(ins -> ins.isAssume() || replacements.containsKey(ins))) {
replacements.put(loadResult.classLoaderInvoke, code.createConstNull());
}
if (directRewriteResult != null) {
populateDirectRewriteChanges(
code, replacements, replacementExtras, loadResult, directRewriteResult);
} else {
populateSyntheticChanges(
code,
methodProcessor,
methodProcessingContext,
synthesizedServiceLoaders,
replacements,
loadResult);
}
}
if (replacements.isEmpty()) {
return CodeRewriterResult.NO_CHANGE;
}
AffectedValues affectedValues = new AffectedValues();
iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
Instruction replacement = replacements.get(instruction);
if (replacement == null) {
continue;
}
iterator.replaceCurrentInstruction(replacement, affectedValues);
List<Instruction> extras = replacementExtras.get(instruction);
if (extras != null) {
iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(extras, options);
if (ListUtils.last(extras).isThrow()) {
iterator.removeRemainingInBlockIgnoreOutValue();
}
}
}
code.removeUnreachableBlocks(affectedValues, ConsumerUtils.emptyConsumer());
code.removeRedundantBlocks();
affectedValues.narrowingWithAssumeRemoval(appView, code);
assert code.isConsistentSSA(appView);
return CodeRewriterResult.HAS_CHANGED;
}
private void populateSyntheticChanges(
IRCode code,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext,
Map<DexType, DexEncodedMethod> synthesizedServiceLoaders,
Map<Instruction, Instruction> replacements,
ServiceLoaderLoadResult loadResult) {
DexEncodedMethod synthesizedMethod =
synthesizedServiceLoaders.computeIfAbsent(
loadResult.serviceType,
service -> {
DexEncodedMethod addedMethod =
createSynthesizedMethod(
service, loadResult.implClasses, methodProcessor, methodProcessingContext);
if (appView.options().isGeneratingClassFiles()) {
addedMethod.upgradeClassFileVersion(
code.context().getDefinition().getClassFileVersion());
}
return addedMethod;
});
InvokeStatic synthesizedInvoke =
new InvokeStatic(
synthesizedMethod.getReference(),
loadResult.iteratorInvoke.outValue(),
Collections.emptyList());
replacements.put(loadResult.iteratorInvoke, synthesizedInvoke);
}
private void populateDirectRewriteChanges(
IRCode code,
Map<Instruction, Instruction> replacements,
Map<Instruction, List<Instruction>> replacementExtras,
ServiceLoaderLoadResult loadResult,
DirectRewriteResult directRewriteResult) {
// Remove ServiceLoader.iterator()
replacements.put(loadResult.iteratorInvoke, code.createConstNull());
// Iterator.hasNext() --> true / false
if (directRewriteResult.priorHasNextInstr != null) {
replacements.put(
directRewriteResult.priorHasNextInstr,
code.createBooleanConstant(!loadResult.implClasses.isEmpty()));
}
if (directRewriteResult.nextInstr != null) {
Position position = directRewriteResult.nextInstr.getPosition();
if (loadResult.implClasses.isEmpty()) {
// Iterator.next() -> null
replacements.put(directRewriteResult.nextInstr, code.createConstNull());
// throw new NoSuchElementException()
NewInstance newInstanceInstr =
new NewInstance(
dexItemFactory.noSuchElementExceptionType,
code.createValue(
dexItemFactory.noSuchElementExceptionType.toNonNullTypeElement(appView)));
InvokeDirect initInstr =
new InvokeDirect(
dexItemFactory.noSuchElementExceptionInit,
null,
List.of(newInstanceInstr.outValue()));
Throw throwInstr = new Throw(newInstanceInstr.outValue());
newInstanceInstr.setPosition(position);
initInstr.setPosition(position);
throwInstr.setPosition(position);
replacementExtras.put(
directRewriteResult.nextInstr, List.of(newInstanceInstr, initInstr, throwInstr));
} else {
// Iterator.next() -> new ServiceImpl()
DexType clazz = loadResult.implClasses.get(0).getType();
NewInstance newInstance =
new NewInstance(
clazz,
code.createValue(
clazz.toNonNullTypeElement(appView),
directRewriteResult.nextInstr.getLocalInfo()));
replacements.put(directRewriteResult.nextInstr, newInstance);
InvokeDirect initInstr =
new InvokeDirect(
dexItemFactory.createInstanceInitializer(clazz),
null,
List.of(newInstance.outValue()));
initInstr.setPosition(position);
replacementExtras.put(directRewriteResult.nextInstr, List.of(initInstr));
}
}
if (directRewriteResult.subsequentHasNextInstr != null) {
replacements.put(
directRewriteResult.subsequentHasNextInstr,
code.createBooleanConstant(loadResult.implClasses.size() > 1));
}
}
private ServiceLoaderLoadResult analyzeServiceLoaderLoad(IRCode code, InvokeStatic invokeInstr) {
// Check that the first argument is a const class.
Value argument = invokeInstr.getFirstArgument().getAliasedValue();
if (!argument.isDefinedByInstructionSatisfying(Instruction::isConstClass)) {
report(code.context(), null, "The service loader type could not be determined");
return null;
}
ConstClass constClass = argument.getDefinition().asConstClass();
DexType serviceType = constClass.getType();
if (invokeInstr.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.loadWithClassLoader)) {
report(
code.context(),
serviceType,
"Inlining is only supported for `java.util.ServiceLoader.load(java.lang.Class,"
+ " java.lang.ClassLoader)`");
return null;
}
String invalidUserMessage =
"The returned ServiceLoader instance must only be used in a call to `java.util.Iterator"
+ " java.lang.ServiceLoader.iterator()`";
Value serviceLoaderLoadOut = invokeInstr.outValue();
if (!serviceLoaderLoadOut.hasSingleUniqueUser() || serviceLoaderLoadOut.hasPhiUsers()) {
report(code.context(), serviceType, invalidUserMessage);
return null;
}
// Check that the only user is a call to iterator().
InvokeVirtual iteratorInvoke = serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual();
if (iteratorInvoke == null
|| iteratorInvoke.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.iterator)) {
report(code.context(), serviceType, invalidUserMessage + ", but found other usages");
return null;
}
// Check that the service is not kept.
if (!options.allowServiceLoaderRewritingPinnedTypes
&& appView().appInfo().isPinnedWithDefinitionLookup(serviceType)) {
report(code.context(), serviceType, "The service loader type is kept");
return null;
}
// Package-private service types are allowed only when the load() call is made from the same
// package. Not worth modelling.
DexClass serviceTypeResolved = appView.definitionFor(serviceType);
if (serviceTypeResolved == null) {
report(code.context(), serviceType, "Service type could not be resolved");
return null;
}
if (!serviceTypeResolved.isPublic()) {
report(code.context(), serviceType, "Service type must be public");
return null;
}
// Check that ClassLoader used is the ClassLoader defined for the service configuration
// that we are instantiating or NULL.
Value classLoaderValue = invokeInstr.getLastArgument().getAliasedValue();
if (classLoaderValue.isPhi()) {
report(
code.context(),
serviceType,
"The java.lang.ClassLoader argument must be defined locally as null or "
+ serviceType
+ ".class.getClassLoader()");
return null;
}
boolean isNullClassLoader = classLoaderValue.getType().isNullType();
InvokeVirtual classLoaderInvoke = classLoaderValue.getDefinition().asInvokeVirtual();
boolean isGetClassLoaderOnConstClass =
classLoaderInvoke != null
&& classLoaderInvoke.arguments().size() == 1
&& classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
&& classLoaderInvoke
.getReceiver()
.getAliasedValue()
.getDefinition()
.asConstClass()
.getType()
.isIdenticalTo(serviceType);
if (!isNullClassLoader && !isGetClassLoaderOnConstClass) {
report(
code.context(),
serviceType,
"The java.lang.ClassLoader argument must be defined locally as null or "
+ serviceType
+ ".class.getClassLoader()");
return null;
}
// Check that we are not service loading anything from a feature into base.
AppServices appServices = appView.appServices();
// Check that we are not service loading anything from a feature into base.
if (hasServiceImplementationInDifferentFeature(code, serviceType, isNullClassLoader)) {
return null;
}
List<DexType> dexTypes = appServices.serviceImplementationsFor(serviceType);
List<DexClass> implClasses = new ArrayList<>(dexTypes.size());
for (DexType serviceImpl : dexTypes) {
DexClass serviceImplementation = appView.definitionFor(serviceImpl);
if (serviceImplementation == null) {
report(
code.context(),
serviceType,
"Unable to find definition for service implementation " + serviceImpl.getTypeName());
return null;
}
if (appView.isSubtype(serviceImpl, serviceType).isFalse()) {
report(
code.context(),
serviceType,
"Implementation is not a subtype of the service: " + serviceImpl.getTypeName());
return null;
}
DexEncodedMethod method = serviceImplementation.getDefaultInitializer();
if (method == null) {
report(
code.context(),
serviceType,
"Implementation has no default constructor: " + serviceImpl.getTypeName());
return null;
}
if (!method.getAccessFlags().wasPublic()) {
// A non-public constructor causes a ServiceConfigurationError on APIs 24 & 25 (Nougat).
report(
code.context(),
serviceType,
"Implementation's default constructor is not public: " + serviceImpl.getTypeName());
return null;
}
implClasses.add(serviceImplementation);
}
return new ServiceLoaderLoadResult(
invokeInstr, classLoaderInvoke, serviceType, implClasses, iteratorInvoke);
}
/**
* Checks that:
*
* <pre>
* * -assumenosideeffects for ServiceLoader.load() is set.
* * ServiceLoader.iterator() is used only by a single .hasNext() and/or .next()
* * .iterator(), .hasNext(), and .next() are all in the same try/catch.
* * .hasNext() never comes after a call to .next()
* * .hasNext() and .next() are not in a loop.
* </pre>
*/
private DirectRewriteResult analyzeForDirectRewrite(ServiceLoaderLoadResult loadResult) {
// Require -assumenosideeffects class java.util.ServiceLoader { java.lang.Object load(...); }
// because this direct rewriting does not wrap exceptions in ServiceConfigurationError.
if (!hasAssumeNoSideEffects) {
return null;
}
InvokeVirtual iteratorInvoke = loadResult.iteratorInvoke;
if (iteratorInvoke.outValue().hasPhiUsers()) {
return null;
}
InvokeMethod priorHasNextInstr = null;
InvokeMethod nextInstr = null;
InvokeMethod subsequentHasNextInstr = null;
// We only bother to support a single call to hasNext() and next(), and they must appear within
// the same try/catch.
for (Instruction user : iteratorInvoke.outValue().aliasedUsers()) {
if (user.isAssume()) {
if (user.outValue().hasPhiUsers()) {
return null;
}
continue;
}
if (!user.getBlock().hasEquivalentCatchHandlers(iteratorInvoke.getBlock())) {
return null;
}
InvokeMethod curCall = user.asInvokeMethod();
if (curCall == null) {
return null;
}
if (curCall.getInvokedMethod().isIdenticalTo(iteratorMethods.hasNext)) {
if (subsequentHasNextInstr != null) {
return null;
}
if (priorHasNextInstr != null) {
subsequentHasNextInstr = curCall;
} else {
priorHasNextInstr = curCall;
}
} else if (curCall.getInvokedMethod().isIdenticalTo(iteratorMethods.next)) {
if (nextInstr != null) {
return null;
}
nextInstr = curCall;
} else {
return null;
}
}
// Figure out which hasNext is the first one.
BasicBlock iteratorBlock = iteratorInvoke.getBlock();
BasicBlock priorHasNextBlock = priorHasNextInstr != null ? priorHasNextInstr.getBlock() : null;
BasicBlock subsequentHasNextBlock =
subsequentHasNextInstr != null ? subsequentHasNextInstr.getBlock() : null;
BasicBlock nextBlock = nextInstr != null ? nextInstr.getBlock() : null;
if (priorHasNextBlock != null && nextBlock != null) {
if (subsequentHasNextBlock != null) {
if (priorHasNextBlock == subsequentHasNextBlock) {
// This would be odd, since hasNext() is usually used in conditionals.
return null;
}
// Make sure subsequentHasNextBlock and priorHasNextBlock are not reversed.
if (hasPredecessorPathTo(iteratorBlock, priorHasNextBlock, subsequentHasNextBlock)) {
InvokeMethod tmp = priorHasNextInstr;
priorHasNextInstr = subsequentHasNextInstr;
subsequentHasNextInstr = tmp;
priorHasNextBlock = subsequentHasNextBlock;
subsequentHasNextBlock = subsequentHasNextInstr.getBlock();
}
// Ensure the second hasNext() comes after the call to next().
if (nextBlock == subsequentHasNextBlock) {
if (nextBlock.iterator(nextInstr).nextUntil(subsequentHasNextInstr) == null) {
return null;
}
} else if (!DominatorChecker.check(iteratorBlock, subsequentHasNextBlock, nextBlock)) {
return null;
}
}
// Ensure the first hasNext() is not after next().
if (priorHasNextBlock == nextBlock) {
if (nextBlock.iterator(nextInstr).nextUntil(priorHasNextInstr) != null) {
return null;
}
} else if (hasPredecessorPathTo(iteratorBlock, priorHasNextBlock, nextBlock)) {
return null;
}
}
// Make sure each instruction can be run at most once (no loops).
if (priorHasNextBlock != null && loopExists(iteratorBlock, priorHasNextBlock)) {
return null;
}
if (nextBlock != null && loopExists(iteratorBlock, nextBlock)) {
return null;
}
if (subsequentHasNextBlock != null && loopExists(iteratorBlock, subsequentHasNextBlock)) {
return null;
}
return new DirectRewriteResult(priorHasNextInstr, nextInstr, subsequentHasNextInstr);
}
private static boolean loopExists(BasicBlock subgraphEntryBlock, BasicBlock targetBlock) {
return hasPredecessorPathTo(subgraphEntryBlock, targetBlock, targetBlock);
}
private static boolean hasPredecessorPathTo(
BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock, BasicBlock targetBlock) {
if (subgraphEntryBlock == subgraphExitBlock) {
return false;
}
if (subgraphEntryBlock == targetBlock) {
return true;
}
WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
workList.markAsSeen(subgraphEntryBlock);
workList.addIfNotSeen(subgraphExitBlock.getPredecessors());
while (workList.hasNext()) {
BasicBlock curBlock = workList.next();
if (curBlock == targetBlock) {
return true;
}
workList.addIfNotSeen(curBlock.getPredecessors());
}
return false;
}
private void report(ProgramMethod method, DexType serviceLoaderType, String message) {
if (shouldReportWhyAreYouNotInliningServiceLoaderLoad()) {
appView
.reporter()
.info(
new ServiceLoaderRewriterDiagnostic(
method.getOrigin(),
"Could not inline ServiceLoader.load"
+ (serviceLoaderType == null
? ""
: (" of type " + serviceLoaderType.getTypeName()))
+ ": "
+ message));
}
}
private boolean hasServiceImplementationInDifferentFeature(
IRCode code, DexType serviceType, boolean baseFeatureOnly) {
AppView<AppInfoWithLiveness> appViewWithClasses = appView();
ClassToFeatureSplitMap classToFeatureSplitMap =
appViewWithClasses.appInfo().getClassToFeatureSplitMap();
if (classToFeatureSplitMap.isEmpty()) {
return false;
}
Map<FeatureSplit, List<DexType>> featureImplementations =
appView.appServices().serviceImplementationsByFeatureFor(serviceType);
if (featureImplementations == null || featureImplementations.isEmpty()) {
return false;
}
DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
if (serviceClass == null) {
return false;
}
FeatureSplit serviceFeature =
classToFeatureSplitMap.getFeatureSplit(serviceClass, appViewWithClasses);
if (baseFeatureOnly && !serviceFeature.isBase()) {
report(
code.context(),
serviceType,
"ClassLoader arg was null and service interface is in non-base feature");
return true;
}
for (var entry : featureImplementations.entrySet()) {
FeatureSplit metaInfFeature = entry.getKey();
if (!metaInfFeature.isBase()) {
if (baseFeatureOnly) {
report(
code.context(),
serviceType,
"ClassLoader arg was null and META-INF/ service entry found in non-base feature");
return true;
}
if (metaInfFeature != serviceFeature) {
report(
code.context(),
serviceType,
"META-INF/ service found in different feature from service interface");
return true;
}
}
for (DexType impl : entry.getValue()) {
FeatureSplit implFeature = classToFeatureSplitMap.getFeatureSplit(impl, appViewWithClasses);
if (implFeature != serviceFeature) {
report(
code.context(),
serviceType,
"Implementation found in different feature from service interface: " + impl);
return true;
}
}
}
return false;
}
private DexEncodedMethod createSynthesizedMethod(
DexType serviceType,
List<DexClass> classes,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
DexProto proto =
appView.dexItemFactory().createProto(appView.dexItemFactory().javaUtilIteratorType);
ProgramMethod method =
appView
.getSyntheticItems()
.createMethod(
kinds -> kinds.SERVICE_LOADER,
methodProcessingContext.createUniqueContext(),
appView,
builder ->
builder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setProto(proto)
.setApiLevelForDefinition(appView.computedMinApiLevel())
.setApiLevelForCode(
apiLevelCompute.computeApiLevelForDefinition(
ListUtils.map(classes, clazz -> clazz.type)))
.setCode(
m ->
ServiceLoaderSourceCode.generate(
serviceType, classes, appView.dexItemFactory())));
methodProcessor.scheduleDesugaredMethodForProcessing(method);
methodProcessor
.getEventConsumer()
.acceptServiceLoaderLoadUtilityMethod(method, methodProcessingContext.getMethodContext());
return method.getDefinition();
}
private static class ServiceLoaderLoadResult {
public final InvokeStatic loadInvoke;
public final InvokeVirtual classLoaderInvoke;
public final DexType serviceType;
public final List<DexClass> implClasses;
public final InvokeVirtual iteratorInvoke;
public ServiceLoaderLoadResult(
InvokeStatic loadInvoke,
InvokeVirtual classLoaderInvoke,
DexType serviceType,
List<DexClass> implClasses,
InvokeVirtual iteratorInvoke) {
this.loadInvoke = loadInvoke;
this.classLoaderInvoke = classLoaderInvoke;
this.serviceType = serviceType;
this.implClasses = implClasses;
this.iteratorInvoke = iteratorInvoke;
}
}
private static class DirectRewriteResult {
public final InvokeMethod priorHasNextInstr;
public final InvokeMethod nextInstr;
public final InvokeMethod subsequentHasNextInstr;
public DirectRewriteResult(
InvokeMethod priorHasNextInstr,
InvokeMethod nextInstr,
InvokeMethod subsequentHasNextInstr) {
this.priorHasNextInstr = priorHasNextInstr;
this.nextInstr = nextInstr;
this.subsequentHasNextInstr = subsequentHasNextInstr;
}
}
}