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) {