| // 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.shaking; | |
| import com.android.tools.r8.graph.AppView; | |
| import com.android.tools.r8.graph.DexClass; | |
| import com.android.tools.r8.graph.DexClassAndMethod; | |
| import com.android.tools.r8.graph.DexEncodedMethod; | |
| 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.graph.TopDownClassHierarchyTraversal; | |
| import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis; | |
| import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration; | |
| import com.android.tools.r8.ir.code.IRCode; | |
| import com.android.tools.r8.ir.code.Instruction; | |
| import com.android.tools.r8.ir.code.InvokeDirect; | |
| import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; | |
| import com.google.common.collect.Sets; | |
| import java.util.Collections; | |
| import java.util.Set; | |
| public class LibraryMethodOverrideAnalysis { | |
| private final AppView<AppInfoWithLiveness> appView; | |
| // Note: Set is accessed concurrently and must be thread-safe. | |
| private final Set<DexType> nonEscapingClassesWithLibraryMethodOverrides; | |
| public LibraryMethodOverrideAnalysis(AppView<AppInfoWithLiveness> appView) { | |
| this.appView = appView; | |
| this.nonEscapingClassesWithLibraryMethodOverrides = | |
| Collections.synchronizedSet( | |
| getInitialNonEscapingClassesWithLibraryMethodOverrides(appView)); | |
| } | |
| private static Set<DexType> getInitialNonEscapingClassesWithLibraryMethodOverrides( | |
| AppView<AppInfoWithLiveness> appView) { | |
| Set<DexType> initialNonEscapingClassesWithLibraryMethodOverrides = | |
| getClassesWithLibraryMethodOverrides(appView); | |
| // Remove all types that are pinned from the initial set of non-escaping classes. | |
| appView | |
| .appInfo() | |
| .getKeepInfo() | |
| .forEachPinnedType( | |
| initialNonEscapingClassesWithLibraryMethodOverrides::remove, appView.options()); | |
| return initialNonEscapingClassesWithLibraryMethodOverrides; | |
| } | |
| private static Set<DexType> getClassesWithLibraryMethodOverrides( | |
| AppView<AppInfoWithLiveness> appView) { | |
| Set<DexType> classesWithLibraryMethodOverrides = Sets.newIdentityHashSet(); | |
| TopDownClassHierarchyTraversal.forProgramClasses(appView) | |
| .visit( | |
| appView.appInfo().classes(), | |
| clazz -> { | |
| if (hasLibraryMethodOverrideDirectlyOrIndirectly( | |
| clazz, classesWithLibraryMethodOverrides)) { | |
| classesWithLibraryMethodOverrides.add(clazz.type); | |
| } | |
| }); | |
| return classesWithLibraryMethodOverrides; | |
| } | |
| private static boolean hasLibraryMethodOverrideDirectlyOrIndirectly( | |
| DexProgramClass clazz, Set<DexType> classesWithLibraryMethodOverrides) { | |
| return hasLibraryMethodOverrideDirectly(clazz) | |
| || hasLibraryMethodOverrideIndirectly(clazz, classesWithLibraryMethodOverrides); | |
| } | |
| private static boolean hasLibraryMethodOverrideDirectly(DexProgramClass clazz) { | |
| for (DexEncodedMethod method : clazz.virtualMethods()) { | |
| if (method.accessFlags.isAbstract()) { | |
| continue; | |
| } | |
| if (method.isLibraryMethodOverride().isPossiblyTrue()) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private static boolean hasLibraryMethodOverrideIndirectly( | |
| DexProgramClass clazz, Set<DexType> classesWithLibraryMethodOverrides) { | |
| if (classesWithLibraryMethodOverrides.contains(clazz.superType)) { | |
| return true; | |
| } | |
| for (DexType interfaceType : clazz.interfaces.values) { | |
| if (classesWithLibraryMethodOverrides.contains(interfaceType)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| public void analyze(IRCode code) { | |
| if (nonEscapingClassesWithLibraryMethodOverrides.isEmpty()) { | |
| // No need to run escape analysis since all types have already escaped. | |
| return; | |
| } | |
| // Must be thread local. | |
| EscapeAnalysis escapeAnalysis = | |
| new EscapeAnalysis(appView, LibraryEscapeAnalysisConfiguration.getInstance()); | |
| for (Instruction instruction : code.instructions()) { | |
| if (instruction.isNewInstance()) { | |
| DexType type = instruction.asNewInstance().clazz; | |
| DexClass clazz = appView.definitionFor(type); | |
| if (clazz == null || !clazz.isProgramClass()) { | |
| continue; | |
| } | |
| if (nonEscapingClassesWithLibraryMethodOverrides.contains(type)) { | |
| // We need to remove this instance from the set of non-escaping classes if it escapes. | |
| if (escapeAnalysis.isEscaping(code, instruction.outValue())) { | |
| nonEscapingClassesWithLibraryMethodOverrides.remove(type); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public void finish() { | |
| assert verifyNoUninstantiatedTypesEscapeIntoLibrary(); | |
| appView.setClassesEscapingIntoLibrary( | |
| type -> !nonEscapingClassesWithLibraryMethodOverrides.contains(type)); | |
| } | |
| private boolean verifyNoUninstantiatedTypesEscapeIntoLibrary() { | |
| Set<DexType> classesWithLibraryMethodOverrides = getClassesWithLibraryMethodOverrides(appView); | |
| for (DexProgramClass clazz : appView.appInfo().classes()) { | |
| assert appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz) | |
| || !classesWithLibraryMethodOverrides.contains(clazz.type) | |
| || nonEscapingClassesWithLibraryMethodOverrides.contains(clazz.type); | |
| } | |
| return true; | |
| } | |
| static class LibraryEscapeAnalysisConfiguration implements EscapeAnalysisConfiguration { | |
| private static final LibraryEscapeAnalysisConfiguration INSTANCE = | |
| new LibraryEscapeAnalysisConfiguration(); | |
| private LibraryEscapeAnalysisConfiguration() {} | |
| public static LibraryEscapeAnalysisConfiguration getInstance() { | |
| return INSTANCE; | |
| } | |
| @Override | |
| public boolean isLegitimateEscapeRoute( | |
| AppView<?> appView, | |
| EscapeAnalysis escapeAnalysis, | |
| Instruction escapeRoute, | |
| ProgramMethod context) { | |
| if (appView.appInfo().hasLiveness()) { | |
| return isLegitimateConstructorInvocation( | |
| appView.withLiveness(), escapeAnalysis, escapeRoute, context); | |
| } | |
| return false; | |
| } | |
| private boolean isLegitimateConstructorInvocation( | |
| AppView<AppInfoWithLiveness> appView, | |
| EscapeAnalysis escapeAnalysis, | |
| Instruction instruction, | |
| ProgramMethod context) { | |
| if (!instruction.isInvokeDirect()) { | |
| return false; | |
| } | |
| InvokeDirect invoke = instruction.asInvokeDirect(); | |
| if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) { | |
| return false; | |
| } | |
| for (int i = 1; i < invoke.arguments().size(); i++) { | |
| if (escapeAnalysis.isValueOfInterestOrAlias(invoke.arguments().get(i))) { | |
| return false; | |
| } | |
| } | |
| DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); | |
| if (singleTarget == null) { | |
| return false; | |
| } | |
| InstanceInitializerInfo initializerInfo = | |
| singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo(invoke); | |
| return initializerInfo.receiverNeverEscapesOutsideConstructorChain(); | |
| } | |
| } | |
| } |