Tests and fixes for missing classes from instance field instructions

Change-Id: I9e11248b9b7e6eee9276358e279b9e0a3a3dfde6
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 41ae033..24b17fb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -381,7 +381,7 @@
           // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
           // written such that we cannot optimize any field reads or writes.
           enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
-          worklist.enqueueMarkReachableFieldAction(
+          worklist.enqueueMarkInstanceFieldAsReachableAction(
               valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           valueStorageIsLive = true;
         } else {
@@ -446,7 +446,7 @@
           // Unconditionally register the hazzer and one-of proto fields as written from
           // dynamicMethod().
           if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
-            worklist.enqueueMarkReachableFieldAction(
+            worklist.enqueueMarkInstanceFieldAsReachableAction(
                 newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           }
         }
@@ -566,7 +566,7 @@
     }
 
     if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
-      worklist.enqueueMarkReachableFieldAction(
+      worklist.enqueueMarkInstanceFieldAsReachableAction(
           oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
     }
   }
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 b8a6cd5..73d5b00 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1451,11 +1451,10 @@
       return;
     }
 
-    // Must mark the field as targeted even if it does not exist.
-    markFieldAsTargeted(fieldReference, currentMethod);
-
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
+      // Must mark the field as targeted even if it does not exist.
+      markFieldAsTargeted(fieldReference, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1481,15 +1480,12 @@
       Log.verbose(getClass(), "Register Iget `%s`.", fieldReference);
     }
 
-    // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
-    // the field as live, if the holder is an interface.
-    if (appView.options().enableUnusedInterfaceRemoval) {
-      if (field.getReference() != fieldReference) {
-        markTypeAsLive(field.getHolder(), currentMethod);
-      }
+    if (field.getReference() != fieldReference) {
+      // Mark the initial resolution holder as live.
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
-    workList.enqueueMarkReachableFieldAction(
+    workList.enqueueMarkInstanceFieldAsReachableAction(
         field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
   }
 
@@ -1507,11 +1503,10 @@
       return;
     }
 
-    // Must mark the field as targeted even if it does not exist.
-    markFieldAsTargeted(fieldReference, currentMethod);
-
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
+      // Must mark the field as targeted even if it does not exist.
+      markFieldAsTargeted(fieldReference, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1537,16 +1532,13 @@
       Log.verbose(getClass(), "Register Iput `%s`.", fieldReference);
     }
 
-    // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
-    // the field as live, if the holder is an interface.
-    if (appView.options().enableUnusedInterfaceRemoval) {
-      if (field.getReference() != fieldReference) {
-        markTypeAsLive(field.getHolder(), currentMethod);
-      }
+    if (field.getReference() != fieldReference) {
+      // Mark the initial resolution holder as live.
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
-    workList.enqueueMarkReachableFieldAction(field, currentMethod, reason);
+    workList.enqueueMarkInstanceFieldAsReachableAction(field, currentMethod, reason);
   }
 
   void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
@@ -1608,9 +1600,9 @@
     }
 
     if (field.getReference() != fieldReference) {
-      // Mark the non-rebound field access as targeted. Note that this should only be done if the
-      // field is not a dead proto field (in which case we bail-out above).
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Mark the initial resolution holder as live. Note that this should only be done if the field
+      // is not a dead proto field (in which case we bail-out above).
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     markStaticFieldAsLive(field, currentMethod);
@@ -1673,9 +1665,9 @@
     }
 
     if (field.getReference() != fieldReference) {
-      // Mark the non-rebound field access as targeted. Note that this should only be done if the
-      // field is not a dead proto field (in which case we bail-out above).
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Mark the initial resolution holder as live. Note that this should only be done if the field
+      // is not a dead proto field (in which case we bail-out above).
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     markStaticFieldAsLive(field, currentMethod);
@@ -1743,6 +1735,13 @@
     markTypeAsLive(clazz, reason);
   }
 
+  private void markTypeAsLive(DexClass clazz, ProgramDefinition context) {
+    if (clazz.isProgramClass()) {
+      DexProgramClass programClass = clazz.asProgramClass();
+      markTypeAsLive(programClass, graphReporter.reportClassReferencedFrom(programClass, context));
+    }
+  }
+
   private void markTypeAsLive(DexProgramClass clazz, ProgramDefinition context) {
     markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, context));
   }
@@ -2658,9 +2657,14 @@
     }
   }
 
