[Retrace] Filter map lines before materializing of strings

Bug: b/226892337
Bug: b/226885646
Change-Id: I616f76ddb24fa75d757f90a62e6ff576934cf77c
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 7657adc..0b0d20f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -47,16 +47,6 @@
 
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
     private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
-    private final Set<String> buildForClass;
-
-    private Builder(Set<String> buildForClass) {
-      this.buildForClass = buildForClass;
-    }
-
-    @Override
-    public boolean buildForClass(String typeName) {
-      return buildForClass == null || buildForClass.contains(typeName);
-    }
 
     @Override
     public ClassNamingForNameMapper.Builder classNamingBuilder(
@@ -88,11 +78,7 @@
   }
 
   public static Builder builder() {
-    return new Builder(null);
-  }
-
-  public static Builder builder(Set<String> buildForClass) {
-    return new Builder(buildForClass);
+    return new Builder();
   }
 
   public static ClassNameMapper mapperFromFile(Path path) throws IOException {
@@ -182,7 +168,7 @@
             diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
             allowEmptyMappedRanges,
             allowExperimentalMapping)) {
-      ClassNameMapper.Builder builder = ClassNameMapper.builder(buildForClass);
+      ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index d1e7463..6b95444 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -16,10 +16,6 @@
     abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
 
     abstract ProguardMap build();
-
-    public boolean buildForClass(String typeName) {
-      return true;
-    }
   }
 
   boolean hasMapping(DexType type);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index bbea4c4..f28d4bc 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -261,18 +261,11 @@
       String after = parseType(false);
       skipWhitespace();
       expect(':');
