Version 1.7.1-dev

Merge commit '706f75519ffa158611a9934893ba79ad638b9b81' into 1.7
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index ef7e2a6..52963e9 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 = "1.7.0-dev";
+  public static final String LABEL = "1.7.1-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index 14833f5..f645d0b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -31,6 +31,8 @@
     IsLibraryMethod,
     OverridingMethod,
     MethodHandleUseFrom,
+    CompanionClass,
+    CompanionMethod,
     Unknown
   }
 
@@ -79,6 +81,10 @@
         return "defined in library method overridden by";
       case MethodHandleUseFrom:
         return "referenced by method handle";
+      case CompanionClass:
+        return "companion class for";
+      case CompanionMethod:
+        return "companion method for";
       default:
         assert false : "Unknown edge kind: " + edgeKind();
         // fall through
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 8b2e770..e77f932 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -58,6 +58,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -67,6 +68,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -322,6 +324,7 @@
 
   private final GraphReporter graphReporter;
   private final GraphConsumer keptGraphConsumer;
+  private CollectingGraphConsumer verificationGraphConsumer = null;
 
   Enqueuer(
       AppView<? extends AppInfoWithSubtyping> appView,
@@ -335,7 +338,7 @@
     this.compatibility = compatibility;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter();
-    this.keptGraphConsumer = keptGraphConsumer;
+    this.keptGraphConsumer = recordKeptGraph(options, keptGraphConsumer);
     this.mode = mode;
     this.options = options;
     this.workList = EnqueuerWorklist.createWorklist(appView);
@@ -1669,8 +1672,10 @@
   private void markStaticFieldAsLive(DexEncodedField encodedField, KeepReason reason) {
     // Mark the type live here, so that the class exists at runtime.
     DexField field = encodedField.field;
-    markTypeAsLive(field.holder, reason);
-    markTypeAsLive(field.type, reason);
+    markTypeAsLive(
+        field.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, encodedField));
+    markTypeAsLive(
+        field.type, clazz -> graphReporter.reportClassReferencedFrom(clazz, encodedField));
 
     DexProgramClass clazz = getProgramClassOrNull(field.holder);
     if (clazz == null) {
@@ -1804,8 +1809,10 @@
       Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
     }
 
-    markTypeAsLive(field.holder, reason);
-    markTypeAsLive(field.type, reason);
+    markTypeAsLive(
+        field.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, encodedField));
+    markTypeAsLive(
+        field.type, clazz -> graphReporter.reportClassReferencedFrom(clazz, encodedField));
 
     DexProgramClass clazz = getProgramClassOrNull(field.holder);
     if (clazz == null) {
@@ -1820,7 +1827,6 @@
       markStaticFieldAsLive(encodedField, reason);
     } else {
       if (isInstantiatedOrHasInstantiatedSubtype(clazz)) {
-        // We have at least one live subtype, so mark it as live.
         markInstanceFieldAsLive(encodedField, reason);
       } else {
         // Add the field to the reachable set if the type later becomes instantiated.
@@ -2110,9 +2116,51 @@
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     analyses.forEach(EnqueuerAnalysis::done);
+    assert verifyKeptGraph();
     return createAppInfo(appInfo);
   }
 
+  private GraphConsumer recordKeptGraph(InternalOptions options, GraphConsumer consumer) {
+    if (options.testing.verifyKeptGraphInfo) {
+      verificationGraphConsumer = new CollectingGraphConsumer(consumer);
+      return verificationGraphConsumer;
+    }
+    return consumer;
+  }
+
+  private boolean verifyKeptGraph() {
+    if (options.testing.verifyKeptGraphInfo) {
+      assert verificationGraphConsumer != null;
+      for (DexProgramClass liveType : liveTypes.items) {
+        assert verifyRootedPath(liveType, verificationGraphConsumer);
+      }
+    }
+    return true;
+  }
+
+  private boolean verifyRootedPath(DexProgramClass liveType, CollectingGraphConsumer graph) {
+    ClassGraphNode node = getClassGraphNode(liveType.type);
+    Set<GraphNode> seen = Sets.newIdentityHashSet();
+    Deque<GraphNode> targets = DequeUtils.newArrayDeque(node);
+    while (!targets.isEmpty()) {
+      GraphNode item = targets.pop();
+      if (item instanceof KeepRuleGraphNode) {
+        KeepRuleGraphNode rule = (KeepRuleGraphNode) item;
+        if (rule.getPreconditions().isEmpty()) {
+          return true;
+        }
+      }
+      if (seen.add(item)) {
+        Map<GraphNode, Set<GraphEdgeInfo>> sources = graph.getSourcesTargeting(item);
+        assert sources != null : "No sources set for " + item;
+        assert !sources.isEmpty() : "Empty sources set for " + item;
+        targets.addAll(sources.keySet());
+      }
+    }
+    assert false : "No rooted path to " + liveType.type;
+    return true;
+  }
+
   private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
     ImmutableSortedSet.Builder<DexType> builder =
         ImmutableSortedSet.orderedBy(PresortedComparable::slowCompareTo);
@@ -2367,8 +2415,11 @@
           DexEncodedMethod implementation = target.getDefaultInterfaceMethodImplementation();
           if (implementation != null) {
             DexProgramClass companion = getProgramClassOrNull(implementation.method.holder);
-            markTypeAsLive(companion, reason);
-            markVirtualMethodAsLive(companion, implementation, reason);
+            markTypeAsLive(companion, graphReporter.reportCompanionClass(holder, companion));
+            markVirtualMethodAsLive(
+                companion,
+                implementation,
+                graphReporter.reportCompanionMethod(target, implementation));
           }
         }
       }
