Fix record rewriting with dontobfuscate dontshrink

Change-Id: I6678ecb4830465c1a232fcfafc79f4bf1809be9d
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index da49f2e..1083cb8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplicationReadFlags;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -678,9 +679,11 @@
       timing.begin("Run postlude");
       performFinalMainDexTracing(appView, executorService);
 
-      if (appView.appInfo().hasLiveness()) {
+      // If there was a reference to java.lang.Record in the input, we rewrite the record values.
+      DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
+      if (flags.hasReadRecordReferenceFromProgramClass()) {
         RecordFieldValuesRewriter recordFieldArrayRemover =
-            RecordFieldValuesRewriter.create(appView.withLiveness());
+            RecordFieldValuesRewriter.create(appView);
         if (recordFieldArrayRemover != null) {
           recordFieldArrayRemover.rewriteRecordFieldValues();
         }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index aaa4dad..b742d92 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -951,6 +951,11 @@
     return (AppView<AppInfo>) this;
   }
 
+  @SuppressWarnings("unchecked")
+  public AppView<AppInfo> unsafeWithoutClassHierarchy() {
+    return (AppView<AppInfo>) this;
+  }
+
   public boolean hasLiveness() {
     return appInfo().hasLiveness();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index dcc3abf..13b059d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -222,8 +222,7 @@
                 && appView.options().canHaveVerifyErrorForUnknownUnusedReturnValue())
             ? new RemoveVerificationErrorForUnknownReturnedValues(appView)
             : null;
-    if (appView.enableWholeProgramOptimizations()) {
-      assert appView.appInfo().hasLiveness();
+    if (appView.enableWholeProgramOptimizations() && appView.appInfo().hasLiveness()) {
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       assumeInserter = new AssumeInserter(appViewWithLiveness);
@@ -254,7 +253,10 @@
           options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
       this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
     } else {
-      AppView<AppInfo> appViewWithoutClassHierarchy = appView.withoutClassHierarchy();
+      AppView<AppInfo> appViewWithoutClassHierarchy =
+          appView.enableWholeProgramOptimizations()
+              ? appView.unsafeWithoutClassHierarchy()
+              : appView.withoutClassHierarchy();
       this.assumeInserter = null;
       this.classInliner = null;
       this.dynamicTypeOptimization = null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
index 71e0780..839fc6a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
 import java.util.List;
@@ -32,17 +32,17 @@
 /** Used to shrink record field arrays in dex compilations */
 public class RecordFieldValuesRewriter {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<?> appView;
   private final IRConverter irConverter;
 
-  public static RecordFieldValuesRewriter create(AppView<AppInfoWithLiveness> appView) {
+  public static RecordFieldValuesRewriter create(AppView<?> appView) {
     if (appView.enableWholeProgramOptimizations() && appView.options().isGeneratingDex()) {
       return new RecordFieldValuesRewriter(appView);
     }
     return null;
   }
 
-  private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) {
+  private RecordFieldValuesRewriter(AppView<?> appView) {
     this.appView = appView;
     irConverter = new IRConverter(appView);
   }
@@ -50,18 +50,32 @@
   // Called after final tree shaking, prune and minify field names and field values.
   // At least one instruction is a newRecordFieldArray.
   public void rewriteRecordFieldValues() {
-    for (DexMethod recordFieldValuesReference : appView.appInfo().recordFieldValuesReferences) {
-      DexClass dexClass =
-          appView.contextIndependentDefinitionFor(recordFieldValuesReference.getHolderType());
-      assert dexClass.isProgramClass();
-      ProgramMethod programMethod =
-          dexClass.asProgramClass().lookupProgramMethod(recordFieldValuesReference);
-      assert programMethod != null;
-      rewriteRecordFieldValues(programMethod);
+    if (appView.hasLiveness()) {
+      for (DexMethod recordFieldValuesReference :
+          appView.appInfo().withLiveness().recordFieldValuesReferences) {
+        DexClass dexClass =
+            appView.contextIndependentDefinitionFor(recordFieldValuesReference.getHolderType());
+        assert dexClass.isProgramClass();
+        ProgramMethod programMethod =
+            dexClass.asProgramClass().lookupProgramMethod(recordFieldValuesReference);
+        assert programMethod != null;
+        rewriteRecordFieldValues(programMethod, false);
+      }
+    } else {
+      // Without liveness, i.e., R8 called with -dontshrink, we take a slow path and exhaustively
+      // process all methods.
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        clazz.forEachProgramMethod(
+            method -> {
+              if (method.getDefinition().hasCode()) {
+                rewriteRecordFieldValues(method, true);
+              }
+            });
+      }
     }
   }
 
-  public void rewriteRecordFieldValues(ProgramMethod programMethod) {
+  public void rewriteRecordFieldValues(ProgramMethod programMethod, boolean wide) {
     IRCode irCode =
         programMethod
             .getDefinition()
@@ -84,7 +98,7 @@
         }
       }
     }
-    assert done;
+    assert wide || done;
     irConverter.removeDeadCodeAndFinalizeIR(
         irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
diff --git a/src/test/examplesJava17/records/RecordHashCodeTest.java b/src/test/examplesJava17/records/RecordHashCodeTest.java
index 2f2609d..b0b0b0e 100644
--- a/src/test/examplesJava17/records/RecordHashCodeTest.java
+++ b/src/test/examplesJava17/records/RecordHashCodeTest.java
@@ -83,6 +83,28 @@
     builder.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
+  @Test
+  public void testR8DontShrinkDontObfuscate() throws Exception {
+    parameters.assumeR8TestParameters();
+    assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addInnerClassesAndStrippedOuter(getClass())
+            .setMinApi(parameters)
+            .addDontShrink()
+            .addDontObfuscate()
+            .addInliningAnnotations()
+            .addKeepMainRule(TestClass.class);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    builder.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
   public static class TestClass {
 
     record Person(String name, int age) {}