Allow retracing of stacktraces with classloader and module

For JVM9 and forward, the stack traces can contain classloader and
module names.

Bug: 148917473
Change-Id: I09b6045c015970a5b0bdf43007c111493c71ccc3
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 8d3e956..89e7c9d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -262,8 +262,10 @@
    * <ul>
    *   <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
    *   <li>at dalvik.system.NativeStart.main(:99)
-   *   <li>dalvik.system.NativeStart.main(Foo.java:)
+   *   <li>at dalvik.system.NativeStart.main(Foo.java:)
    *   <li>at dalvik.system.NativeStart.main(Native Method)
+   *   <li>at classloader/named_module@version/foo.bar.baz(:20)
+   *   <li>at classloader//foo.bar.baz(:20)
    * </ul>
    *
    * <p>Empirical evidence suggests that the "at" string is never localized.
@@ -275,6 +277,8 @@
 
     private final String startingWhitespace;
     private final String at;
+    private final String classLoaderName;
+    private final String moduleName;
     private final String clazz;
     private final String method;
     private final String methodAsString;
@@ -285,6 +289,8 @@
     private AtLine(
         String startingWhitespace,
         String at,
+        String classLoaderName,
+        String moduleName,
         String clazz,
         String method,
         String methodAsString,
@@ -293,6 +299,8 @@
         boolean isAmbiguous) {
       this.startingWhitespace = startingWhitespace;
       this.at = at;
+      this.classLoaderName = classLoaderName;
+      this.moduleName = moduleName;
       this.clazz = clazz;
       this.method = method;
       this.methodAsString = methodAsString;
@@ -314,11 +322,13 @@
           || line.charAt(firstNonWhiteSpace + 2) != ' ') {
         return null;
       }
-      int classStartIndex = firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
-      if (classStartIndex >= line.length() || classStartIndex != firstNonWhiteSpace + 3) {
+      int classClassLoaderOrModuleStartIndex =
+          firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
+      if (classClassLoaderOrModuleStartIndex >= line.length()
+          || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
         return null;
       }
-      int parensStart = firstCharFromIndex(line, classStartIndex, '(');
+      int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
       if (parensStart >= line.length()) {
         return null;
       }
@@ -330,7 +340,7 @@
         return null;
       }
       int methodSeparator = line.lastIndexOf('.', parensStart);
-      if (methodSeparator <= classStartIndex) {
+      if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
         return null;
       }
       // Check if we have a filename and position.
@@ -348,11 +358,32 @@
       } else {
         fileName = line.substring(parensStart + 1, parensEnd);
       }
+      String classLoaderName = null;
+      String moduleName = null;
+      int classStartIndex = classClassLoaderOrModuleStartIndex;
+      int classLoaderOrModuleEndIndex =
+          firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
+      if (classLoaderOrModuleEndIndex < methodSeparator) {
+        int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
+        if (moduleEndIndex < methodSeparator) {
+          // The stack trace contains both a class loader and module
+          classLoaderName =
+              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+          moduleName = line.substring(classLoaderOrModuleEndIndex + 1, moduleEndIndex);
+          classStartIndex = moduleEndIndex + 1;
+        } else {
+          moduleName =
+              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+          classStartIndex = classLoaderOrModuleEndIndex + 1;
+        }
+      }
       String className = line.substring(classStartIndex, methodSeparator);
       String methodName = line.substring(methodSeparator + 1, parensStart);
       return new AtLine(
           line.substring(0, firstNonWhiteSpace),
-          line.substring(firstNonWhiteSpace, classStartIndex),
+          line.substring(firstNonWhiteSpace, classClassLoaderOrModuleStartIndex),
+          classLoaderName,
+          moduleName,
           className,
           methodName,
           className + "." + methodName,
@@ -368,6 +399,27 @@
     @Override
     List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       List<StackTraceLine> lines = new ArrayList<>();
+      String retraceClassLoaderName = classLoaderName;
+      if (retraceClassLoaderName != null) {
+        ClassReference classLoaderReference = Reference.classFromTypeName(retraceClassLoaderName);
+        retraceBase
+            .retrace(classLoaderReference)
+            .forEach(
+                classElement -> {
+                  retraceClassAndMethods(
+                      retraceBase, verbose, lines, classElement.getClassReference().getTypeName());
+                });
+      } else {
+        retraceClassAndMethods(retraceBase, verbose, lines, retraceClassLoaderName);
+      }
+      return lines;
+    }
+
+    private void retraceClassAndMethods(
+        RetraceBase retraceBase,
+        boolean verbose,
+        List<StackTraceLine> lines,
+        String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
       retraceBase
           .retrace(classReference)
@@ -380,6 +432,8 @@
                     new AtLine(
                         startingWhitespace,
                         at,
+                        classLoaderName,
+                        moduleName,
                         methodReference.getHolderClass().getTypeName(),
                         methodReference.getMethodName(),
                         methodDescriptionFromMethodReference(methodReference, verbose),
@@ -390,13 +444,20 @@
                             : linePosition,
                         methodElement.getRetraceMethodResult().isAmbiguous()));
               });
