Version 2.0.66
Cherry pick: Trace dependent items from consequent root set with already satisfied precondition
CL: https://r8-review.googlesource.com/c/r8/+/50361
Cherry pick: Keep bridge and synthetic information for conditional rules
CL: https://r8-review.googlesource.com/c/r8/+/50363
Cherry pick: Add a regression test for inadequate tracing of consequent root set
CL: https://r8-review.googlesource.com/c/r8/+/50360
Cherry pick: Add a format diff script in tools
CL: https://r8-review.googlesource.com/c/r8/+/49823
Bug: 153858923, 153926577, 132828740
Change-Id: Ibaa1d5b757c605b8f50881bdbf3998a3c200f3e6
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7919492..3b19a4a 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.0.65";
+ public static final String LABEL = "2.0.66";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 61def68..c709a31 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -225,6 +225,10 @@
unset(Constants.ACC_SYNTHETIC);
}
+ public void demoteFromSynthetic() {
+ demote(Constants.ACC_SYNTHETIC);
+ }
+
public void promoteToFinal() {
promote(Constants.ACC_FINAL);
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 1b8e3a0..c91b62d 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -144,6 +144,10 @@
unset(Constants.ACC_BRIDGE);
}
+ public void demoteFromBridge() {
+ demote(Constants.ACC_BRIDGE);
+ }
+
public boolean isVarargs() {
return isSet(Constants.ACC_VARARGS);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index ab3274d..9876ae8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -973,10 +973,10 @@
// The synthetic and bridge flags are maintained only if the inlinee has also these flags.
if (context.accessFlags.isBridge() && !inlinee.code.method.accessFlags.isBridge()) {
- context.accessFlags.unsetBridge();
+ context.accessFlags.demoteFromBridge();
}
if (context.accessFlags.isSynthetic() && !inlinee.code.method.accessFlags.isSynthetic()) {
- context.accessFlags.unsetSynthetic();
+ context.accessFlags.demoteFromSynthetic();
}
context.copyMetadata(singleTarget);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index cdbeeb4..05108dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1962,6 +1962,14 @@
return directAndIndirectlyInstantiatedTypes.contains(clazz);
}
+ public boolean isMemberLive(DexDefinition member) {
+ assert member != null;
+ assert member.isDexEncodedField() || member.isDexEncodedMethod();
+ return member.isDexEncodedField()
+ ? liveFields.contains(member.asDexEncodedField())
+ : liveMethods.contains(member.asDexEncodedMethod());
+ }
+
public boolean isMethodLive(DexEncodedMethod method) {
return liveMethods.contains(method);
}
@@ -2483,12 +2491,32 @@
}
private void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+ consequentRootSet.forEachClassWithDependentItems(
+ appView,
+ clazz -> {
+ if (isTypeLive(clazz)) {
+ consequentRootSet.forEachDependentInstanceConstructor(
+ clazz, appView, this::enqueueHolderWithDependentInstanceConstructor);
+ consequentRootSet.forEachDependentStaticMember(
+ clazz, appView, this::enqueueDependentItem);
+ if (isInstantiatedOrHasInstantiatedSubtype(clazz)) {
+ consequentRootSet.forEachDependentNonStaticMember(
+ clazz, appView, this::enqueueDependentItem);
+ }
+ compatEnqueueHolderIfDependentNonStaticMember(
+ clazz, consequentRootSet.getDependentKeepClassCompatRule(clazz.type));
+ }
+ });
+ consequentRootSet.forEachMemberWithDependentItems(
+ appView,
+ member -> {
+ if (isMemberLive(member)) {
+ enqueueRootItems(consequentRootSet.getDependentItems(member));
+ }
+ });
// TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
enqueueRootItems(consequentRootSet.noShrinking);
- // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
- consequentRootSet.dependentNoShrinking.forEach(
- (precondition, dependentItems) -> enqueueRootItems(dependentItems));
// Check for compatibility rules indicating that the holder must be implicitly kept.
if (forceProguardCompatibility) {
consequentRootSet.dependentKeepClassCompatRule.forEach(
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 6ae1edb..3df34bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,6 +3,8 @@
// 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.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -14,6 +16,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -1222,33 +1225,135 @@
}
}
- public static class RootSet {
+ abstract static class RootSetBase {
- public final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
- public final Set<DexReference> noOptimization;
- private final Set<DexReference> noObfuscation;
+ final Set<DexMethod> neverInline;
+ final Set<DexType> neverClassInline;
+ final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
+ final Set<DexReference> noObfuscation;
+ final Set<DexReference> noOptimization;
+ final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
+ final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
+ final List<DelayedRootSetActionItem> delayedRootSetActionItems;
+
+ RootSetBase(
+ Set<DexMethod> neverInline,
+ Set<DexType> neverClassInline,
+ Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+ Set<DexReference> noObfuscation,
+ Set<DexReference> noOptimization,
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+ Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+ this.neverInline = neverInline;
+ this.neverClassInline = neverClassInline;
+ this.noShrinking = noShrinking;
+ this.noObfuscation = noObfuscation;
+ this.noOptimization = noOptimization;
+ this.dependentNoShrinking = dependentNoShrinking;
+ this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
+ this.delayedRootSetActionItems = delayedRootSetActionItems;
+ }
+
+ public void forEachClassWithDependentItems(
+ DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
+ for (DexReference reference : dependentNoShrinking.keySet()) {
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+ if (clazz != null) {
+ consumer.accept(clazz);
+ }
+ }
+ }
+ }
+
+ public void forEachMemberWithDependentItems(
+ DexDefinitionSupplier definitions, Consumer<DexDefinition> consumer) {
+ for (DexReference reference : dependentNoShrinking.keySet()) {
+ if (reference.isDexField() || reference.isDexMethod()) {
+ DexDefinition definition = definitions.definitionFor(reference);
+ if (definition != null) {
+ consumer.accept(definition);
+ }
+ }
+ }
+ }
+
+ public void forEachDependentInstanceConstructor(
+ DexProgramClass clazz,
+ AppView<?> appView,
+ Consumer3<DexProgramClass, DexEncodedMethod, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(clazz)
+ .forEach(
+ (reference, reasons) -> {
+ DexDefinition definition = appView.definitionFor(reference);
+ if (definition != null
+ && definition.isDexEncodedMethod()
+ && definition.asDexEncodedMethod().isInstanceInitializer()) {
+ fn.accept(clazz, definition.asDexEncodedMethod(), reasons);
+ }
+ });
+ }
+
+ public void forEachDependentNonStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(item)
+ .forEach(
+ (reference, reasons) -> {
+ DexDefinition definition = appView.definitionFor(reference);
+ if (definition != null
+ && !definition.isDexClass()
+ && !definition.isStaticMember()) {
+ fn.accept(item, definition, reasons);
+ }
+ });
+ }
+
+ public void forEachDependentStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(item)
+ .forEach(
+ (reference, reasons) -> {
+ DexDefinition definition = appView.definitionFor(reference);
+ if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
+ fn.accept(item, definition, reasons);
+ }
+ });
+ }
+
+ Map<DexReference, Set<ProguardKeepRuleBase>> getDependentItems(DexDefinition item) {
+ return Collections.unmodifiableMap(
+ dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
+ }
+
+ Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
+ return dependentKeepClassCompatRule.get(type);
+ }
+ }
+
+ public static class RootSet extends RootSetBase {
+
public final ImmutableList<DexReference> reasonAsked;
public final ImmutableList<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
- public final Set<DexMethod> neverInline;
public final Set<DexMethod> bypassClinitForInlining;
public final Set<DexMethod> whyAreYouNotInlining;
public final Set<DexMethod> keepConstantArguments;
public final Set<DexMethod> keepUnusedArguments;
public final Set<DexType> alwaysClassInline;
- public final Set<DexType> neverClassInline;
public final Set<DexType> neverMerge;
public final Set<DexReference> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
public final Map<DexReference, ProguardMemberRule> noSideEffects;
public final Map<DexReference, ProguardMemberRule> assumedValues;
- private final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>>
- dependentNoShrinking;
- private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
- public final List<DelayedRootSetActionItem> delayedRootSetActionItems;
private RootSet(
Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
@@ -1275,30 +1380,31 @@
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
- this.noShrinking = noShrinking;
- this.noOptimization = noOptimization;
- this.noObfuscation = noObfuscation;
+ super(
+ neverInline,
+ neverClassInline,
+ noShrinking,
+ noObfuscation,
+ noOptimization,
+ dependentNoShrinking,
+ dependentKeepClassCompatRule,
+ delayedRootSetActionItems);
this.reasonAsked = reasonAsked;
this.checkDiscarded = checkDiscarded;
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
- this.neverInline = neverInline;
this.bypassClinitForInlining = bypassClinitForInlining;
this.whyAreYouNotInlining = whyAreYouNotInlining;
this.keepConstantArguments = keepConstantArguments;
this.keepUnusedArguments = keepUnusedArguments;
this.alwaysClassInline = alwaysClassInline;
- this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
- this.dependentNoShrinking = dependentNoShrinking;
- this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
- this.delayedRootSetActionItems = delayedRootSetActionItems;
}
public void checkAllRulesAreUsed(InternalOptions options) {
@@ -1347,61 +1453,6 @@
.putAll(dependence));
}
- Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
- return dependentKeepClassCompatRule.get(type);
- }
-
- Map<DexReference, Set<ProguardKeepRuleBase>> getDependentItems(DexDefinition item) {
- return Collections.unmodifiableMap(
- dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
- }
-
- public void forEachDependentStaticMember(
- DexDefinition item,
- AppView<?> appView,
- Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(item)
- .forEach(
- (reference, reasons) -> {
- DexDefinition definition = appView.definitionFor(reference);
- if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
- fn.accept(item, definition, reasons);
- }
- });
- }
-
- public void forEachDependentNonStaticMember(
- DexDefinition item,
- AppView<?> appView,
- Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(item)
- .forEach(
- (reference, reasons) -> {
- DexDefinition definition = appView.definitionFor(reference);
- if (definition != null
- && !definition.isDexClass()
- && !definition.isStaticMember()) {
- fn.accept(item, definition, reasons);
- }
- });
- }
-
- public void forEachDependentInstanceConstructor(
- DexProgramClass clazz,
- AppView<?> appView,
- Consumer3<DexProgramClass, DexEncodedMethod, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(clazz)
- .forEach(
- (reference, reasons) -> {
- DexDefinition definition = appView.definitionFor(reference);
- if (definition != null
- && definition.isDexEncodedMethod()
- && definition.asDexEncodedMethod().isInstanceInitializer()) {
- fn.accept(clazz, definition.asDexEncodedMethod(), reasons);
- }
- });
- }
-
public void copy(DexReference original, DexReference rewritten) {
if (noShrinking.containsKey(original)) {
noShrinking.put(rewritten, noShrinking.get(original));
@@ -1624,17 +1675,9 @@
// A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
// keep rules.
- public static class ConsequentRootSet {
- final Set<DexMethod> neverInline;
- final Set<DexType> neverClassInline;
- final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
- final Set<DexReference> noOptimization;
- final Set<DexReference> noObfuscation;
- final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
- final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
- final List<DelayedRootSetActionItem> delayedRootSetActionItems;
+ public static class ConsequentRootSet extends RootSetBase {
- private ConsequentRootSet(
+ ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
@@ -1643,14 +1686,15 @@
Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
- this.neverInline = Collections.unmodifiableSet(neverInline);
- this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
- this.noShrinking = Collections.unmodifiableMap(noShrinking);
- this.noOptimization = Collections.unmodifiableSet(noOptimization);
- this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
- this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
- this.dependentKeepClassCompatRule = Collections.unmodifiableMap(dependentKeepClassCompatRule);
- this.delayedRootSetActionItems = Collections.unmodifiableList(delayedRootSetActionItems);
+ super(
+ neverInline,
+ neverClassInline,
+ noShrinking,
+ noObfuscation,
+ noOptimization,
+ dependentNoShrinking,
+ dependentKeepClassCompatRule,
+ delayedRootSetActionItems);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java
new file mode 100644
index 0000000..f65d00b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java
@@ -0,0 +1,80 @@
+// 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.shaking.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConsequentRootSetWithSatisfiedDependentItemsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ConsequentRootSetWithSatisfiedDependentItemsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ConsequentRootSetWithSatisfiedDependentItemsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + A.class.getTypeName(),
+ "-keepclassmembers class " + A.class.getTypeName() + "{",
+ " <init>();",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertFalse(aClassSubject.getDexClass().isAbstract());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ Class<?> clazz = System.currentTimeMillis() > 0 ? A.class : null;
+ instantiate(clazz);
+ }
+
+ static A instantiate(Class<?> clazz) throws Exception {
+ return (A) clazz.getDeclaredConstructor().newInstance();
+ }
+ }
+
+ static class A {
+
+ int x;
+
+ A() {
+ this(42);
+ }
+
+ A(int x) {
+ this.x = x;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 28588e7..8c31345 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -257,6 +257,15 @@
return;
}
+ if (shrinker.isR8()) {
+ // The -keepclassmembers rule should not keep Dependent nor DependentUser since DependentUser
+ // is never referenced.
+ assertThat(codeInspector.clazz(EmptyMainClassForIfOnClassTests.class), isPresent());
+ assertThat(codeInspector.clazz(Precondition.class), isPresent());
+ assertEquals(2, codeInspector.allClasses().size());
+ return;
+ }
+
ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSyntheticConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSyntheticConstructorTest.java
new file mode 100644
index 0000000..ff4f02d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSyntheticConstructorTest.java
@@ -0,0 +1,76 @@
+// 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.shaking.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoLongerSyntheticConstructorTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public NoLongerSyntheticConstructorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addProgramClassFileData(
+ transformer(A.class)
+ .setAccessFlags(A.class.getDeclaredConstructor(), AccessFlags::setSynthetic)
+ .transform())
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + A.class.getTypeName() + " {",
+ " synthetic <init>();",
+ "}",
+ "-keep class " + B.class.getTypeName())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new A());
+ }
+ }
+
+ static class A {
+
+ int x;
+
+ A() {
+ this(42);
+ }
+
+ A(int x) {
+ this.x = x;
+ }
+ }
+
+ static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 22a91f8..8486892 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
@@ -16,6 +17,7 @@
import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@@ -299,10 +301,23 @@
});
}
+ public ClassFileTransformer setSynthetic(Method method) {
+ return setAccessFlags(method, AccessFlags::setSynthetic);
+ }
+
+ public ClassFileTransformer setAccessFlags(
+ Constructor<?> constructor, Consumer<MethodAccessFlags> setter) {
+ return setAccessFlags(Reference.methodFromMethod(constructor), setter);
+ }
+
public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
+ return setAccessFlags(Reference.methodFromMethod(method), setter);
+ }
+
+ private ClassFileTransformer setAccessFlags(
+ MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {
- final MethodReference methodReference = Reference.methodFromMethod(method);
@Override
public MethodVisitor visitMethod(
diff --git a/tools/fmt-diff.py b/tools/fmt-diff.py
new file mode 100755
index 0000000..4e97f3a
--- /dev/null
+++ b/tools/fmt-diff.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# 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.
+
+import os
+import subprocess
+import sys
+
+import utils
+
+from subprocess import Popen, PIPE
+
+GOOGLE_JAVA_FORMAT_DIFF = os.path.join(
+ utils.THIRD_PARTY,
+ 'google-java-format',
+ 'google-java-format-google-java-format-1.7',
+ 'scripts',
+ 'google-java-format-diff.py')
+
+def main():
+ upstream = subprocess.check_output(['git', 'cl', 'upstream']).strip()
+ git_diff_process = Popen(['git', 'diff', '-U0', upstream], stdout=PIPE)
+ fmt_process = Popen(
+ ['python', GOOGLE_JAVA_FORMAT_DIFF, '-p1', '-i'],
+ stdin=git_diff_process.stdout)
+ git_diff_process.stdout.close()
+ fmt_process.communicate()
+
+if __name__ == '__main__':
+ sys.exit(main())