Reproduce OOM caused by large stack trace

Bug: b/569311697
Change-Id: I185b69445256a254374bb08fa9808d6ac031a923
diff --git a/src/test/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerInvalidKeepRulesTest.java b/src/test/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerInvalidKeepRulesTest.java
new file mode 100644
index 0000000..6035ecf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerInvalidKeepRulesTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2026, 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.libanalyzer;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.libanalyzer.LibraryAnalyzerTestBuilder.AarOrJar;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.internal.BooleanBox;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryAnalyzerInvalidKeepRulesTest extends TestBase {
+
+  @Parameter(0)
+  public AarOrJar aarOrJar;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
+  public static List<Object[]> data() {
+    return buildParameters(AarOrJar.values(), getTestParameters().withNoneRuntime().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    BooleanBox oom = new BooleanBox();
+    testForLibraryAnalyzer()
+        .addProgramClasses(Main.class)
+        .addDefaultLibrary()
+        .addKeepRules("-classobfuscationdictionary dictionary.txt")
+        .apply(
+            b -> {
+              for (int i = 0; i < 16; i++) {
+                b.addSecondaryAarOrJar("-keep class " + Main.class.getTypeName());
+              }
+            })
+        .setAarOrJar(aarOrJar)
+        .setMinApi(AndroidApiLevel.getDefault())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              try {
+                for (Diagnostic diagnostic :
+                    Iterables.concat(diagnostics.getErrors(), diagnostics.getWarnings())) {
+                  diagnostic.getDiagnosticMessage();
+                }
+              } catch (OutOfMemoryError e) {
+                oom.set();
+              }
+            })
+        .inspectD8CompileResult(D8CompileResultInspector::assertPresent)
+        .inspectR8CompileResult(R8CompileResultInspector::assertAbsent)
+        .inspectValidateConsumerKeepRulesResult(
+            ValidateConsumerKeepRulesResultInspector::assertAbsent);
+    assertTrue(oom.get());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCompileResult.java b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCompileResult.java
index 6766406..ac2d21f 100644
--- a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCompileResult.java
@@ -23,14 +23,20 @@
   public <E extends Exception> LibraryAnalyzerCompileResult inspectD8CompileResult(
       ThrowingConsumer<D8CompileResultInspector, E> inspector) {
     inspector.acceptWithRuntimeException(
-        new D8CompileResultInspector(LibraryAnalyzerResult.getD8CompileResult()));
+        new D8CompileResultInspector(
+            LibraryAnalyzerResult.hasD8CompileResult()
+                ? LibraryAnalyzerResult.getD8CompileResult()
+                : null));
     return this;
   }
 
   public <E extends Exception> LibraryAnalyzerCompileResult inspectR8CompileResult(
       ThrowingConsumer<R8CompileResultInspector, E> inspector) {
     inspector.acceptWithRuntimeException(
-        new R8CompileResultInspector(LibraryAnalyzerResult.getR8CompileResult()));
+        new R8CompileResultInspector(
+            LibraryAnalyzerResult.hasR8CompileResult()
+                ? LibraryAnalyzerResult.getR8CompileResult()
+                : null));
     return this;
   }
 
@@ -38,7 +44,9 @@
       ThrowingConsumer<ValidateConsumerKeepRulesResultInspector, E> inspector) {
     inspector.acceptWithRuntimeException(
         new ValidateConsumerKeepRulesResultInspector(
-            LibraryAnalyzerResult.getValidateConsumerKeepRulesResult()));
+            LibraryAnalyzerResult.hasValidateConsumerKeepRulesResult()
+                ? LibraryAnalyzerResult.getValidateConsumerKeepRulesResult()
+                : null));
     return this;
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerResultInspector.java b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerResultInspector.java
index 0735000..b29478b 100644
--- a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerResultInspector.java
+++ b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerResultInspector.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.libanalyzer;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 public abstract class LibraryAnalyzerResultInspector<
     T, I extends LibraryAnalyzerResultInspector<T, I>> {
@@ -14,6 +15,11 @@
     this.result = result;
   }
 
