Avoid NPE while computing inner-name separator from missing attributes.

Bug: 131210377, 131140696
Change-Id: I2006177073ccc34e39e7607778194063aa21f0f6
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d0eeaad..16cd797 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -806,24 +806,23 @@
 
   public boolean isLocalClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
+    // The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
     return innerClass != null
-        && innerClass.isNamed()
-        && getEnclosingMethod() != null;
+        && innerClass.getOuter() == null
+        && innerClass.isNamed();
   }
 
   public boolean isMemberClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
     return innerClass != null
         && innerClass.getOuter() != null
-        && innerClass.isNamed()
-        && getEnclosingMethod() == null;
+        && innerClass.isNamed();
   }
 
   public boolean isAnonymousClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
-    return innerClass != null
-        && innerClass.isAnonymous()
-        && getEnclosingMethod() != null;
+    // The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
+    return innerClass != null && innerClass.isAnonymous();
   }
 
   public boolean isInANest() {
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 0fe8c04..5457311 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -72,4 +72,13 @@
       enclosingMethod.collectIndexedItems(indexedItems);
     }
   }
+
+  @Override
+  public String toString() {
+    return "[enclosingClass: "
+        + (enclosingClass == null ? "null" : enclosingClass.toDescriptorString())
+        + ", enclosingMethod: "
+        + (enclosingMethod == null ? "null" : enclosingMethod.toSourceString())
+        + "]";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index 04feae7..b40f328 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -102,4 +102,13 @@
     }
     return context;
   }
