blob: 5e340612f5159daa76857153aed8395462bdefff [file] [log] [blame]
// Copyright (c) 2024, 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.keepanno;
import com.android.tools.r8.ExternalR8TestBuilder;
import com.android.tools.r8.ProguardTestBuilder;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.keepanno.KeepAnnoParameters.KeepAnnoConfig;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.junit.rules.TemporaryFolder;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public abstract class KeepAnnoTestBuilder {
public static KeepAnnoTestBuilder forKeepAnnoTest(KeepAnnoParameters params, TemporaryFolder temp)
throws IOException {
switch (params.config()) {
case REFERENCE:
return new ReferenceBuilder(params, temp);
case R8_DIRECT:
case R8_NORMALIZED:
case R8_RULES:
return new R8NativeBuilder(params, temp);
case R8_LEGACY:
return new R8LegacyBuilder(params, temp);
case PG:
return new PGBuilder(params, temp);
default:
throw new IllegalStateException("Unexpected keep anno config: " + params.config());
}
}
private final KeepAnnoParameters keepAnnoParams;
private KeepAnnoTestBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
this.keepAnnoParams = params;
}
public final TestParameters parameters() {
return keepAnnoParams.parameters();
}
public abstract KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses)
throws IOException;
public final KeepAnnoTestBuilder addProgramClassFileData(byte[]... programClasses)
throws IOException {
return addProgramClassFileData(Arrays.asList(programClasses));
}
public abstract KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException;
public final KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass) {
return applyIfShrinker(b -> b.addKeepMainRule(mainClass));
}
public final KeepAnnoTestBuilder addKeepClassRules(Class<?>... classes) {
return applyIfShrinker(b -> b.addKeepClassRules(classes));
}
public abstract SingleTestRunResult<?> run(Class<?> mainClass) throws Exception;
public KeepAnnoTestBuilder applyIfShrinker(
ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
applyIfR8(builderConsumer);
applyIfPG(builderConsumer::accept);
return this;
}
public KeepAnnoTestBuilder applyIfR8(
ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
return this;
}
public KeepAnnoTestBuilder applyIfR8Current(ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
return this;
}
public KeepAnnoTestBuilder applyIfPG(ThrowableConsumer<ProguardTestBuilder> builderConsumer) {
return this;
}
public KeepAnnoTestBuilder enableNativeInterpretation() {
return this;
}
public final KeepAnnoTestBuilder setExcludedOuterClass(Class<?> clazz) {
return applyIfPG(b -> b.addDontWarn(clazz));
}
public KeepAnnoTestBuilder allowUnusedProguardConfigurationRules() {
return this;
}
public final KeepAnnoTestBuilder allowAccessModification() {
applyIfShrinker(TestShrinkerBuilder::allowAccessModification);
return this;
}
public final KeepAnnoTestBuilder printRules() {
return inspectOutputConfig(System.out::println);
}
public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
// Default to ignore the consumer.
return this;
}
private static class ReferenceBuilder extends KeepAnnoTestBuilder {
private final TestBuilder<? extends SingleTestRunResult<?>, ?> builder;
public ReferenceBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
super(params, temp);
if (parameters().isCfRuntime()) {
builder = TestBase.testForJvm(temp);
} else {
assert parameters().isDexRuntime();
builder = TestBase.testForD8(temp).setMinApi(parameters());
}
}
@Override
public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) {
builder.addProgramClasses(programClasses);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException {
builder.addProgramClassFileData(programClasses);
return this;
}
@Override
public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
return builder.run(parameters().getRuntime(), mainClass);
}
}
private static class R8NativeBuilder extends KeepAnnoTestBuilder {
private final R8FullTestBuilder builder;
private List<Consumer<R8TestCompileResult>> compileResultConsumers = new ArrayList<>();
private final boolean normalizeEdges;
private final boolean extractRules;
private boolean enableNative = false;
private R8NativeBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
super(params, temp);
builder =
TestBase.testForR8(temp, parameters().getBackend())
.enableExperimentalKeepAnnotations()
.setMinApi(parameters());
extractRules = params.config() == KeepAnnoConfig.R8_RULES;
normalizeEdges = params.config() == KeepAnnoConfig.R8_NORMALIZED;
if (normalizeEdges) {
builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(true);
} else {
builder.getBuilder().setEnableExperimentalKeepAnnotations(true);
builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(false);
}
}
@Override
public KeepAnnoTestBuilder allowUnusedProguardConfigurationRules() {
if (!enableNative) {
builder.allowUnusedProguardConfigurationRules();
}
return this;
}
@Override
public KeepAnnoTestBuilder enableNativeInterpretation() {
if (extractRules) {
return this;
}
enableNative = true;
// TODO(b/323816623): The compiler assumes that this is known prior to tracing.
// We should update the compiler to always read annotations and only remove them after
// initial tracing regardless of -keepattributes.
builder.addKeepRuntimeVisibleAnnotations();
builder.addKeepRuntimeInvisibleAnnotations();
// This enables native interpretation of all keep annotations.
builder.addOptionsModification(
o -> {
o.testing.enableExtractedKeepAnnotations = true;
o.testing.enableEmbeddedKeepAnnotations = true;
});
// This disables all reading of annotations in the command reader.
builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(false);
return this;
}
@Override
public KeepAnnoTestBuilder applyIfR8(
ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
builderConsumer.acceptWithRuntimeException(builder);
return this;
}
@Override
public KeepAnnoTestBuilder applyIfR8Current(
ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
builderConsumer.acceptWithRuntimeException(builder);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
for (Class<?> programClass : programClasses) {
extractAndAdd(ToolHelper.getClassAsBytes(programClass));
}
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException {
for (byte[] programClass : programClasses) {
extractAndAdd(programClass);
}
return this;
}
@Override
public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
compileResultConsumers.add(
result -> configConsumer.accept(result.getProguardConfiguration()));
return this;
}
private void extractAndAdd(byte[] classFileData) {
builder.addProgramClassFileData(classFileData);
if (normalizeEdges) {
List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
if (!declarations.isEmpty()) {
String binaryName =
DescriptorUtils.getBinaryNameFromDescriptor(
TestBase.extractClassDescriptor(classFileData));
String synthesizingTarget = binaryName + "$$ExtractedKeepEdges";
ClassWriter classWriter = new ClassWriter(InternalOptions.ASM_VERSION);
classWriter.visit(
Opcodes.V1_8,
ClassAccessFlags.createPublicFinalSynthetic().getAsCfAccessFlags(),
synthesizingTarget,
null,
"java/lang/Object",
null);
KeepEdgeWriter.writeExtractedEdges(
declarations,
(descriptor, visible) ->
KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible)));
classWriter.visitEnd();
builder
.getBuilder()
.addClassProgramData(
classWriter.toByteArray(),
new Origin(Origin.root()) {
@Override
public String part() {
return "edge-extraction";
}
});
}
}
}
@Override
public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
R8TestCompileResult compileResult = builder.compile();
compileResultConsumers.forEach(fn -> fn.accept(compileResult));
return compileResult.run(parameters().getRuntime(), mainClass);
}
}
private static class R8LegacyBuilder extends KeepAnnoTestBuilder {
private final KeepRuleExtractorOptions extractorOptions =
KeepRuleExtractorOptions.getR8Options();
private final ExternalR8TestBuilder builder;
private final List<Consumer<List<String>>> configConsumers = new ArrayList<>();
public R8LegacyBuilder(KeepAnnoParameters params, TemporaryFolder temp) throws IOException {
super(params, temp);
builder =
TestBase.testForExternalR8(temp, parameters().getBackend())
.useProvidedR8(KeepAnnoTestUtils.R8_LIB)
.addProgramFiles(KeepAnnoTestUtils.getKeepAnnoLib(temp))
.setMinApi(parameters());
}
@Override
public KeepAnnoTestBuilder applyIfR8(
ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
builderConsumer.acceptWithRuntimeException(builder);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions);
builder.addProgramClasses(programClasses);
builder.addKeepRules(rules);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException {
List<String> rules =
KeepAnnoTestUtils.extractRulesFromBytes(programClasses, extractorOptions);
builder.addProgramClassFileData(programClasses);
builder.addKeepRules(rules);
return this;
}
@Override
public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
configConsumers.add(lines -> configConsumer.accept(String.join("\n", lines)));
return this;
}
@Override
public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
configConsumers.forEach(fn -> fn.accept(builder.getConfig()));
return builder.run(parameters().getRuntime(), mainClass);
}
}
private static class PGBuilder extends KeepAnnoTestBuilder {
private final KeepRuleExtractorOptions extractorOptions =
KeepRuleExtractorOptions.getPgOptions();
private final ProguardTestBuilder builder;
private final List<Consumer<List<String>>> configConsumers = new ArrayList<>();
public PGBuilder(KeepAnnoParameters params, TemporaryFolder temp) throws IOException {
super(params, temp);
builder =
TestBase.testForProguard(KeepAnnoTestUtils.PG_VERSION, temp)
.addProgramFiles(KeepAnnoTestUtils.getKeepAnnoLib(temp))
.setMinApi(parameters());
}
@Override
public KeepAnnoTestBuilder applyIfPG(ThrowableConsumer<ProguardTestBuilder> builderConsumer) {
builderConsumer.acceptWithRuntimeException(builder);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions);
builder.addProgramClasses(programClasses);
builder.addKeepRules(rules);
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException {
List<String> rules =
KeepAnnoTestUtils.extractRulesFromBytes(programClasses, extractorOptions);
builder.addProgramClassFileData(programClasses);
builder.addKeepRules(rules);
return this;
}
@Override
public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
configConsumers.add(lines -> configConsumer.accept(String.join("\n", lines)));
return this;
}
@Override
public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
configConsumers.forEach(fn -> fn.accept(builder.getConfig()));
return builder.run(parameters().getRuntime(), mainClass);
}
}
}