blob: 715c9367f3125ccf25774798238b19d5d225f91d [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.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.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
// This pass
// - cleans the nests: it removes missing nest host/members from the input and
// report them (warning or error).
// - clears nests which do not use nest based access control to allow other
// optimizations such as class merging to perform better.
public class NestReducer {
private AppView<?> appView;
public NestReducer(AppView<?> appView) {
this.appView = appView;
}
private DexClass definitionFor(DexType type) {
assert appView.graphLense().lookupType(type) == type;
return appView.definitionFor(appView.graphLense().lookupType(type));
}
public void run(ExecutorService executorService) throws ExecutionException {
Set<DexType> nestHosts = Sets.newIdentityHashSet();
List<Future<?>> futures = new ArrayList<>();
// It is possible that a nest member is on the program path but its nest host
// is only in the class path.
// Nests are therefore computed the first time a nest member is met, host or not.
// The computedNestHosts list is there to avoid processing multiple times the same nest.
for (DexProgramClass clazz : appView.appInfo().classes()) {
DexType hostType = clazz.getNestHost();
if (hostType != null && !nestHosts.contains(hostType)) {
nestHosts.add(hostType);
futures.add(
executorService.submit(
() -> {
processNestFrom(clazz);
return null; // we want a Callable not a Runnable to be able to throw
}));
}
}
ThreadUtils.awaitFutures(futures);
}
private void processNestFrom(DexClass clazz) {
DexClass nestHost = definitionFor(clazz.getNestHost());
if (nestHost == null) {
reportMissingNestHost(clazz);
clazz.clearNestHost();
return;
}
boolean hasPrivateMembers = hasPrivateMembers(nestHost);
Iterator<NestMemberClassAttribute> iterator =
nestHost.getNestMembersClassAttributes().iterator();
boolean reported = false;
while (iterator.hasNext()) {
DexClass member = definitionFor(iterator.next().getNestMember());
if (member == null) {
if (!reported) {
reported = true;
reportIncompleteNest(nestHost);
}
iterator.remove();
} else {
hasPrivateMembers = hasPrivateMembers || hasPrivateMembers(member);
}
}
if (!hasPrivateMembers && appView.options().enableNestReduction) {
clearNestAttributes(nestHost);
}
}
private void reportMissingNestHost(DexClass clazz) {
if (appView.options().ignoreMissingClasses) {
appView.options().warningMissingClassMissingNestHost(clazz);
} else {
appView.options().errorMissingClassMissingNestHost(clazz);
}
}
private void reportIncompleteNest(DexClass nestHost) {
List<DexType> nest = new ArrayList<>(nestHost.getNestMembersClassAttributes().size() + 1);
for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
nest.add(attr.getNestMember());
}
nest.add(nestHost.type);
if (appView.options().ignoreMissingClasses) {
appView.options().warningMissingClassIncompleteNest(nest, appView);
} else {
appView.options().errorMissingClassIncompleteNest(nest, appView);
}
}
private void clearNestAttributes(DexClass nestHost) {
nestHost.getNestMembersClassAttributes().clear();
for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
DexClass member =
appView.definitionFor(appView.graphLense().lookupType(attr.getNestMember()));
member.clearNestHost();
}
}
private boolean hasPrivateMembers(DexClass clazz) {
for (DexEncodedMethod method : clazz.methods()) {
if (method.accessFlags.isPrivate()) {
return true;
}
}
for (DexEncodedField field : clazz.fields()) {
if (field.accessFlags.isPrivate()) {
return true;
}
}
return false;
}
}