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())