@@ -3128,6 +3179,40 @@
       return KeepReasonWitness.INSTANCE;
     }
 
+    public KeepReasonWitness reportClassReferencedFrom(
+        DexProgramClass clazz, DexEncodedField field) {
+      if (keptGraphConsumer != null) {
+        FieldGraphNode source = getFieldGraphNode(field.field);
+        ClassGraphNode target = getClassGraphNode(clazz.type);
+        return reportEdge(source, target, EdgeKind.ReferencedFrom);
+      }
+      return KeepReasonWitness.INSTANCE;
+    }
+
+    private KeepReason reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
+      assert iface.isInterface();
+      assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
+      if (keptGraphConsumer == null) {
+        return KeepReasonWitness.INSTANCE;
+      }
+      return reportEdge(
+          getClassGraphNode(iface.type),
+          getClassGraphNode(companion.type),
+          EdgeKind.CompanionClass);
+    }
+
+    private KeepReason reportCompanionMethod(
+        DexEncodedMethod definition, DexEncodedMethod implementation) {
+      assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
+      if (keptGraphConsumer == null) {
+        return KeepReasonWitness.INSTANCE;
+      }
+      return reportEdge(
+          getMethodGraphNode(definition.method),
+          getMethodGraphNode(implementation.method),
+          EdgeKind.CompanionMethod);
+    }
+
     private KeepReasonWitness reportEdge(
         GraphNode source, GraphNode target, GraphEdgeInfo.EdgeKind kind) {
       assert keptGraphConsumer != null;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d202fd8..2e45d5f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -972,6 +972,7 @@
 
     // Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
     public boolean enableForceNestBasedAccessDesugaringForTest = false;
+    public boolean verifyKeptGraphInfo = false;
 
     public boolean desugarLambdasThroughLensCodeRewriter() {
       return enableStatefulLambdaCreateInstanceMethod;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index c3dd579..4dba762 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -84,6 +84,10 @@
 
   private void configure(InternalOptions options) {
     options.enableVerticalClassMerging = enableVerticalClassMerging;
+    // TODO(b/141093535): The precondition set for conditionals is currently based on the syntactic
+    // form, when merging is enabled, if the precondition is merged to a differently named type, the
+    // rule will still fire, but the reported precondition type is incorrect.
+    options.testing.verifyKeptGraphInfo = !enableVerticalClassMerging;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 0c06960..a43e42c 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -82,10 +82,11 @@
     inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
 
     // The field is primarily kept by the reflective lookup in main.
-    inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+    QueryNode fooNode = inspector.field(fooField).assertRenamed();
+    fooNode.assertReflectedFrom(mainMethod);
 
     // The field is also kept by the write in Foo.<init>.
     // We may want to change that behavior. See b/124428834.
-    inspector.field(fooField).assertRenamed().assertKeptBy(inspector.method(fooInit));
+    fooNode.assertKeptBy(inspector.method(fooInit));
   }
 }
diff --git a/tools/archive.py b/tools/archive.py
index 32c5c32..1041b40 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -59,18 +59,18 @@
   # commits to archive these to the hash based location
   if len(branches) == 0:
     return True
-  if not version.endswith('-dev'):
+  if not version == 'master':
     # Sanity check, we don't want to archive on top of release builds EVER
     # Note that even though we branch, we never push the bots to build the same
     # commit as master on a branch since we always change the version to
-    # not have dev (or we crash here :-)).
+    # not be just 'master' (or we crash here :-)).
     if 'origin/master' in branches:
       raise Exception('We are seeing origin/master in a commit that '
-                      'don\'t have -dev in version')
+                      'don\'t have \'master\' as version')
     return False
   if not 'origin/master' in branches:
       raise Exception('We are not seeing origin/master '
-                      'in a commit that have -dev in version')
+                      'in a commit that have \'master\' as version')
   return True
 
 def GetStorageDestination(storage_prefix,
@@ -239,9 +239,10 @@
           if options.dry_run:
             print('Dry run, not actually creating maven repo for '
                 + 'desugar configuration.')
-            shutil.copyfile(
-                desugar_jdk_libs_configuration_jar,
-                os.path.join(options.dry_run_output, jar_name))
+            if options.dry_run_output:
+              shutil.copyfile(
+                  desugar_jdk_libs_configuration_jar,
+                  os.path.join(options.dry_run_output, jar_name))
           else:
             utils.upload_file_to_cloud_storage(
                 desugar_jdk_libs_configuration_jar, maven_dst)