+  public I assertAbsent() {
+    assertNull(result);
+    return self();
+  }
+
   public I assertPresent() {
     assertNotNull(result);
     return self();
diff --git a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerTestBuilder.java
index 624c62d..93ce390 100644
--- a/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerTestBuilder.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.internal.Box;
+import com.android.tools.r8.utils.internal.ThrowingConsumer;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
@@ -45,6 +46,7 @@
   private AarOrJar aarOrJar;
   private AndroidApp.Builder androidAppBuilder = AndroidApp.builder();
   private List<String> keepRules = new ArrayList<>();
+  private List<Collection<String>> secondaryKeepRules = new ArrayList<>();
 
   private LibraryAnalyzerTestBuilder(TemporaryFolder temp) {
     this.commandBuilder = LibraryAnalyzerCommand.builder(diagnostics);
@@ -55,6 +57,12 @@
     return new LibraryAnalyzerTestBuilder(temp);
   }
 
+  public <E extends Exception> LibraryAnalyzerTestBuilder apply(
+      ThrowingConsumer<LibraryAnalyzerTestBuilder, E> fn) throws E {
+    fn.accept(this);
+    return this;
+  }
+
   public LibraryAnalyzerTestBuilder addDefaultLibrary() {
     commandBuilder.addLibraryPath(ToolHelper.getMostRecentAndroidJar());
     return this;
@@ -69,6 +77,15 @@
     return this;
   }
 
+  public LibraryAnalyzerTestBuilder addSecondaryAarOrJar(String... keepRules) {
+    return addSecondaryAarOrJar(Arrays.asList(keepRules));
+  }
+
+  public LibraryAnalyzerTestBuilder addSecondaryAarOrJar(Collection<String> keepRules) {
+    this.secondaryKeepRules.add(keepRules);
+    return this;
+  }
+
   public LibraryAnalyzerTestBuilder addProgramClasses(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       androidAppBuilder.addProgramFile(
@@ -111,9 +128,9 @@
     assertNotNull("Must call setAar() or setJar() to specify input type.", aarOrJar);
     Box<LibraryAnalyzerResult> LibraryAnalyzerResult = new Box<>();
     if (aarOrJar == AarOrJar.AAR) {
-      commandBuilder.addAarPath(createAar());
+      createAars().forEach(commandBuilder::addAarPath);
     } else {
-      commandBuilder.addJarPath(createJar());
+      createJars().forEach(commandBuilder::addJarPath);
     }
     LibraryAnalyzerCommand command =
         commandBuilder.setInternalOutputConsumer(LibraryAnalyzerResult::set).build();
@@ -122,20 +139,30 @@
     return new LibraryAnalyzerCompileResult(LibraryAnalyzerResult.get());
   }
 
-  private Path createAar() {
+  private List<Path> createAars() {
+    List<Path> aarPaths = new ArrayList<>();
+    aarPaths.add(createAar(androidAppBuilder.build(), keepRules));
+    androidAppBuilder = null;
+    keepRules = null;
+    for (Collection<String> secondaryKeepRule : secondaryKeepRules) {
+      aarPaths.add(createAar(AndroidApp.builder().build(), secondaryKeepRule));
+    }
+    secondaryKeepRules = null;
+    return aarPaths;
+  }
+
+  private Path createAar(AndroidApp app, Collection<String> keepRules) {
     try {
-      Path aarDir = temp.newFolder("aar").toPath();
+      Path aarDir = temp.newFolder().toPath();
       Path classesJarPath = aarDir.resolve("classes.jar");
-      androidAppBuilder.build().writeToZipForTesting(classesJarPath, OutputMode.ClassFile);
-      androidAppBuilder = null;
+      app.writeToZipForTesting(classesJarPath, OutputMode.ClassFile);
 
       if (!keepRules.isEmpty()) {
         Path proguardTxtPath = aarDir.resolve("proguard.txt");
         Files.write(proguardTxtPath, keepRules, StandardCharsets.UTF_8);
-        keepRules = null;
       }
 
-      Path aarPath = temp.newFile("lib.aar").toPath();
+      Path aarPath = temp.newFolder().toPath().resolve("lib.aar");
       ZipUtils.zip(aarPath, aarDir);
       return aarPath;
     } catch (IOException e) {
@@ -143,20 +170,30 @@
     }
   }
 
-  private Path createJar() {
+  private List<Path> createJars() {
+    List<Path> jarPaths = new ArrayList<>();
+    jarPaths.add(createJar(androidAppBuilder.build(), keepRules));
+    androidAppBuilder = null;
+    keepRules = null;
+    for (Collection<String> secondaryKeepRule : secondaryKeepRules) {
+      jarPaths.add(createJar(AndroidApp.builder().build(), secondaryKeepRule));
+    }
+    secondaryKeepRules = null;
+    return jarPaths;
+  }
+
+  private Path createJar(AndroidApp app, Collection<String> keepRules) {
     try {
-      Path jarDir = temp.newFolder("jar").toPath();
-      androidAppBuilder.build().writeToDirectory(jarDir, OutputMode.ClassFile);
-      androidAppBuilder = null;
+      Path jarDir = temp.newFolder().toPath();
+      app.writeToDirectory(jarDir, OutputMode.ClassFile);
 
       if (!keepRules.isEmpty()) {
         Path libProPath = jarDir.resolve("META-INF/proguard/lib.pro");
         Files.createDirectories(libProPath.getParent());
         Files.write(libProPath, keepRules, StandardCharsets.UTF_8);
-        keepRules = null;
       }
 
-      Path jarPath = temp.newFile("lib.jar").toPath();
+      Path jarPath = temp.newFolder().toPath().resolve("lib.jar");
       ZipUtils.zip(jarPath, jarDir);
       return jarPath;
     } catch (IOException e) {