blob: 51e9b02a067814e7b905b484418c69c618bab3a4 [file] [log] [blame]
// Copyright (c) 2017, 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 static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import java.util.HashSet;
import java.util.Set;
/**
* Removes abstract methods if they only shadow methods of the same signature in a superclass.
* <p>
* We do not consider classes from the library for this optimization, as the program might run
* against a different version of the library where methods are missing.
* <p>
* This optimization is beneficial mostly as it removes superfluous abstract methods that are
* created by the {@link TreePruner}.
*/
public class AbstractMethodRemover {
private final AppView<AppInfoWithLiveness> appView;
private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
private ScopedDexMethodSet scope = new ScopedDexMethodSet();
public AbstractMethodRemover(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.immediateSubtypingInfo = ImmediateProgramSubtypingInfo.create(appView);
}
public void run() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
assert scope.getParent() == null;
if (isRoot(clazz)) {
processClass(clazz);
}
}
appView.notifyOptimizationFinishedForTesting();
}
private boolean isRoot(DexProgramClass clazz) {
if (clazz.isInterface()) {
return false;
}
if (!clazz.hasSuperType()) {
return true;
}
return asProgramClassOrNull(appView.definitionFor(clazz.getSuperType(), clazz)) == null;
}
private void processClass(DexProgramClass clazz) {
scope = scope.newNestedScope();
processMethods(clazz);
immediateSubtypingInfo.getSubclasses(clazz).forEach(this::processClass);
scope = scope.getParent();
}
private void processMethods(DexProgramClass clazz) {
Set<DexEncodedMethod> toRemove = null;
for (ProgramMethod method : clazz.virtualProgramMethods()) {
if (!isNonAbstractPinnedOrWideningVisibility(method)) {
if (toRemove == null) {
toRemove = new HashSet<>();
}
toRemove.add(method.getDefinition());
}
}
if (toRemove != null) {
clazz.getMethodCollection().removeMethods(toRemove);
}
}
private boolean isNonAbstractPinnedOrWideningVisibility(ProgramMethod method) {
if (!method.getAccessFlags().isAbstract()) {
return true;
}
// Check if the method widens visibility. Adding to the scope mutates it.
if (scope.addMethodIfMoreVisible(method.getDefinition())
!= AddMethodIfMoreVisibleResult.NOT_ADDED) {
return true;
}
if (appView.appInfo().isPinned(method)) {
return true;
}
// We will filter the method out since it is not pinned.
return false;
}
}