Reland "Use the type as the prefix for external synthetics."
This reverts commit df524d261735db40ca981db5a83bd5b4bb9bd4f6.
Bug: 179174151
Change-Id: Ic327e901f7723249976fef8c31ccb860bc5b4dbb
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 5ebf3ae..ef99d01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -36,6 +36,19 @@
}
@Override
+ public int compareTo(DexReference other) {
+ if (other.isDexField()) {
+ return compareTo(other.asDexField());
+ }
+ if (other.isDexMethod()) {
+ int comparisonResult = getHolderType().compareTo(other.getContextType());
+ return comparisonResult != 0 ? comparisonResult : -1;
+ }
+ int comparisonResult = getHolderType().compareTo(other.asDexType());
+ return comparisonResult != 0 ? comparisonResult : 1;
+ }
+
+ @Override
public DexField self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 29e9792..8484a94 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -36,6 +36,15 @@
}
@Override
+ public int compareTo(DexReference other) {
+ if (other.isDexMethod()) {
+ return compareTo(other.asDexMethod());
+ }
+ int comparisonResult = getHolderType().compareTo(other.getContextType());
+ return comparisonResult != 0 ? comparisonResult : 1;
+ }
+
+ @Override
public StructuralMapping<DexMethod> getStructuralMapping() {
return DexMethod::specify;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 77976f1..27264bb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -29,6 +29,8 @@
public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
+ public abstract int compareTo(DexReference other);
+
public abstract DexType getContextType();
public boolean isDexType() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index aaeb156..75b592b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -53,6 +53,15 @@
}
@Override
+ public int compareTo(DexReference other) {
+ if (other.isDexType()) {
+ return compareTo(other.asDexType());
+ }
+ int comparisonResult = compareTo(other.getContextType());
+ return comparisonResult != 0 ? comparisonResult : -1;
+ }
+
+ @Override
public DexType self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 1067e93..82f3204 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -8,15 +8,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.Comparator;
import java.util.Set;
@@ -63,33 +60,6 @@
return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin);
}
- static SynthesizingContext fromSyntheticContextChange(
- SyntheticKind kind,
- DexType syntheticType,
- SynthesizingContext oldContext,
- DexItemFactory factory) {
- String descriptor = syntheticType.toDescriptorString();
- DexType newContext;
- if (kind.isFixedSuffixSynthetic) {
- int i = descriptor.lastIndexOf(kind.descriptor);
- if (i < 0 || descriptor.length() != i + kind.descriptor.length() + 1) {
- assert false : "Unexpected fixed synthetic with invalid suffix: " + syntheticType;
- return null;
- }
- newContext = factory.createType(descriptor.substring(0, i) + ";");
- } else {
- int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
- if (i <= 0) {
- assert false : "Unexpected synthetic without internal separator: " + syntheticType;
- return null;
- }
- newContext = factory.createType(descriptor.substring(0, i) + ";");
- }
- return newContext == oldContext.getSynthesizingContextType()
- ? oldContext
- : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
- }
-
private SynthesizingContext(
DexType synthesizingContextType, DexType inputContextType, Origin inputContextOrigin) {
this.synthesizingContextType = synthesizingContextType;
@@ -127,10 +97,10 @@
}
void registerPrefixRewriting(DexType hygienicType, AppView<?> appView) {
- assert hygienicType.toSourceString().startsWith(synthesizingContextType.toSourceString());
if (!appView.options().isDesugaredLibraryCompilation()) {
return;
}
+ assert hygienicType.toSourceString().startsWith(synthesizingContextType.toSourceString());
DexType rewrittenContext =
appView
.options()
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
index 25142ee..69f87fe 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -30,4 +30,9 @@
DexType getHolder() {
return type;
}
+
+ @Override
+ DexType getReference() {
+ return type;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
index 5b9ba3c..72beb13 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
@@ -34,9 +34,12 @@
}
@Override
- SyntheticClasspathClassReference rewrite(NonIdentityGraphLens lens) {
+ SyntheticClasspathClassReference internalRewrite(
+ SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
assert type == lens.lookupType(type)
: "Unexpected classpath rewrite of type " + type.toSourceString();
+ assert getContext() == rewrittenContext
+ : "Unexpected classpath rewrite of context type " + getContext();
return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 239e7d0..9520d57 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -59,12 +59,21 @@
return context;
}
+ final String getPrefixForExternalSyntheticType() {
+ return SyntheticNaming.getPrefixForExternalSyntheticType(getKind(), getHolder().getType());
+ }
+
public abstract C getHolder();
final HashCode computeHash(
RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
Hasher hasher = Hashing.murmur3_128().newHasher();
- if (intermediate || getKind().isFixedSuffixSynthetic) {
+ if (getKind().isFixedSuffixSynthetic) {
+ // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
+ getHolder().getType().hash(hasher);
+ return hasher.hash();
+ }
+ if (intermediate) {
// If in intermediate mode, include the context type as sharing is restricted to within a
// single context.
getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
@@ -95,7 +104,13 @@
boolean includeContext,
GraphLens graphLens,
ClassToFeatureSplitMap classToFeatureSplitMap) {
- if (includeContext || getKind().isFixedSuffixSynthetic) {
+ DexType thisType = getHolder().getType();
+ DexType otherType = other.getHolder().getType();
+ if (getKind().isFixedSuffixSynthetic) {
+ // Fixed synthetics are non-shareable. Ordered by their unique type.
+ return thisType.compareTo(otherType);
+ }
+ if (includeContext) {
int order = getContext().compareTo(other.getContext());
if (order != 0) {
return order;
@@ -113,8 +128,6 @@
return order;
}
}
- DexType thisType = getHolder().getType();
- DexType otherType = other.getHolder().getType();
RepresentativeMap map = null;
// If the synthetics have been moved include the original types in the equivalence.
if (graphLens.isNonIdentityLens()) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 741831e..60dca8f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -265,7 +265,7 @@
ImmutableMap.builder();
List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>();
{
- Map<DexType, NumberGenerator> generators = new IdentityHashMap<>();
+ Map<String, NumberGenerator> generators = new HashMap<>();
application =
buildLensAndProgram(
appView,
@@ -317,7 +317,7 @@
Map<DexType, EquivalenceGroup<D>> computeEquivalences(
AppView<?> appView,
ImmutableCollection<R> references,
- Map<DexType, NumberGenerator> generators) {
+ Map<String, NumberGenerator> generators) {
boolean intermediate = appView.options().intermediate;
Map<DexType, D> definitions = lookupDefinitions(appView, references);
ClassToFeatureSplitMap classToFeatureSplitMap =
@@ -662,11 +662,11 @@
private <T extends SyntheticDefinition<?, T, ?>>
Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
Collection<List<T>> potentialEquivalences,
- Map<DexType, NumberGenerator> generators,
+ Map<String, NumberGenerator> generators,
AppView<?> appView,
boolean intermediate,
ClassToFeatureSplitMap classToFeatureSplitMap) {
- Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
+ Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
potentialEquivalences.forEach(
members -> {
List<List<T>> groups =
@@ -677,17 +677,16 @@
// The representative is required to be the first element of the group.
group.remove(representative);
group.add(0, representative);
- groupsPerContext
+ groupsPerPrefix
.computeIfAbsent(
- representative.getContext().getSynthesizingContextType(),
- k -> new ArrayList<>())
+ representative.getPrefixForExternalSyntheticType(), k -> new ArrayList<>())
.add(new EquivalenceGroup<>(representative, group));
}
});
Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
- groupsPerContext.forEach(
- (context, groups) -> {
+ groupsPerPrefix.forEach(
+ (externalSyntheticTypePrefix, groups) -> {
// Sort the equivalence groups that go into 'context' including the context type of the
// representative which is equal to 'context' here (see assert below).
groups.sort(
@@ -695,14 +694,18 @@
a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
- assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
+ assert group
+ .getRepresentative()
+ .getPrefixForExternalSyntheticType()
+ .equals(externalSyntheticTypePrefix);
// Two equivalence groups in same context type must be distinct otherwise the assignment
// of the synthetic name will be non-deterministic between the two.
assert i == 0
|| checkGroupsAreDistinct(
groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
SyntheticKind kind = group.members.get(0).getKind();
- DexType representativeType = createExternalType(kind, context, generators, appView);
+ DexType representativeType =
+ createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
equivalences.put(representativeType, group);
}
});
@@ -752,7 +755,7 @@
T smallest = members.get(0);
for (int i = 1; i < members.size(); i++) {
T next = members.get(i);
- if (next.compareTo(smallest, true, graphLens, classToFeatureSplitMap) < 0) {
+ if (next.toReference().getReference().compareTo(smallest.toReference().getReference()) < 0) {
smallest = next;
}
}
@@ -761,20 +764,20 @@
private DexType createExternalType(
SyntheticKind kind,
- DexType representativeContext,
- Map<DexType, NumberGenerator> generators,
+ String externalSyntheticTypePrefix,
+ Map<String, NumberGenerator> generators,
AppView<?> appView) {
DexItemFactory factory = appView.dexItemFactory();
if (kind.isFixedSuffixSynthetic) {
- return SyntheticNaming.createExternalType(kind, representativeContext, "", factory);
+ return SyntheticNaming.createExternalType(kind, externalSyntheticTypePrefix, "", factory);
}
NumberGenerator generator =
- generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
+ generators.computeIfAbsent(externalSyntheticTypePrefix, k -> new NumberGenerator());
DexType externalType;
do {
externalType =
SyntheticNaming.createExternalType(
- kind, representativeContext, Integer.toString(generator.next()), factory);
+ kind, externalSyntheticTypePrefix, Integer.toString(generator.next()), factory);
DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
if (clazz != null && isNotSyntheticType(clazz.type)) {
assert options.testing.allowConflictingSyntheticTypes
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 930443a..fd63ea2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -34,6 +34,11 @@
}
@Override
+ DexMethod getReference() {
+ return method;
+ }
+
+ @Override
SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
DexClass clazz = definitions.apply(method.holder);
if (clazz == null) {
@@ -47,7 +52,8 @@
}
@Override
- SyntheticMethodReference rewrite(NonIdentityGraphLens lens) {
+ SyntheticMethodReference internalRewrite(
+ SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
DexMethod rewritten = lens.lookupMethod(method);
// If the reference has been non-trivially rewritten the compiler has changed it and it can no
// longer be considered a synthetic. The context may or may not have changed.
@@ -58,20 +64,10 @@
assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
return null;
}
- SynthesizingContext context = getContext().rewrite(lens);
- if (context == getContext() && rewritten == method) {
+ if (rewrittenContext == getContext() && rewritten == method) {
return this;
}
- // Ensure that if a synthetic moves its context moves consistently.
- if (method != rewritten) {
- context =
- SynthesizingContext.fromSyntheticContextChange(
- getKind(), rewritten.holder, context, lens.dexItemFactory());
- if (context == null) {
- return null;
- }
- }
- return new SyntheticMethodReference(getKind(), context, rewritten);
+ return new SyntheticMethodReference(getKind(), rewrittenContext, rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 63772a2..25e59ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.references.ClassReference;
@@ -62,24 +63,32 @@
}
}
+ private static final String SYNTHETIC_CLASS_SEPARATOR = "-$$";
/**
* The internal synthetic class separator is only used for representing synthetic items during
* compilation. In particular, this separator must never be used to write synthetic classes to the
* final compilation result.
*/
- private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
+ private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR =
+ SYNTHETIC_CLASS_SEPARATOR + "InternalSynthetic";
/**
* The external synthetic class separator is used when writing classes. It may appear in types
* during compilation as the output of a compilation may be the input to another.
*/
- private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
+ private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR =
+ SYNTHETIC_CLASS_SEPARATOR + "ExternalSynthetic";
/** Method prefix when generating synthetic methods in a class. */
static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
- // TODO(b/158159959): Remove usage of name-based identification.
- public static boolean isSyntheticName(String typeName) {
- return typeName.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
- || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+ static String getPrefixForExternalSyntheticType(SyntheticKind kind, DexType type) {
+ String binaryName = type.toBinaryName();
+ int index =
+ binaryName.lastIndexOf(
+ kind.isFixedSuffixSynthetic ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR);
+ if (index < 0) {
+ throw new Unreachable("Unexpected failure to compute an synthetic prefix");
+ }
+ return binaryName.substring(0, index);
}
public static DexType createFixedType(
@@ -100,12 +109,12 @@
}
static DexType createExternalType(
- SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+ SyntheticKind kind, String externalSyntheticTypePrefix, String id, DexItemFactory factory) {
assert kind.isFixedSuffixSynthetic == id.isEmpty();
return createType(
kind.isFixedSuffixSynthetic ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
kind,
- context,
+ externalSyntheticTypePrefix,
id,
factory);
}
@@ -115,10 +124,19 @@
return factory.createType(createDescriptor(separator, kind, context.getInternalName(), id));
}
+ private static DexType createType(
+ String separator,
+ SyntheticKind kind,
+ String externalSyntheticTypePrefix,
+ String id,
+ DexItemFactory factory) {
+ return factory.createType(createDescriptor(separator, kind, externalSyntheticTypePrefix, id));
+ }
+
private static String createDescriptor(
- String separator, SyntheticKind kind, String context, String id) {
+ String separator, SyntheticKind kind, String externalSyntheticTypePrefix, String id) {
return DescriptorUtils.getDescriptorFromClassBinaryName(
- context + separator + kind.descriptor + id);
+ externalSyntheticTypePrefix + separator + kind.descriptor + id);
}
public static boolean verifyNotInternalSynthetic(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
index 7e798e0..5a17b04 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -36,7 +36,8 @@
}
@Override
- SyntheticProgramClassReference rewrite(NonIdentityGraphLens lens) {
+ SyntheticProgramClassReference internalRewrite(
+ SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
DexType rewritten = lens.lookupType(type);
// If the reference has been non-trivially rewritten the compiler has changed it and it can no
// longer be considered a synthetic. The context may or may not have changed.
@@ -46,20 +47,10 @@
assert SyntheticNaming.verifyNotInternalSynthetic(rewritten);
return null;
}
- SynthesizingContext context = getContext().rewrite(lens);
- if (context == getContext() && rewritten == type) {
+ if (rewrittenContext == getContext() && rewritten == type) {
return this;
}
- // Ensure that if a synthetic moves its context moves consistently.
- if (type != rewritten) {
- context =
- SynthesizingContext.fromSyntheticContextChange(
- getKind(), rewritten, context, lens.dexItemFactory());
- if (context == null) {
- return null;
- }
- }
- return new SyntheticProgramClassReference(getKind(), context, rewritten);
+ return new SyntheticProgramClassReference(getKind(), rewrittenContext, rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index 1a2d2fb..ddb28f7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.synthesis;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -20,13 +21,13 @@
C extends DexClass> {
private final SyntheticKind kind;
- private final SynthesizingContext context;
+ private final SynthesizingContext rewrittenContext;
SyntheticReference(SyntheticKind kind, SynthesizingContext context) {
assert kind != null;
assert context != null;
this.kind = kind;
- this.context = context;
+ this.rewrittenContext = context;
}
abstract D lookupDefinition(Function<DexType, DexClass> definitions);
@@ -36,10 +37,17 @@
}
final SynthesizingContext getContext() {
- return context;
+ return rewrittenContext;
}
abstract DexType getHolder();
- abstract R rewrite(NonIdentityGraphLens lens);
+ abstract DexReference getReference();
+
+ final R rewrite(NonIdentityGraphLens lens) {
+ SynthesizingContext rewrittenContext = getContext().rewrite(lens);
+ return internalRewrite(rewrittenContext, lens);
+ }
+
+ abstract R internalRewrite(SynthesizingContext rewrittenContext, NonIdentityGraphLens lens);
}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
new file mode 100644
index 0000000..a203729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2021, 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageFeatureWithSyntheticsTest extends RepackageTestBase {
+
+ public RepackageFeatureWithSyntheticsTest(
+ String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+ super(flattenPackageHierarchyOrRepackageClasses, parameters);
+ }
+
+ private static final Class<?> FIRST_FOO =
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.Foo.class;
+
+ private static final Class<?> FIRST_PKG_PRIVATE =
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
+ .PkgProtectedMethod.class;
+
+ private static final List<Class<?>> FIRST_CLASSES =
+ ImmutableList.of(FIRST_FOO, FIRST_PKG_PRIVATE);
+
+ private static final List<Class<?>> FIRST_FIRST_CLASSES =
+ ImmutableList.of(
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first.Foo
+ .class,
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
+ .PkgProtectedMethod.class);
+
+ private static List<Class<?>> getTestClasses() {
+ return ImmutableList.<Class<?>>builder()
+ .addAll(getBaseClasses())
+ .add(TestClass.class)
+ .add(I.class)
+ .build();
+ }
+
+ private static List<Class<?>> getBaseClasses() {
+ return FIRST_CLASSES;
+ }
+
+ private static List<Class<?>> getFeatureClasses() {
+ return FIRST_FIRST_CLASSES;
+ }
+
+ private static String EXPECTED = StringUtils.lines("first.Foo", "first.first.Foo");
+
+ @Override
+ public String getRepackagePackage() {
+ return "dest";
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getTestClasses())
+ .addProgramClasses(getFeatureClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue("Feature splits require DEX output.", parameters.isDexRuntime());
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getTestClasses())
+ .addFeatureSplit(getFeatureClasses().toArray(new Class<?>[0]))
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
+ .PkgProtectedMethod.class,
+ com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
+ .first.PkgProtectedMethod.class)
+ .addKeepClassAndMembersRules(I.class)
+ .addKeepMethodRules(
+ Reference.methodFromMethod(TestClass.class.getDeclaredMethod("run", I.class)))
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .noMinification()
+ .apply(this::configureRepackaging)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ // Each Foo class will give rise to a single lambda.
+ int expectedSyntheticsInBase = 1;
+ int expectedSyntheticsInFeature = 1;
+
+ // Check that the first.Foo is repackaged but that the pkg private access class is not.
+ // The implies that the lambda which is doing a package private access cannot be repackaged.
+ // If it is, the access will fail resulting in a runtime exception.
+ CodeInspector baseInspector = compileResult.inspector();
+ assertThat(FIRST_FOO, isRepackagedAsExpected(baseInspector, "first"));
+ assertThat(FIRST_PKG_PRIVATE, isNotRepackaged(baseInspector));
+ assertEquals(
+ getTestClasses().size() + expectedSyntheticsInBase, baseInspector.allClasses().size());
+
+ // The feature first.first.Foo is not repackaged and neither is the lambda.
+ // TODO(b/180086194): first.first.Foo should have been repackaged, but is currently identified
+ // as being in 'base' when inlining takes place.
+ CodeInspector featureInspector = new CodeInspector(compileResult.getFeature(0));
+ getFeatureClasses().forEach(c -> assertThat(c, isNotRepackaged(featureInspector)));
+ assertEquals(
+ getFeatureClasses().size() + expectedSyntheticsInFeature,
+ featureInspector.allClasses().size());
+
+ compileResult
+ .addFeatureSplitsToRunClasspathFiles()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("first.Foo", "first.first.Foo");
+ }
+
+ public interface I {
+ void run();
+ }
+
+ public static class TestClass {
+
+ // Public kept run method to accept a lambda ensuring desugaring which cannot be optimized out.
+ public static void run(I i) {
+ i.run();
+ }
+
+ public static void main(String[] args) {
+ new com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.Foo();
+ new com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
+ .Foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/Foo.java
new file mode 100644
index 0000000..e7b1cf9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/Foo.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.repackage.testclasses.repackagefeaturewithsynthetics.first;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.repackage.RepackageFeatureWithSyntheticsTest.TestClass;
+
+@NeverClassInline
+public class Foo {
+
+ public Foo() {
+ TestClass.run(() -> PkgProtectedMethod.getStream().println("first.Foo"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/PkgProtectedMethod.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/PkgProtectedMethod.java
new file mode 100644
index 0000000..10ddc99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/PkgProtectedMethod.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.repackage.testclasses.repackagefeaturewithsynthetics.first;
+
+import java.io.PrintStream;
+
+public class PkgProtectedMethod {
+
+ /* package protected */
+ static PrintStream getStream() {
+ return System.out;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/Foo.java
new file mode 100644
index 0000000..dcee445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/Foo.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.repackage.testclasses.repackagefeaturewithsynthetics.first.first;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.repackage.RepackageFeatureWithSyntheticsTest.TestClass;
+
+@NeverClassInline
+public class Foo {
+
+ public Foo() {
+ TestClass.run(() -> PkgProtectedMethod.getStream().println("first.first.Foo"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/PkgProtectedMethod.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/PkgProtectedMethod.java
new file mode 100644
index 0000000..0473a26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagefeaturewithsynthetics/first/first/PkgProtectedMethod.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.repackage.testclasses.repackagefeaturewithsynthetics.first.first;
+
+import java.io.PrintStream;
+
+public class PkgProtectedMethod {
+
+ /* package protected */
+ static PrintStream getStream() {
+ return System.out;
+ }
+}