Reduce memory pressure when reading line positions

When reading in line positions in class files, the ArchiveResourceProvider will read all bytes before closing the file and return a map. By using a consumer, we only consume the memory needed for visiting that a single class file at a time.

Bug: 147009037
Change-Id: I3baf9f00689af0a8d5786dbfb0f4bdfd8c479645
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 5a50f1b..a66d383 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
@@ -136,4 +137,43 @@
   private boolean isProgramResourceName(String name) {
     return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
   }
+
+  public void accept(Consumer<ProgramResource> visitor) throws ResourceException {
+    try (ZipFile zipFile =
+        FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        String name = entry.getName();
+        if (archive.matchesFile(name) && isProgramResourceName(name)) {
+          Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+          try (InputStream stream = zipFile.getInputStream(entry)) {
+            if (ZipUtils.isDexFile(name)) {
+              OneShotByteResource resource =
+                  OneShotByteResource.create(
+                      Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
+              visitor.accept(resource);
+            } else if (ZipUtils.isClassFile(name)) {
+              OneShotByteResource resource =
+                  OneShotByteResource.create(
+                      Kind.CF,
+                      entryOrigin,
+                      ByteStreams.toByteArray(stream),
+                      Collections.singleton(DescriptorUtils.guessTypeDescriptor(name)));
+              visitor.accept(resource);
+            }
+          }
+        }
+      }
+    } catch (ZipException e) {
+      throw new ResourceException(
+          origin,
+          new CompilationError("Zip error while reading '" + archive + "': " + e.getMessage(), e));
+    } catch (IOException e) {
+      throw new ResourceException(
+          origin,
+          new CompilationError(
+              "I/O exception while reading '" + archive + "': " + e.getMessage(), e));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
index 94024ee..7713857 100644
--- a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -27,7 +27,7 @@
   }
 
   public String lookupNameAndDescriptor(String binaryName, int lineNumber)
-      throws IOException, ResourceException {
+      throws ResourceException {
     if (sourceMethodMapping.isEmpty()) {
       readLineNumbersFromClassFiles();
     }
@@ -35,13 +35,32 @@
     return lineMappings == null ? null : lineMappings.get(lineNumber);
   }
 
-  private void readLineNumbersFromClassFiles() throws ResourceException, IOException {
+  private void readLineNumbersFromClassFiles() throws ResourceException {
     ClassVisitor classVisitor = new ClassVisitor();
     for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
-      for (ProgramResource programResource : resourceProvider.getProgramResources()) {
-        if (programResource.getKind() == Kind.CF) {
-          new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
-              .accept(classVisitor, ClassReader.SKIP_FRAMES);
+      if (resourceProvider instanceof ArchiveResourceProvider) {
+        ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider;
+        provider.accept(
+            programResource -> {
+              try {
+                new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+                    .accept(classVisitor, ClassReader.SKIP_FRAMES);
+              } catch (IOException | ResourceException e) {
+                // Intentionally left empty because the addition of inline info for kotlin inline
+                // functions is a best effort.
+              }
+            });
+      } else {
+        for (ProgramResource programResource : resourceProvider.getProgramResources()) {
+          if (programResource.getKind() == Kind.CF) {
+            try {
+              new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+                  .accept(classVisitor, ClassReader.SKIP_FRAMES);
+            } catch (IOException e) {
+              // Intentionally left empty because the addition of inline info for kotlin inline
+              // functions is a best effort.
+            }
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c7324da..df13ac6 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -48,7 +48,6 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -120,7 +119,6 @@
 
     private KotlinInlineFunctionPositionRemapper(
         AppView<?> appView,
-        AndroidApp inputApp,
         PositionRemapper baseRemapper,
         CfLineToMethodMapper lineToMethodMapper) {
       this.appView = appView;
@@ -172,7 +170,7 @@
         }
         // This is the same position, so we should really not mark this as an inline position. Fall
         // through to the default case.
-      } catch (IOException | ResourceException ignored) {
+      } catch (ResourceException ignored) {
         // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
       }
       return baseRemapper.createRemappedPosition(position);
@@ -322,7 +320,7 @@
         // remapper to allow for remapping original positions to kotlin inline positions.
         KotlinInlineFunctionPositionRemapper kotlinRemapper =
             new KotlinInlineFunctionPositionRemapper(
-                appView, inputApp, positionRemapper, cfLineToMethodMapper);
+                appView, positionRemapper, cfLineToMethodMapper);
 
         for (DexEncodedMethod method : methods) {
           kotlinRemapper.currentMethod = method;