blob: ded7e1d3cb5311199cc8bf3b0dc4e6f29de7896c [file] [log] [blame]
// Copyright (c) 2020, 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.horizontalclassmerging.policies;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class RespectPackageBoundaries extends MultiClassPolicy {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Mode mode;
public RespectPackageBoundaries(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
this.appView = appView;
this.mode = mode;
}
boolean shouldRestrictMergingAcrossPackageBoundary(DexProgramClass clazz) {
// Check that the class is public, otherwise it is package private.
if (!clazz.isPublic()) {
return true;
}
// Annotations may access non-public items (or themselves be non-public), so for now we
// conservatively restrict class merging when annotations are present.
//
// Note that in non-compat mode we should never see any annotations here, since we only merge
// non-kept classes with non-kept members.
if (clazz.hasAnnotations()
|| Iterables.any(clazz.members(), DexDefinition::hasAnyAnnotations)) {
return true;
}
// Check that each implemented interface is public.
for (DexType interfaceType : clazz.getInterfaces()) {
if (clazz.getType().isSamePackage(interfaceType)) {
DexClass interfaceClass = appView.definitionFor(interfaceType);
if (interfaceClass == null || !interfaceClass.isPublic()) {
return true;
}
}
}
// If any members are package private or protected, then their access depends on the package.
for (DexEncodedMember<?, ?> member : clazz.members()) {
if (member.getAccessFlags().isPackagePrivateOrProtected()) {
return true;
}
}
// If any field has a non-public type, then field merging may lead to check-cast instructions
// being synthesized. These synthesized accesses depends on the package.
for (DexEncodedField field : clazz.fields()) {
DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
if (fieldBaseType.isClassType()) {
DexClass fieldBaseClass = appView.definitionFor(fieldBaseType);
if (fieldBaseClass == null || !fieldBaseClass.isPublic()) {
return true;
}
}
}
// Check that all accesses from [clazz] to classes or members from the current package of
// [clazz] will continue to work. This is guaranteed if the methods of [clazz] do not access
// any private or protected classes or members from the current package of [clazz].
TraversalContinuation<?, ?> result =
clazz.traverseProgramMethods(
method -> {
boolean foundIllegalAccess =
method.registerCodeReferencesWithResult(
new IllegalAccessDetector(appView, method) {
@Override
protected boolean checkRewrittenFieldType(DexClassAndField field) {
if (mode.isInitial()) {
// If the type of the field is package private, we need to keep the
// current class in its package in case we end up synthesizing a
// check-cast for the field type after relaxing the type of the field
// after instance field merging.
DexType fieldBaseType = field.getType().toBaseType(dexItemFactory());
if (fieldBaseType.isClassType()) {
DexClass fieldBaseClass = appView.definitionFor(fieldBaseType);
if (fieldBaseClass == null || !fieldBaseClass.isPublic()) {
return setFoundPackagePrivateAccess();
}
}
} else {
// No relaxing of field types, hence no insertion of casts where we need
// to guarantee visibility.
}
return continueSearchForPackagePrivateAccess();
}
});
if (foundIllegalAccess) {
return TraversalContinuation.doBreak();
}
return TraversalContinuation.doContinue();
});
return result.shouldBreak();
}
/** Sort unrestricted classes into restricted classes if they are in the same package. */
void tryFindRestrictedPackage(
MergeGroup unrestrictedClasses, Map<String, MergeGroup> restrictedClasses) {
unrestrictedClasses.removeIf(
clazz -> {
MergeGroup restrictedPackage = restrictedClasses.get(clazz.type.getPackageDescriptor());
if (restrictedPackage != null) {
restrictedPackage.add(clazz);
return true;
}
return false;
});
}
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
Map<String, MergeGroup> restrictedClasses = new LinkedHashMap<>();
MergeGroup unrestrictedClasses = new MergeGroup();
// Sort all restricted classes into packages.
for (DexProgramClass clazz : group) {
if (shouldRestrictMergingAcrossPackageBoundary(clazz)) {
restrictedClasses
.computeIfAbsent(clazz.getType().getPackageDescriptor(), ignore -> new MergeGroup())
.add(clazz);
} else {
unrestrictedClasses.add(clazz);
}
}
tryFindRestrictedPackage(unrestrictedClasses, restrictedClasses);
removeTrivialGroups(restrictedClasses.values());
// TODO(b/166577694): Add the unrestricted classes to restricted groups, but ensure they aren't
// the merge target.
Collection<MergeGroup> groups = new ArrayList<>(restrictedClasses.size() + 1);
if (unrestrictedClasses.size() > 1) {
groups.add(unrestrictedClasses);
}
groups.addAll(restrictedClasses.values());
return groups;
}
@Override
public String getName() {
return "RespectPackageBoundaries";
}
}