blob: 248411346d657df5f2835ad2d7ae193e647affa4 [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.ir.desugar;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
// Helper class implementing bunch of default interface method handling operations.
final class DefaultMethodsHelper {
// Collection of default methods that need to have generated forwarding methods.
public static class DefaultMethodCandidates {
final List<DexEncodedMethod> candidates;
final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
private static final DefaultMethodCandidates EMPTY =
new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
public static DefaultMethodCandidates empty() {
return EMPTY;
}
public DefaultMethodCandidates(
List<DexEncodedMethod> candidates,
Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
this.candidates = candidates;
this.conflicts = conflicts;
}
public int size() {
return candidates.size() + conflicts.size();
}
public boolean isEmpty() {
return candidates.isEmpty() && conflicts.isEmpty();
}
}
// Equivalence wrapper for comparing two method signatures modulo holder type.
private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
@Override
protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
return method1.method.match(method2.method);
}
@Override
protected int doHash(DexEncodedMethod method) {
return Objects.hash(method.method.name, method.method.proto);
}
}
// Current set of default interface methods, may overlap with `hidden`.
private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
// Current set of known hidden default interface methods.
private final Set<DexEncodedMethod> hidden = Sets.newIdentityHashSet();
// Represents information about default interface methods of an
// interface and its superinterfaces. Namely: a list of live (not hidden)
// and hidden default interface methods in this interface's hierarchy.
//
// Note that it is assumes that these lists should never be big.
final static class Collection {
static final Collection EMPTY =
new Collection(Collections.emptyList(), Collections.emptyList());
// All live default interface methods in this interface's hierarchy.
private final List<DexEncodedMethod> live;
// All hidden default interface methods in this interface's hierarchy.
private final List<DexEncodedMethod> hidden;
private Collection(List<DexEncodedMethod> live, List<DexEncodedMethod> hidden) {
this.live = live;
this.hidden = hidden;
}
// If there is just one live method having specified
// signature return it, otherwise return null.
DexMethod getSingleCandidate(DexMethod method) {
DexMethod candidate = null;
for (DexEncodedMethod encodedMethod : live) {
DexMethod current = encodedMethod.method;
if (current.proto == method.proto && current.name == method.name) {
if (candidate != null) {
return null;
}
candidate = current;
}
}
return candidate;
}
}
final void merge(Collection collection) {
candidates.addAll(collection.live);
hidden.addAll(collection.hidden);
}
final void hideMatches(DexMethod method) {
Iterator<DexEncodedMethod> it = candidates.iterator();
while (it.hasNext()) {
DexEncodedMethod candidate = it.next();
if (method.match(candidate)) {
hidden.add(candidate);
it.remove();
}
}
}
final void addDefaultMethod(DexEncodedMethod encoded) {
candidates.add(encoded);
}
final DefaultMethodCandidates createCandidatesList() {
// The common cases is for no default methods or a single one.
if (candidates.isEmpty()) {
return DefaultMethodCandidates.empty();
}
if (candidates.size() == 1 && hidden.isEmpty()) {
return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
}
// In case there are more we need to check for potential duplicates and treat them specially
// to preserve the IncompatibleClassChangeError that would arise at runtime.
int maxSize = candidates.size();
SignatureEquivalence equivalence = new SignatureEquivalence();
Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
boolean foundConflicts = false;
for (DexEncodedMethod candidate : candidates) {
if (hidden.contains(candidate)) {
continue;
}
Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
List<DexEncodedMethod> conflicts = groups.get(key);
if (conflicts != null) {
foundConflicts = true;
} else {
conflicts = new ArrayList<>(maxSize);
groups.put(key, conflicts);
}
conflicts.add(candidate);
}
// In the fast path we don't expect any conflicts or hidden candidates.
if (!foundConflicts && hidden.isEmpty()) {
return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
}
// Slow case in the case of conflicts or hidden candidates build the result.
List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
if (entry.getValue().size() == 1) {
actualCandidates.add(entry.getKey().get());
} else {
conflicts.put(entry.getKey().get(), entry.getValue());
}
}
return new DefaultMethodCandidates(actualCandidates, conflicts);
}
final List<DexEncodedMethod> createFullList() {
if (candidates.isEmpty() && hidden.isEmpty()) {
return Collections.emptyList();
}
List<DexEncodedMethod> fullList =
new ArrayList<DexEncodedMethod>(candidates.size() + hidden.size());
fullList.addAll(candidates);
fullList.addAll(hidden);
return fullList;
}
// Create default interface collection based on collected information.
final Collection wrapInCollection() {
candidates.removeAll(hidden);
return (candidates.isEmpty() && hidden.isEmpty()) ? Collection.EMPTY
: new Collection(Lists.newArrayList(candidates), Lists.newArrayList(hidden));
}
}