-      return lines;
     }
 
     @Override
     public String toString() {
       StringBuilder sb = new StringBuilder(startingWhitespace);
       sb.append(at);
+      if (classLoaderName != null) {
+        sb.append(classLoaderName);
+        sb.append("/");
+      }
+      if (moduleName != null) {
+        sb.append(moduleName);
+        sb.append("/");
+      }
       sb.append(methodAsString);
       sb.append("(");
       sb.append(fileName);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 2f9bf9d..d4b758e 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -128,11 +128,6 @@
       if (line.startsWith(AT_PREFIX)) {
         line = line.substring(AT_PREFIX.length());
       }
-      // TODO(b/148917473): Remove hack.
-      if (line.startsWith("java.base/")) {
-        originalLine = originalLine.replaceFirst("java.base/", "");
-        line = line.substring("java.base/".length());
-      }
 
       // Expect only one '(', and only one ')' with an optional ':' in between.
       int parenBeginIndex = line.indexOf('(');
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index 2d7d0fd..809a521 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -193,7 +194,12 @@
   private StackTrace.Builder createStackTraceBuilder() {
     StackTrace.Builder builder = StackTrace.builder();
     if (canUseRequireNonNull()) {
-      builder.addWithoutFileNameAndLineNumber(Objects.class, "requireNonNull");
+      if (parameters.isCfRuntime()
+          && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+        builder.addWithoutFileNameAndLineNumber("java.base/java.util.Objects", "requireNonNull");
+      } else {
+        builder.addWithoutFileNameAndLineNumber(Objects.class, "requireNonNull");
+      }
     }
     return builder;
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index cfc4fba..174dd5e 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
@@ -165,6 +166,12 @@
     runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
   }
 
+  @Test
+  public void testBootLoaderAndNamedModulesStackTrace() {
+    assumeFalse(useRegExpParsing);
+    runRetraceTest(new NamedModuleStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
new file mode 100644
index 0000000..8bd402f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
@@ -0,0 +1,56 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is testing the string representation of stack trace elements with built-in class loaders and
+ * named/unnamed modules:
+ * https://docs.oracle.com/javase/10/docs/api/java/lang/StackTraceElement.html#toString()
+ */
+public class NamedModuleStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "SomeFakeException: this is a fake exception",
+        "\tat classloader.a.b.a/named_module@9.0/a.a(:101)",
+        "\tat classloader.a.b.a//a.b(App.java:12)",
+        "\tat named_module@2.1/a.c(Lib.java:80)",
+        "\tat named_module/a.d(Lib.java:81)",
+        "\tat a.e(MyClass.java:9)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "com.android.tools.r8.Classloader -> classloader.a.b.a:",
+        "com.android.tools.r8.Main -> a:",
+        "  101:101:void main(java.lang.String[]):1:1 -> a",
+        "  12:12:void foo(java.lang.String[]):2:2 -> b",
+        "  80:80:void bar(java.lang.String[]):3:3 -> c",
+        "  81:81:void baz(java.lang.String[]):4:4 -> d",
+        "  9:9:void qux(java.lang.String[]):5:5 -> e");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "SomeFakeException: this is a fake exception",
+        "\tat com.android.tools.r8.Classloader/named_module@9.0/com.android.tools.r8.Main.main(Main.java:1)",
+        "\tat com.android.tools.r8.Classloader//com.android.tools.r8.Main.foo(Main.java:2)",
+        "\tat named_module@2.1/com.android.tools.r8.Main.bar(Main.java:3)",
+        "\tat named_module/com.android.tools.r8.Main.baz(Main.java:4)",
+        "\tat com.android.tools.r8.Main.qux(Main.java:5)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}