-      if (mapBuilder.buildForClass(after)) {
-        ClassNaming.Builder currentClassBuilder =
-            mapBuilder.classNamingBuilder(after, before, getPosition());
-        skipWhitespace();
-        if (nextLine()) {
-          parseMemberMappings(currentClassBuilder);
-        }
-      } else {
-        do {
-          lineOffset = line.length();
-          nextLine();
-        } while (hasLine() && !isClassMapping());
+      ClassNaming.Builder currentClassBuilder =
+          mapBuilder.classNamingBuilder(after, before, getPosition());
+      skipWhitespace();
+      if (nextLine()) {
+        parseMemberMappings(currentClassBuilder);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
index 279a038..d42da83 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.COMPLETE_CLASS_MAPPING;
+import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.IS_COMMENT_SOURCE_FILE;
 import static java.lang.Integer.MAX_VALUE;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.LineReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -15,12 +18,182 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.Set;
 
 public abstract class ProguardMapReaderWithFiltering implements LineReader {
 
+  // The LineParserState encodes a simple state that the line parser can be in, where the
+  // (successful) transitions allowed are:
+
+  // BEGINNING -> BEGINNING_NO_WHITESPACE -> SEEN_ORIGINAL_CLASS || IS_COMMENT_START
+
+  // IS_COMMENT_START -> IS_COMMENT_SOURCE_FILE
+
+  // SEEN_ORIGINAL_CLASS -> SEEN_ARROW -> SEEN_OBFUSCATED_CLASS -> COMPLETE_CLASS_MAPPING
+  //
+  // From all states there is a transition on invalid input to NOT_CLASS_MAPPING_OR_SOURCE_FILE.
+  // The terminal states are:
+  // { IS_COMMENT_SOURCE_FILE, COMPLETE_CLASS_MAPPING, NOT_CLASS_MAPPING_OR_SOURCE_FILE }
+  //
+  public enum LineParserState {
+    BEGINNING,
+    BEGINNING_NO_WHITESPACE,
+    SEEN_ORIGINAL_CLASS,
+    SEEN_ARROW,
+    SEEN_OBFUSCATED_CLASS,
+    COMPLETE_CLASS_MAPPING,
+    IS_COMMENT_START,
+    IS_COMMENT_SOURCE_FILE,
+    NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+
+    private boolean isTerminal() {
+      return this == NOT_CLASS_MAPPING_OR_SOURCE_FILE
+          || this == COMPLETE_CLASS_MAPPING
+          || this == IS_COMMENT_SOURCE_FILE;
+    }
+
+    private static int currentIndex;
+    private static int endIndex;
+    private static byte[] bytes;
+    private static final byte[] SOURCE_FILE_BYTES = "sourceFile".getBytes();
+
+    public static LineParserState computeState(byte[] bytes, int startIndex, int endIndex) {
+      currentIndex = startIndex;
+      LineParserState.endIndex = endIndex;
+      LineParserState.bytes = bytes;
+      LineParserState currentState = BEGINNING;
+      while (!currentState.isTerminal()) {
+        currentState = currentState.computeNextState();
+      }
+      return currentState;
+    }
+
+    private LineParserState computeNextState() {
+      assert this != NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+      switch (this) {
+        case BEGINNING:
+          return readUntilNoWhiteSpace()
+              ? BEGINNING_NO_WHITESPACE
+              : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case BEGINNING_NO_WHITESPACE:
+          if (isCommentChar()) {
+            return IS_COMMENT_START;
+          } else {
+            int readLength = readCharactersNoWhiteSpaceUntil(' ');
+            return readLength > 0 ? SEEN_ORIGINAL_CLASS : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+        case SEEN_ORIGINAL_CLASS:
+          return readArrow() ? SEEN_ARROW : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case SEEN_ARROW:
+          int colonIndex = readCharactersNoWhiteSpaceUntil(':');
+          return colonIndex > 0 ? SEEN_OBFUSCATED_CLASS : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case SEEN_OBFUSCATED_CLASS:
+          boolean read = readColon();
+          if (!read) {
+            return NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+          boolean noWhiteSpace = readUntilNoWhiteSpace();
+          return (!noWhiteSpace || isCommentChar())
+              ? COMPLETE_CLASS_MAPPING
+              : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case IS_COMMENT_START:
+          if (readCharactersUntil('{')
+              && readCharactersUntil(':')
+              && readSingleOrDoubleQuote()
+              && readSourceFile()) {
+            return IS_COMMENT_SOURCE_FILE;
+          } else {
+            return NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+        default:
+          assert isTerminal();
+          throw new Unreachable("Should not compute next state on terminal state");
+      }
+    }
+
+    private boolean readColon() {
+      return read(':');
+    }
+
+    private boolean readCharactersUntil(char ch) {
+      while (currentIndex < endIndex) {
+        if (bytes[currentIndex++] == ch) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private int readCharactersNoWhiteSpaceUntil(char ch) {
+      int startIndex = currentIndex;
+      while (currentIndex < endIndex) {
+        byte readByte = bytes[currentIndex];
+        if (readByte == ch) {
+          return currentIndex - startIndex;
+        }
+        if (Character.isWhitespace(readByte)) {
+          return -1;
+        }
+        currentIndex++;
+      }
+      return -1;
+    }
+
+    private boolean readUntilNoWhiteSpace() {
+      while (currentIndex < endIndex) {
+        if (!Character.isWhitespace(bytes[currentIndex])) {
+          return true;
+        }
+        currentIndex++;
+      }
+      return false;
+    }
+
+    private boolean readArrow() {
+      return readSpace() && read('-') && read('>') && readSpace();
+    }
+
+    private boolean readSpace() {
+      return read(' ');
+    }
+
+    private boolean read(char ch) {
+      return bytes[currentIndex++] == ch;
+    }
+
+    private boolean isCommentChar() {
+      return bytes[currentIndex] == '#';
+    }
+
+    private boolean readSourceFile() {
+      if (endIndex - currentIndex < SOURCE_FILE_BYTES.length) {
+        return false;
+      }
+      int endSourceFileIndex = currentIndex + SOURCE_FILE_BYTES.length;
+      int sourceFileByteIndex = 0;
+      for (; currentIndex < endSourceFileIndex; currentIndex++) {
+        if (SOURCE_FILE_BYTES[sourceFileByteIndex++] != bytes[currentIndex]) {
+          return false;
+        }
+      }
+      return readSingleOrDoubleQuote();
+    }
+
+    private boolean readSingleOrDoubleQuote() {
+      byte readByte = bytes[currentIndex++];
+      return readByte == '\'' || readByte == '"';
+    }
+  }
+
   private int startIndex = 0;
   private int endIndex = 0;
 
+  private final Set<String> filter;
+
+  protected ProguardMapReaderWithFiltering(Set<String> filter) {
+    this.filter = filter;
+  }
+
   public abstract byte[] read() throws IOException;
 
   public abstract int getStartIndex();
@@ -29,15 +202,43 @@
 
   public abstract boolean exceedsBuffer();
 
+  private boolean isInsideClassOfInterest = false;
+  private boolean seenFirstClass = false;
+
   @Override
   public String readLine() throws IOException {
-    byte[] bytes = readLineFromMultipleReads();
-    if (bytes == null) {
-      return null;
+    while (true) {
+      byte[] bytes = readLineFromMultipleReads();
+      if (bytes == null) {
+        return null;
+      }
+      if (filter == null) {
+        return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
+      }
+      LineParserState lineParserState = LineParserState.computeState(bytes, startIndex, endIndex);
+      if (lineParserState == COMPLETE_CLASS_MAPPING) {
+        seenFirstClass = true;
+        String classMapping = getBufferAsString(bytes);
+        String obfuscatedClassName = getObfuscatedClassName(classMapping);
+        isInsideClassOfInterest = filter.contains(obfuscatedClassName);
+        return classMapping;
+      } else if (lineParserState == IS_COMMENT_SOURCE_FILE) {
+        return getBufferAsString(bytes);
+      } else if (isInsideClassOfInterest || !seenFirstClass) {
+        return getBufferAsString(bytes);
+      }
     }
+  }
+
+  private String getBufferAsString(byte[] bytes) {
     return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
   }
 
+  private String getObfuscatedClassName(String classMapping) {
+    int arrowIndex = classMapping.indexOf(">");
+    return classMapping.substring(arrowIndex + 2, classMapping.length() - 1);
+  }
+
   private byte[] readLineFromMultipleReads() throws IOException {
     startIndex = 0;
     endIndex = 0;
@@ -82,7 +283,9 @@
     private int currentPosition = 0;
     private int temporaryBufferPosition = 0;
 
-    public ProguardMapReaderWithFilteringMappedBuffer(Path mappingFile) throws IOException {
+    public ProguardMapReaderWithFilteringMappedBuffer(
+        Path mappingFile, Set<String> classNamesOfInterest) throws IOException {
+      super(classNamesOfInterest);
       fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
       channelSize = fileChannel.size();
       readFromChannel();
@@ -160,7 +363,9 @@
     private int endIndex = 0;
     private int endReadIndex = 0;
 
-    public ProguardMapReaderWithFilteringInputBuffer(InputStream inputStream) {
+    public ProguardMapReaderWithFilteringInputBuffer(
+        InputStream inputStream, Set<String> classNamesOfInterest) {
+      super(classNamesOfInterest);
       this.inputStream = inputStream;
     }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index cb6991e..a5c1dd6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -64,17 +64,16 @@
   @Override
   public ProguardMappingProvider build() {
     try {
+      Set<String> buildForClass = allowLookupAllClasses ? null : allowedLookup;
       LineReader reader =
           proguardMapProducer.isFileBacked()
-              ? new ProguardMapReaderWithFilteringMappedBuffer(proguardMapProducer.getPath())
-              : new ProguardMapReaderWithFilteringInputBuffer(proguardMapProducer.get());
+              ? new ProguardMapReaderWithFilteringMappedBuffer(
+                  proguardMapProducer.getPath(), buildForClass)
+              : new ProguardMapReaderWithFilteringInputBuffer(
+                  proguardMapProducer.get(), buildForClass);
       return new ProguardMappingProviderImpl(
           ClassNameMapper.mapperFromLineReaderWithFiltering(
-              reader,
-              diagnosticsHandler,
-              true,
-              allowExperimental,
-              allowLookupAllClasses ? null : allowedLookup));
+              reader, diagnosticsHandler, true, allowExperimental, buildForClass));
     } catch (Exception e) {
       throw new InvalidMappingFileException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
index eb1d9f3..f5943b5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -40,11 +40,9 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".throwsException(KotlinJavaSourceFileTestLibrary.java:22)",
+            + ".throwsException(KotlinJavaSourceFileTestLibrary.kt:22)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.java:19)",
+            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.kt:19)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
             + ".main(KotlinJavaSourceFileTestObject.java:32)");
   }
@@ -53,11 +51,9 @@
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".void throwsException()(KotlinJavaSourceFileTestLibrary.java:22)",
+            + ".void throwsException()(KotlinJavaSourceFileTestLibrary.kt:22)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.java:19)",
+            + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.kt:19)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
             + ".void main(java.lang.String[])(KotlinJavaSourceFileTestObject.java:32)");
   }