Add simple logging of reachable resource entries

This enables simple logging for the reachable entries in the resource
table when using optimized shrinking.

Bug: b/360284664
Bug: b/287398085
Change-Id: I69a88568870caea86baaf4c6f93b2179d98898b3
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index 5e52e33..660504d 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -65,7 +65,11 @@
                   && definition.getStaticValue().isDexValueResourceNumber()) {
                 appView
                     .getResourceShrinkerState()
-                    .trace(definition.getStaticValue().asDexValueResourceNumber().getValue());
+                    .trace(
+                        definition.getStaticValue().asDexValueResourceNumber().getValue(),
+                        // TODO(b/378625969): Consider wrapping this in a reachability structure
+                        // to avoid decoding.
+                        field.toString());
               }
             }
           });
@@ -110,7 +114,7 @@
       // these.
       if (integers != null) {
         for (Integer integer : integers) {
-          resourceShrinkerState.trace(integer);
+          resourceShrinkerState.trace(integer, field.toString());
         }
       }
     }
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 df58f3d..cb5e723 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1129,7 +1129,7 @@
   }
 
   public void traceResourceValue(int value) {
-    appView.getResourceShrinkerState().trace(value);
+    appView.getResourceShrinkerState().trace(value, "from dex");
   }
 
   public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index fccc50d..34e581b 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -44,6 +44,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -58,11 +59,13 @@
   private final List<Supplier<InputStream>> manifestProviders = new ArrayList<>();
   private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
   private final Map<FeatureSplit, ResourceTable> resourceTables = new HashMap<>();
+  private final ShrinkerDebugReporter shrinkerDebugReporter;
   private ClassReferenceCallback enqueuerCallback;
   private Map<Integer, List<String>> resourceIdToXmlFiles;
   private Set<String> packageNames;
   private final Set<String> seenNoneClassValues = new HashSet<>();
   private final Set<Integer> seenResourceIds = new HashSet<>();
+  private final Map<Resource, String> reachabilityMap = new ConcurrentHashMap<>();
 
   private static final Set<String> SPECIAL_MANIFEST_ELEMENTS =
       ImmutableSet.of(
@@ -86,10 +89,11 @@
       Function<Exception, RuntimeException> errorHandler,
       ShrinkerDebugReporter shrinkerDebugReporter) {
     r8ResourceShrinkerModel = new R8ResourceShrinkerModel(shrinkerDebugReporter, true);
+    this.shrinkerDebugReporter = shrinkerDebugReporter;
     this.errorHandler = errorHandler;
   }
 
-  public void trace(int id) {
+  public void trace(int id, String reachableFrom) {
     if (!seenResourceIds.add(id)) {
       return;
     }
@@ -97,12 +101,17 @@
     if (resource == null) {
       return;
     }
+    assert reachableFrom != null;
+
+    // For deterministic output, sort the strings lexicographically.
+    reachabilityMap.compute(
+        resource, (r, v) -> v == null || v.compareTo(reachableFrom) > 0 ? reachableFrom : v);
     ResourceUsageModel.markReachable(resource);
     traceXmlForResourceId(id);
     if (resource.references != null) {
       for (Resource reference : resource.references) {
         if (!reference.isReachable()) {
-          trace(reference.value);
+          trace(reference.value, reference.toString());
         }
       }
     }
@@ -122,7 +131,7 @@
     r8ResourceShrinkerModel
         .getResourceStore()
         .processToolsAttributes()
-        .forEach(resource -> trace(resource.value));
+        .forEach(resource -> trace(resource.value, "keep xml file"));
     for (Supplier<InputStream> manifestProvider : manifestProviders) {
       traceXml("AndroidManifest.xml", manifestProvider.get());
     }
@@ -212,6 +221,13 @@
               featureSplit,
               ResourceTableUtilKt.nullOutEntriesWithIds(resourceTable, resourceIdsToRemove, true));
         });
+    for (Map.Entry<Resource, String> resourceStringEntry : reachabilityMap.entrySet()) {
+      shrinkerDebugReporter.debug(
+          () ->
+              resourceStringEntry.getKey().toString()
+                  + " reachable from "
+                  + resourceStringEntry.getValue());
+    }
     return new ShrinkerResult(resEntriesToKeep, shrunkenTables);
   }
 
@@ -243,7 +259,7 @@
       // resources for the reachable marker.
       ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(xmlNode, r8ResourceShrinkerModel)
           .iterator()
-          .forEachRemaining(resource -> trace(resource.value));
+          .forEachRemaining(resource -> trace(resource.value, xmlFile));
     } catch (IOException e) {
       errorHandler.apply(e);
     }
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
index 1028b38..67dbef6 100644
--- a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
@@ -118,9 +118,22 @@
       ensureResourceReachabilityState(strings, "drawable", "foobar", true);
       ensureRootResourceState(strings, "drawable", "foobar", true);
       ensureUnusedState(strings, "drawable", "foobar", false);
+    } else {
+      assertTrue(finished.get());
+      List<String> strings = StringUtils.splitLines(log.toString());
+      ensureReachableOptimized(strings, "string", "bar", true);
+      ensureReachableOptimized(strings, "string", "foo", true);
+      ensureReachableOptimized(strings, "drawable", "foobar", true);
+      ensureReachableOptimized(strings, "drawable", "unused_drawable", false);
     }
   }
 
+  private void ensureReachableOptimized(
+      List<String> logStrings, String type, String name, boolean reachable) {
+    assertEquals(
+        logStrings.stream().anyMatch(s -> s.startsWith(type + ":" + name + ":")), reachable);
+  }
+
   private void ensureDexReachableResourcesState(
       List<String> logStrings, String type, String name, boolean reachable) {
     // Example line: