blob: d8fb33f9a297cd394e6b9dc29794a7244a06f4ea [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.shaking;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.OriginMatcher.hasPart;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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 LibraryProvidedProguardRulesFromClasspathOrLibraryTest
extends LibraryProvidedProguardRulesTestBase {
private enum HowToAdd {
API_CLASSPATH,
API_LIBRARY,
RULES_LIBRARYJARS
}
@Parameter(0)
public TestParameters parameters;
@Parameter(1)
public LibraryType libraryType;
@Parameter(2)
public HowToAdd howToAdd;
@Parameters(name = "{0}, libraryType: {1}, howToAdd: {2}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(),
LibraryType.values(),
HowToAdd.values());
}
private Path buildLibrary(List<String> rules) throws Exception {
return buildLibrary(libraryType, ImmutableList.of(Interface.class, Keep.class), rules);
}
private void addClasspathOrLibrary(Path library, R8FullTestBuilder builder) {
builder.applyIf(
howToAdd == HowToAdd.API_CLASSPATH,
b -> b.addClasspathFiles(library),
howToAdd == HowToAdd.API_LIBRARY,
b ->
b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
.addLibraryFiles(library),
b -> b.addKeepRules("-libraryjars " + library.toAbsolutePath()));
}
private CodeInspector runTest(List<String> rules) throws Exception {
Path library = buildLibrary(rules);
return testForR8(parameters.getBackend())
.addProgramClasses(A.class, B.class)
.apply(b -> addClasspathOrLibrary(library, b))
.setMinApi(parameters)
.apply(b -> ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(b.getBuilder(), true))
.compile()
.inspector();
}
private CodeInspector runTest(String rules) throws Exception {
return runTest(ImmutableList.of(rules));
}
@Test
public void testDuplicateClassesInLibraryJar() throws Exception {
assumeTrue(howToAdd == HowToAdd.API_LIBRARY);
assumeTrue(libraryType == LibraryType.JAR_WITH_RULES);
Path library =
buildLibrary(
ImmutableList.of(
"-keep class * implements " + Interface.class.getTypeName() + " { *; }"));
assertThrows(
CompilationFailedException.class,
() ->
testForR8(parameters.getBackend())
.addProgramClasses(A.class, B.class)
.addKeepRules("-libraryjars " + library.toAbsolutePath())
.addKeepRules("-libraryjars " + library.toAbsolutePath())
.setMinApi(parameters)
.allowStdoutMessages()
.apply(
b ->
ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(
b.getBuilder(), true))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorsMatch(
diagnosticType(DuplicateTypesDiagnostic.class))));
}
@Test
public void providedKeepRuleImplements() throws Exception {
CodeInspector inspector =
runTest("-keep class * implements " + Interface.class.getTypeName() + " { *; }");
// TODO(b/228319861): Read Proguard rules from AAR's.
assertThat(inspector.clazz(A.class), notIf(isPresent(), libraryType.isAar()));
assertThat(inspector.clazz(B.class), not(isPresent()));
}
@Test
public void providedKeepRuleAnnotated() throws Exception {
CodeInspector inspector = runTest("-keep @" + Keep.class.getTypeName() + " class * { *; }");
assertThat(inspector.clazz(A.class), not(isPresent()));
// TODO(b/228319861): Read Proguard rules from AAR's.
assertThat(inspector.clazz(B.class), notIf(isPresent(), libraryType.isAar()));
}
@Test
public void providedKeepRuleImplementsOrAnnotated() throws Exception {
CodeInspector inspector =
runTest(
ImmutableList.of(
"-keep class * implements " + Interface.class.getTypeName() + " { *; }",
"-keep @" + Keep.class.getTypeName() + " class * { *; }"));
// TODO(b/228319861): Read Proguard rules from AAR's.
assertThat(inspector.clazz(A.class), notIf(isPresent(), libraryType.isAar()));
assertThat(inspector.clazz(B.class), notIf(isPresent(), libraryType.isAar()));
}
@Test
public void providedKeepRuleSyntaxError() {
// TODO(b/228319861): Read Proguard rules from AAR's.
assumeTrue(!libraryType.isAar());
assertThrows(
CompilationFailedException.class,
() ->
testForR8(parameters.getBackend())
.addProgramFiles(buildLibrary(ImmutableList.of("error")))
.setMinApi(parameters)
.apply(
b ->
ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(
b.getBuilder(), true))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorThatMatches(
allOf(
diagnosticMessage(containsString("Expected char '-'")),
diagnosticOrigin(hasPart("META-INF/proguard/jar.rules")),
diagnosticOrigin(instanceOf(ArchiveEntryOrigin.class))))));
}
@Test
public void providedKeepRuleInjarsError() {
// TODO(b/228319861): Read Proguard rules from AAR's.
assumeTrue(!libraryType.isAar());
assertThrows(
CompilationFailedException.class,
() -> {
Path library = buildLibrary(ImmutableList.of("-injars some.jar"));
testForR8(parameters.getBackend())
.apply(b -> addClasspathOrLibrary(library, b))
.setMinApi(parameters)
.apply(
b -> ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(b.getBuilder(), true))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorThatMatches(
diagnosticMessage(
containsString("Options with file names are not supported"))));
});
}
@Test
public void providedKeepRuleIncludeError() {
// TODO(b/228319861): Read Proguard rules from AAR's.
assumeTrue(!libraryType.isAar());
assertThrows(
CompilationFailedException.class,
() -> {
Path library = buildLibrary(ImmutableList.of("-include other.rules"));
testForR8(parameters.getBackend())
.apply(b -> addClasspathOrLibrary(library, b))
.setMinApi(parameters)
.apply(
b -> ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(b.getBuilder(), true))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorThatMatches(
diagnosticMessage(
containsString("Options with file names are not supported"))));
});
}
static class TestProvider implements ClassFileResourceProvider, DataResourceProvider {
@Override
public ProgramResource getProgramResource(String descriptor) {
byte[] bytes;
try {
bytes = ByteStreams.toByteArray(Keep.class.getResourceAsStream("K.class"));
} catch (IOException e) {
return null;
}
return ProgramResource.fromBytes(
Origin.unknown(),
Kind.CF,
bytes,
Collections.singleton(DescriptorUtils.javaTypeToDescriptor(Keep.class.getTypeName())));
}
@Override
public Set<String> getClassDescriptors() {
return null;
}
@Override
public DataResourceProvider getDataResourceProvider() {
return this;
}
@Override
public void accept(Visitor visitor) throws ResourceException {
throw new ResourceException(Origin.unknown(), "Cannot provide data resources after all");
}
}
@Test
public void throwingDataResourceProvider() {
assumeFalse(howToAdd == HowToAdd.RULES_LIBRARYJARS);
// TODO(b/228319861): Read Proguard rules from AAR's.
assumeTrue(!libraryType.isAar());
assertThrows(
CompilationFailedException.class,
() ->
testForR8(parameters.getBackend())
.applyIf(
howToAdd == HowToAdd.API_CLASSPATH,
b -> b.addClasspathResourceProviders(new TestProvider()),
b ->
b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
.addLibraryResourceProviders(new TestProvider()))
.setMinApi(parameters)
.apply(
b ->
ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary(
b.getBuilder(), true))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorThatMatches(
allOf(
diagnosticMessage(
containsString("Cannot provide data resources after all")),
diagnosticOrigin(is(Origin.unknown()))))));
}
// Classes in the library.
@interface Keep {}
public interface Interface {}
// Classes in the program.
static class A implements Interface {}
@Keep
static class B {}
}