| // 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.graph.analysis; |
| |
| import com.android.tools.r8.graph.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.analysis.type.ClassTypeElement; |
| import com.android.tools.r8.shaking.Enqueuer; |
| import com.android.tools.r8.shaking.EnqueuerWorklist; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| |
| public class InitializedClassesInInstanceMethodsAnalysis extends EnqueuerAnalysis { |
| |
| // A simple structure that stores the result of the analysis. |
| public static class InitializedClassesInInstanceMethods { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final Map<DexType, DexType> mapping; |
| |
| private InitializedClassesInInstanceMethods( |
| AppView<? extends AppInfoWithClassHierarchy> appView, Map<DexType, DexType> mapping) { |
| this.appView = appView; |
| this.mapping = mapping; |
| } |
| |
| public boolean isClassDefinitelyLoadedInInstanceMethod( |
| DexProgramClass subject, ProgramMethod context) { |
| assert !context.getDefinition().isStatic(); |
| // If `subject` is kept, then it is instantiated by reflection, which means that the analysis |
| // has not seen all allocation sites. In that case, we conservatively return false. |
| AppInfoWithClassHierarchy appInfo = appView.appInfo(); |
| if (appInfo.hasLiveness() && appInfo.withLiveness().isPinned(subject.type)) { |
| return false; |
| } |
| |
| // Check that `subject` is guaranteed to be initialized in all instance methods of `context`. |
| DexType guaranteedToBeInitializedInContext = |
| mapping.getOrDefault(context.getHolderType(), appView.dexItemFactory().objectType); |
| if (!appInfo.isSubtype(guaranteedToBeInitializedInContext, subject.type)) { |
| return false; |
| } |
| |
| // Also check that `subject` is not an interface, since interfaces are not initialized |
| // transitively. |
| return !subject.isInterface(); |
| } |
| } |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| |
| // If the mapping contains an entry `X -> Y`, then the type Y is guaranteed to be initialized in |
| // all instance methods of X. |
| private final Map<DexType, DexType> mapping = new IdentityHashMap<>(); |
| |
| public InitializedClassesInInstanceMethodsAnalysis( |
| AppView<? extends AppInfoWithClassHierarchy> appView) { |
| this.appView = appView; |
| } |
| |
| @Override |
| public void processNewlyInstantiatedClass( |
| DexProgramClass clazz, ProgramMethod context, EnqueuerWorklist worklist) { |
| DexType key = clazz.type; |
| DexType objectType = appView.dexItemFactory().objectType; |
| if (context == null) { |
| // Record that we don't know anything about the set of classes that are guaranteed to be |
| // initialized in the instance methods of `clazz`. |
| mapping.put(key, objectType); |
| return; |
| } |
| |
| // Record that the enclosing class is guaranteed to be initialized at the allocation site. |
| AppInfoWithClassHierarchy appInfo = appView.appInfo(); |
| DexType guaranteedToBeInitialized = context.getHolderType(); |
| DexType existingGuaranteedToBeInitialized = |
| mapping.getOrDefault(key, guaranteedToBeInitialized); |
| mapping.put( |
| key, |
| ClassTypeElement.computeLeastUpperBoundOfClasses( |
| appInfo, guaranteedToBeInitialized, existingGuaranteedToBeInitialized)); |
| } |
| |
| @Override |
| public void done(Enqueuer enqueuer) { |
| appView.setInitializedClassesInInstanceMethods( |
| new InitializedClassesInInstanceMethods(appView, mapping)); |
| } |
| } |