Merge commit '7d3ce88195d3a032449ea05fa906a96a619d7e2f' into dev-release

Change-Id: I385652421e0b66a696d6a02e055ff2870a0e386f
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 14aa18a..5e52e33 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueResourceNumber;
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
@@ -100,7 +101,7 @@
     if (enqueuer.isRClass(resolvedField.getHolder())) {
       DexType holderType = resolvedField.getHolderType();
       if (!fieldToValueMapping.containsKey(holderType)) {
-        populateRClassValues(resolvedField);
+        populateRClassValues(resolvedField.getHolder());
       }
       assert fieldToValueMapping.containsKey(holderType);
       RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType);
@@ -115,17 +116,17 @@
     }
   }
 
-  private void populateRClassValues(ProgramField field) {
+  private void populateRClassValues(DexProgramClass programClass) {
     // TODO(287398085): Pending discussions with the AAPT2 team, we might need to harden this
     // to not fail if we wrongly classify an unrelated class as R class in our heuristic..
     RClassFieldToValueStore.Builder rClassValueBuilder = new RClassFieldToValueStore.Builder();
-    analyzeStaticFields(field, rClassValueBuilder);
-    ProgramMethod programClassInitializer = field.getHolder().getProgramClassInitializer();
+    analyzeStaticFields(programClass, rClassValueBuilder);
+    ProgramMethod programClassInitializer = programClass.getProgramClassInitializer();
     if (programClassInitializer != null) {
       analyzeClassInitializer(rClassValueBuilder, programClassInitializer);
     }
-    warnOnFinalIdFields(field.getHolder());
-    fieldToValueMapping.put(field.getHolderType(), rClassValueBuilder.build());
+    warnOnFinalIdFields(programClass);
+    fieldToValueMapping.put(programClass.getType(), rClassValueBuilder.build());
   }
 
   private void warnOnFinalIdFields(DexProgramClass holder) {
@@ -201,13 +202,15 @@
   }
 
   private void analyzeStaticFields(
-      ProgramField field, RClassFieldToValueStore.Builder rClassValueBuilder) {
+      DexProgramClass programClass, RClassFieldToValueStore.Builder rClassValueBuilder) {
     for (DexEncodedField staticField :
-        field.getHolder().staticFields(DexEncodedField::hasExplicitStaticValue)) {
+        programClass.staticFields(DexEncodedField::hasExplicitStaticValue)) {
       DexValue staticValue = staticField.getStaticValue();
       if (staticValue.isDexValueInt()) {
         IntList values = new IntArrayList(1);
         values.add(staticValue.asDexValueInt().getValue());
+        staticField.setStaticValue(
+            DexValueResourceNumber.create(staticValue.asDexValueInt().value));
         rClassValueBuilder.addMapping(staticField.getReference(), values);
       }
     }
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 20db4fc..30d420d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3280,11 +3280,6 @@
       // Already live.
       return;
     }
-
-    assert !field.getAccessFlags().isStatic()
-        || !field.getDefinition().hasExplicitStaticValue()
-        || !field.getDefinition().getStaticValue().isDexValueResourceNumber()
-        || mode.isFinalTreeShaking();
     addEffectivelyLiveOriginalField(field);
 
     // Mark the field as targeted.
diff --git a/src/test/java/com/android/tools/r8/androidresources/NonFinalRClassFieldsWithStaticNonCLInitValueTest.java b/src/test/java/com/android/tools/r8/androidresources/NonFinalRClassFieldsWithStaticNonCLInitValueTest.java
new file mode 100644
index 0000000..4590f8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/NonFinalRClassFieldsWithStaticNonCLInitValueTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2024, 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.androidresources;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonFinalRClassFieldsWithStaticNonCLInitValueTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.drawable.class)
+        // Use static value integers for the fields (i.e., no <clinit>).
+        .useStaticValueForNonFinalFields()
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(FooBar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(FooBar.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("drawable", "foo");
+              resourceTableInspector.assertDoesNotContainResourceWithName("drawable", "bar");
+            });
+  }
+
+  public static class FooBar {
+    public static void main(String[] args) {
+      System.out.println(R.drawable.foo);
+    }
+  }
+
+  public static class R {
+    public static class drawable {
+      public static int foo;
+      public static int bar;
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index 2f9a852..92e0618 100644
--- a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.utils.StreamUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.Lists;
 import com.google.common.collect.MoreCollectors;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.File;
@@ -46,6 +47,8 @@
 import java.util.zip.ZipOutputStream;
 import org.junit.Assert;
 import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class AndroidResourceTestingUtils {
   private static final String RESOURCES_DESCRIPTOR =
@@ -307,6 +310,7 @@
     private final List<Class<?>> classesToRemap = new ArrayList<>();
     private int packageId = 0x7f;
     private String packageName;
+    private boolean useStaticValueForNonFinalFields = false;
 
     // Create the android resources from the passed in R classes
     // All values will be generated based on the fields in the class.
@@ -344,6 +348,14 @@
       return this;
     }
 
+    // When set, use final ids for the AAPT resource generation, but remove the final bit in the
+    // ASM pass. This will emulate the R classes that AGP generates, where the static fields are
+    // set with a static value instead of through <clinit>.
+    AndroidTestResourceBuilder useStaticValueForNonFinalFields() {
+      this.useStaticValueForNonFinalFields = true;
+      return this;
+    }
+
     AndroidTestResourceBuilder withManifest(String manifest) {
       this.manifest = manifest;
       return this;
@@ -485,7 +497,14 @@
       String aaptPackageName =
           packageName != null ? packageName : "thepackage" + packageId + ".foobar";
       compileWithAapt2(
-          resFolder, manifestPath, rClassOutputDir, output, temp, packageId, aaptPackageName);
+          resFolder,
+          manifestPath,
+          rClassOutputDir,
+          output,
+          temp,
+          packageId,
+          aaptPackageName,
+          !useStaticValueForNonFinalFields);
       Path rClassJavaFile =
           Files.walk(rClassOutputDir)
               .filter(path -> path.endsWith("R.java"))
@@ -541,6 +560,19 @@
                             }
 
                             @Override
+                            public FieldVisitor visitField(
+                                int access,
+                                String name,
+                                String descriptor,
+                                String signature,
+                                Object value) {
+                              if (useStaticValueForNonFinalFields) {
+                                access &= ~Opcodes.ACC_FINAL;
+                              }
+                              return super.visitField(access, name, descriptor, signature, value);
+                            }
+
+                            @Override
                             public void visitInnerClass(
                                 String name, String outerName, String innerName, int access) {
                               // Don't make the inner<>outer class connection
@@ -627,7 +659,8 @@
       Path resourceZip,
       TemporaryFolder temp,
       int packageId,
-      String packageName)
+      String packageName,
+      boolean nonFinalIds)
       throws IOException {
     Path compileOutput = temp.newFile("compiled.zip").toPath();
     ProcessResult compileProcessResult =
@@ -635,8 +668,8 @@
             "compile", "-o", compileOutput.toString(), "--dir", resFolder.toString());
     failOnError(compileProcessResult);
 
-    ProcessResult linkProcesResult =
-        ToolHelper.runAapt2(
+    List<String> args =
+        Lists.newArrayList(
             "link",
             "-I",
             ToolHelper.getAndroidJar(AndroidApiLevel.S).toString(),
@@ -644,7 +677,6 @@
             resourceZip.toString(),
             "--java",
             rClassFolder.toString(),
-            "--non-final-ids",
             "--manifest",
             manifest.toString(),
             "--package-id",
@@ -654,6 +686,11 @@
             packageName,
             "--proto-format",
             compileOutput.toString());
+    if (nonFinalIds) {
+      args.add("--non-final-ids");
+    }
+
+    ProcessResult linkProcesResult = ToolHelper.runAapt2(args.toArray(new String[0]));
     failOnError(linkProcesResult);
   }