| // 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.analysis; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.DefaultInstructionVisitor; |
| import com.android.tools.r8.ir.code.DominatorTree; |
| import com.android.tools.r8.ir.code.DominatorTree.Assumption; |
| import com.android.tools.r8.ir.code.FieldInstruction; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.Invoke; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.NewInstance; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * An analysis that given a method returns a set of types that are guaranteed to be initialized by |
| * the method on all normal exits of the given method. |
| */ |
| public class InitializedClassesOnNormalExitAnalysis { |
| |
| public static Set<DexType> computeInitializedClassesOnNormalExit( |
| AppView<AppInfoWithLiveness> appView, IRCode code) { |
| DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS); |
| Visitor visitor = new Visitor(appView, code.context()); |
| for (BasicBlock dominator : dominatorTree.normalExitDominatorBlocks()) { |
| if (dominator.hasCatchHandlers()) { |
| // When determining which classes that are guaranteed to be initialized from a given |
| // instruction, we assume that the given instruction does not throw. Therefore, we skip |
| // blocks that have a catch handler. |
| continue; |
| } |
| |
| for (Instruction instruction : dominator.getInstructions()) { |
| instruction.accept(visitor); |
| } |
| } |
| return visitor.build(); |
| } |
| |
| private static class Visitor extends DefaultInstructionVisitor<Void> { |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final ProgramMethod context; |
| private final Set<DexType> initializedClassesOnNormalExit = Sets.newIdentityHashSet(); |
| |
| Visitor(AppView<AppInfoWithLiveness> appView, ProgramMethod context) { |
| this.appView = appView; |
| this.context = context; |
| } |
| |
| Set<DexType> build() { |
| return Collections.unmodifiableSet(initializedClassesOnNormalExit); |
| } |
| |
| private void markInitializedOnNormalExit(Iterable<DexType> knownToBeInitialized) { |
| knownToBeInitialized.forEach(this::markInitializedOnNormalExit); |
| } |
| |
| private void markInitializedOnNormalExit(DexType knownToBeInitialized) { |
| if (knownToBeInitialized == context.getHolderType()) { |
| // Do not record that the given method causes its own holder to be initialized, since this |
| // is trivial. |
| return; |
| } |
| DexClass clazz = appView.definitionFor(knownToBeInitialized); |
| if (clazz == null) { |
| return; |
| } |
| if (!clazz.isProgramClass()) { |
| // Only mark program classes as being initialized on normal exits. |
| return; |
| } |
| if (!clazz.classInitializationMayHaveSideEffects(appView)) { |
| // Only mark classes that actually have side effects during class initialization. |
| return; |
| } |
| List<DexType> subsumedByKnownToBeInitialized = null; |
| for (DexType alreadyKnownToBeInitialized : initializedClassesOnNormalExit) { |
| if (appView.isSubtype(alreadyKnownToBeInitialized, knownToBeInitialized).isTrue()) { |
| // The fact that there is a subtype B of the given type A, which is known to be |
| // initialized, implies that the given type A is also be initialized. Therefore, we do not |
| // add this type into the set of classes that have been initialized on all normal exits. |
| return; |
| } |
| |
| if (appView.isSubtype(knownToBeInitialized, alreadyKnownToBeInitialized).isTrue()) { |
| if (subsumedByKnownToBeInitialized == null) { |
| subsumedByKnownToBeInitialized = new ArrayList<>(); |
| } |
| subsumedByKnownToBeInitialized.add(alreadyKnownToBeInitialized); |
| } |
| } |
| initializedClassesOnNormalExit.add(knownToBeInitialized); |
| if (subsumedByKnownToBeInitialized != null) { |
| initializedClassesOnNormalExit.removeAll(subsumedByKnownToBeInitialized); |
| } |
| } |
| |
| @Override |
| public Void handleFieldInstruction(FieldInstruction instruction) { |
| DexEncodedField field = |
| appView.appInfo().resolveField(instruction.getField()).getResolvedField(); |
| if (field != null) { |
| if (field.holder().isClassType()) { |
| markInitializedOnNormalExit(field.holder()); |
| } else { |
| assert false : "Expected holder of field type to be a class type"; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void handleInvoke(Invoke instruction) { |
| if (instruction.isInvokeMethod()) { |
| InvokeMethod invoke = instruction.asInvokeMethod(); |
| DexMethod method = invoke.getInvokedMethod(); |
| if (method.holder.isClassType()) { |
| DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); |
| if (singleTarget != null) { |
| markInitializedOnNormalExit(singleTarget.holder()); |
| markInitializedOnNormalExit( |
| singleTarget.getOptimizationInfo().getInitializedClassesOnNormalExit()); |
| } else { |
| markInitializedOnNormalExit(method.holder); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visit(NewInstance instruction) { |
| markInitializedOnNormalExit(instruction.clazz); |
| return null; |
| } |
| } |
| } |