+
+  @Override
+  public String toString() {
+    return "[access : " + Integer.toHexString(access)
+        + ", inner: " + inner.toDescriptorString()
+        + ", outer: " + (outer == null ? "null" : outer.toDescriptorString())
+        + ", innerName: " + (innerName == null ? "(anonymous)" : innerName.toString())
+        + "]";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 5a80fe0..665e3d5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -8,6 +8,8 @@
 import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
+import static org.objectweb.asm.Opcodes.V1_6;
+import static org.objectweb.asm.Opcodes.V9;
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
@@ -240,10 +242,10 @@
     @Override
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
       if (outerName != null && innerName != null) {
-        String separator =
-            DescriptorUtils.computeInnerClassSeparator(
-                outerName, name, innerName, application.options.isMinifying());
-        if (separator == null) {
+        String separator = DescriptorUtils.computeInnerClassSeparator(outerName, name, innerName);
+        if (separator == null
+            && !application.options.isMinifying()
+            && getMajorVersion() < V9) {
           application.options.reporter.info(
               new StringDiagnostic(
                   StringUtils.lines(
@@ -419,6 +421,11 @@
               directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
               virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
               application.getFactory().getSkipNameValidationForTesting());
+      if (enclosingMember == null
+          && (clazz.isLocalClass() || clazz.isAnonymousClass())
+          && getMajorVersion() > V1_6) {
+        application.options.warningMissingEnclosingMember(clazz.type, clazz.origin, version);
+      }
       if (clazz.isProgramClass()) {
         context.owner = clazz.asProgramClass();
         clazz.asProgramClass().setInitialClassFileVersion(version);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index f37e60f..04cc0ba 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -249,14 +249,14 @@
     return attribute.getOuter();
   }
 
-  private String getInnerNameForType(DexType type) {
+  private DexString getInnerNameForType(DexType type) {
     // This util is used only after the corresponding outer-class is recognized.
     // Therefore, the definition for the type and its inner-class attribute should be found.
     DexClass clazz = appView.definitionFor(type);
     assert clazz != null;
     InnerClassAttribute attribute = clazz.getInnerClassAttributeForThisClass();
     assert attribute != null;
-    return attribute.getInnerName().toString();
+    return attribute.getInnerName();
   }
 
   private DexString computeName(DexType type) {
@@ -266,11 +266,11 @@
       // of the outer class for the $ prefix.
       DexType outerClass = getOutClassForType(type);
       if (outerClass != null) {
-        String innerClassSeparator =
-            computeInnerClassSeparator(
-                outerClass, type, getInnerNameForType(type), appView.options().isMinifying());
-        assert innerClassSeparator != null;
-        state = getStateForOuterClass(outerClass, innerClassSeparator);
+        String separator = computeInnerClassSeparator(outerClass, type, getInnerNameForType(type));
+        if (separator == null) {
+          separator = String.valueOf(INNER_CLASS_SEPARATOR);
+        }
+        state = getStateForOuterClass(outerClass, separator);
       }
     }
     if (state == null) {
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 41dd4dc..f14e456 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -64,7 +64,6 @@
     }
     DexType innerType = attribute.getInner();
     String inner = DescriptorUtils.descriptorToInternalName(innerType.descriptor.toString());
-    String innerName = attribute.getInnerName().toString();
     // At this point we assume the input was of the form: <OuterType>$<index><InnerName>
     // Find the mapped type and if it remains the same return that, otherwise split at
     // either the input separator ($, $$, or anything that starts with $) or $ (that we recover
@@ -74,10 +73,12 @@
     if (inner.equals(innerTypeMapped)) {
       return attribute.getInnerName();
     }
-    String separator =
-        DescriptorUtils.computeInnerClassSeparator(
-            attribute.getOuter(), innerType, innerName, options.isMinifying());
-    assert separator != null;
+    String separator = DescriptorUtils.computeInnerClassSeparator(
+        attribute.getOuter(), innerType, attribute.getInnerName());
+    if (separator == null) {
+      separator = String.valueOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+
+    }
     int index = innerTypeMapped.lastIndexOf(separator);
     if (index < 0) {
       assert false : innerType + " -> " + innerTypeMapped;
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index d523610..dfb21da 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.google.common.collect.ImmutableMap;
@@ -309,19 +310,18 @@
   }
 
   public static String computeInnerClassSeparator(
-      DexType outerClass, DexType innerClass, String innerName, boolean isMinifying) {
+      DexType outerClass, DexType innerClass, DexString innerName) {
+    if (innerName == null) {
+      return String.valueOf(INNER_CLASS_SEPARATOR);
+    }
     return computeInnerClassSeparator(
-        outerClass.getInternalName(), innerClass.getInternalName(), innerName, isMinifying);
+        outerClass.getInternalName(), innerClass.getInternalName(), innerName.toString());
   }
 
   public static String computeInnerClassSeparator(
-      String outerDescriptor, String innerDescriptor, String innerName, boolean isMinifying) {
+      String outerDescriptor, String innerDescriptor, String innerName) {
     // outer-internal<separator>inner-name == inner-internal
     if (outerDescriptor.length() + innerName.length() > innerDescriptor.length()) {
-      // But, if the minification is enabled, we can choose $ separator.
-      if (isMinifying) {
-        return String.valueOf(INNER_CLASS_SEPARATOR);
-      }
       return null;
     }
     String separator =
@@ -329,10 +329,6 @@
             outerDescriptor.length(), innerDescriptor.length() - innerName.length());
     // Any non-$ separator results in a runtime exception in getCanonicalName.
     if (!separator.startsWith(String.valueOf(INNER_CLASS_SEPARATOR))) {
-      // Again, if the minification is enabled, we can choose $ separator.
-      if (isMinifying) {
-        return String.valueOf(INNER_CLASS_SEPARATOR);
-      }
       return null;
     }
     return separator;
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/DataClassDumps.java b/src/test/java/com/android/tools/r8/shaking/attributes/DataClassDumps.java
new file mode 100644
index 0000000..8cd3fe1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/DataClassDumps.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2019, 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.shaking.attributes;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+class DataClassDumps implements Opcodes {
+
+  public static byte[] dump18 () {
+
+    ClassWriter classWriter = new ClassWriter(0);
+
+    classWriter.visit(
+        V1_8,
+        ACC_SUPER | ACC_SYNTHETIC,
+        "com/android/tools/r8/shaking/attributes/DataClass$1", null, "java/lang/Object", null);
+
+    classWriter.visitSource("MissingEnclosingMethodTest.java", null);
+
+    classWriter.visitOuterClass(
+        "com/android/tools/r8/shaking/attributes/DataClass", null, null);
+
+    classWriter.visitInnerClass(
+        // Inner
+        "com/android/tools/r8/shaking/attributes/DataClass$1",
+        // Outer, intentionally changed from null.
+        "com/android/tools/r8/shaking/attributes/DataClass",
+        // Inner-name
+        null,
+        ACC_STATIC | ACC_SYNTHETIC);
+
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static byte[] dump16 () {
+
+    ClassWriter classWriter = new ClassWriter(0);
+
+    classWriter.visit(
+        V1_6,
+        ACC_SUPER | ACC_SYNTHETIC,
+        "com/android/tools/r8/shaking/attributes/DataClass$1", null, "java/lang/Object", null);
+
+    classWriter.visitSource("MissingEnclosingMethodTest.java", null);
+
+    classWriter.visitInnerClass(
+        // Inner
+        "com/android/tools/r8/shaking/attributes/DataClass$1",
+        // Outer
+        "com/android/tools/r8/shaking/attributes/DataClass",
+        // Inner-name
+        null,
+        ACC_STATIC | ACC_SYNTHETIC);
+
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java
new file mode 100644
index 0000000..8c0157f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.shaking.attributes;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class DataClass {
+  public final int f1;
+  public final String f2;
+
+  private DataClass(Builder builder) {
+    this.f1 = builder.f1;
+    this.f2 = builder.f2;
+  }
+
+  @Override
+  public String toString() {
+    return "f1: " + f1 + ", f2: " + f2;
+  }
+
+  public static class Builder {
+    private int f1;
+    private String f2;
+
+    public Builder() {
+    }
+
+    public Builder setF1(int f1) {
+      this.f1 = f1;
+      return this;
+    }
+
+    public Builder setF2(String f2) {
+      this.f2 = f2;
+      return this;
+    }
+
+    public DataClass build() {
+      return new DataClass(this);
+    }
+  }
+}
+
+class DataClassUser {
+  public static void main(String... args) {
+    DataClass.Builder builder = new DataClass.Builder();
+    builder.setF1(8);
+    builder.setF2("8");
+    DataClass d = builder.build();
+    System.out.println(d);
+  }
+}
+
+@RunWith(Parameterized.class)
+public class MissingEnclosingMethodTest extends TestBase {
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("f1: 8, f2: 8");
+  private final TestParameters parameters;
+  private final TestConfig config;
+  private final boolean enableMinification;
+
+  enum TestConfig {
+    CLASS,
+    DUMP_18,
+    DUMP_16;
+
+    public void addInnerClasses(R8FullTestBuilder builder) throws IOException {
+      switch (this) {
+        case CLASS:
+          builder.addInnerClasses(DataClass.class);
+          break;
+        case DUMP_18:
+          builder.addProgramClasses(DataClass.Builder.class);
+          builder.addProgramClassFileData(DataClassDumps.dump18());
+          break;
+        case DUMP_16:
+          builder.addProgramClasses(DataClass.Builder.class);
+          builder.addProgramClassFileData(DataClassDumps.dump16());
+          break;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0} {1}  minification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().build(),
+        TestConfig.values(),
+        BooleanUtils.values());
+  }
+
+  public MissingEnclosingMethodTest(TestParameters parameters, TestConfig config, boolean enableMinification) {
+    this.parameters = parameters;
+    this.config = config;
+    this.enableMinification = enableMinification;
+  }
+
+  @Test
+  public void b131210377() throws Exception {
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(DataClass.class, DataClassUser.class);
+    config.addInnerClasses(builder);
+    builder
+        .addKeepMainRule(DataClassUser.class)
+        .addKeepRules("-keepattributes Signature,InnerClasses,EnclosingMethod")
+        .addOptionsModification(o -> o.forceProguardCompatibility = true)
+        .minification(enableMinification)
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), DataClassUser.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+}