+  private void markFieldAsTargeted(ProgramField field) {
+    markTypeAsLive(field.getHolder(), field);
+    markTypeAsLive(field.getType(), field);
+  }
+
   private void markFieldAsTargeted(DexField field, ProgramMethod context) {
-    markTypeAsLive(field.type, context);
-    markTypeAsLive(field.holder, context);
+    markTypeAsLive(field.getHolderType(), context);
+    markTypeAsLive(field.getType(), context);
   }
 
   private void markStaticFieldAsLive(ProgramField field, ProgramMethod context) {
@@ -2669,9 +2673,8 @@
 
   private void markStaticFieldAsLive(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    // Mark the type live here, so that the class exists at runtime.
-    markTypeAsLive(field.getHolder(), field);
-    markTypeAsLive(field.getReference().type, field);
+    // Mark the field type and holder live here, so that they exist at runtime.
+    markFieldAsTargeted(field);
 
     markDirectAndIndirectClassInitializersAsLive(field.getHolder());
 
@@ -2701,8 +2704,8 @@
 
   private void markInstanceFieldAsLive(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    markTypeAsLive(field.getHolder(), field);
-    markTypeAsLive(field.getType(), field);
+    markFieldAsTargeted(field);
+
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field);
     }
@@ -2830,8 +2833,7 @@
         field.getHolder())) {
       markInstanceFieldAsLive(field, context, reason);
     } else {
-      markTypeAsLive(field.getHolder(), field);
-      markTypeAsLive(field.getReference().type, field);
+      markFieldAsTargeted(field);
 
       // Add the field to the reachable set if the type later becomes instantiated.
       reachableInstanceFields
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 07432c7..9ce5737 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -59,13 +59,13 @@
     }
   }
 
-  static class MarkReachableFieldAction extends EnqueuerAction {
+  static class MarkInstanceFieldAsReachableAction extends EnqueuerAction {
     private final ProgramField field;
     // TODO(b/175854431): Avoid pushing context on worklist.
     private final ProgramDefinition context;
     private final KeepReason reason;
 
-    public MarkReachableFieldAction(
+    public MarkInstanceFieldAsReachableAction(
         ProgramField field, ProgramDefinition context, KeepReason reason) {
       this.field = field;
       this.context = context;
@@ -287,9 +287,9 @@
     addToQueue(new MarkReachableSuperAction(method, from));
   }
 
-  public void enqueueMarkReachableFieldAction(
+  public void enqueueMarkInstanceFieldAsReachableAction(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    addToQueue(new MarkReachableFieldAction(field, context, reason));
+    addToQueue(new MarkInstanceFieldAsReachableAction(field, context, reason));
   }
 
   // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
index 2e59a42..e6bd2e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
@@ -58,16 +58,16 @@
     }
 
     String getReferencedFromMessageSuffix(ClassReference missingClass) {
-      if (!methodContexts.isEmpty()) {
-        return " (referenced from: "
-            + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
-            + ")";
-      }
       if (!fieldContexts.isEmpty()) {
         return " (referenced from: "
             + FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
             + ")";
       }
+      if (!methodContexts.isEmpty()) {
+        return " (referenced from: "
+            + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
+            + ")";
+      }
       // TODO(b/175543745): The legacy reporting is context insensitive, and therefore uses the
       //  missing classes as their own context. Once legacy reporting is removed, this should be
       //  simplified to taking the first context.
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java
new file mode 100644
index 0000000..6c7f620
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java
@@ -0,0 +1,65 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromInstanceGetToExistingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "field",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromInstanceGetToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public MissingClass field;
+
+    public static void main(String[] args) {
+      MissingClass ignore = new Main().field;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java
new file mode 100644
index 0000000..851d65d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java
@@ -0,0 +1,61 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromInstanceGetToMissingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInstanceGetToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int ignore = new MissingClass().field;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java
new file mode 100644
index 0000000..b69625e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java
@@ -0,0 +1,65 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromInstancePutToExistingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "field",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromInstancePutToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    MissingClass field;
+
+    public static void main(String[] args) {
+      new Main().field = null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java
new file mode 100644
index 0000000..ec615f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java
@@ -0,0 +1,61 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromInstancePutToMissingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInstancePutToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new MissingClass().field = 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 08cb3de..191c28d 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -34,6 +34,8 @@
   static class MissingClass {
 
     static int FIELD;
+
+    int field;
   }
 
   private final TestParameters parameters;