blob: 188e58d52e90e54ac51db3ad1f362a23634abfdc [file] [log] [blame]
// Copyright (c) 2018, 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.naming;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.KeepingDiagnosticHandler;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase {
private Backend backend;
@Parameterized.Parameters(name = "Backend: {0}")
public static Backend[] data() {
return ToolHelper.getBackends();
}
public AdaptResourceFileNamesTest(Backend backend) {
this.backend = backend;
}
private static final Path CF_DIR =
Paths.get(ToolHelper.EXAMPLES_CF_DIR).resolve("adaptresourcefilenames");
private static final Path TEST_JAR =
Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
.resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
private KeepingDiagnosticHandler diagnosticsHandler;
private ClassNameMapper mapper = null;
@Before
public void reset() {
diagnosticsHandler = new KeepingDiagnosticHandler();
mapper = null;
}
private static String getProguardConfig(
boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
String adaptResourceFilenamesRule;
if (enableAdaptResourceFileNames) {
adaptResourceFilenamesRule = "-adaptresourcefilenames";
if (adaptResourceFileNamesPathFilter != null) {
adaptResourceFilenamesRule += " " + adaptResourceFileNamesPathFilter;
}
} else {
adaptResourceFilenamesRule = "";
}
return String.join(
System.lineSeparator(),
adaptResourceFilenamesRule,
"-keep class adaptresourcefilenames.TestClass {",
" public static void main(...);",
"}");
}
private static String getProguardConfigWithNeverInline(
boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
return StringUtils.lines(
getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
"-neverinline class adaptresourcefilenames.A {",
" public void method();",
"}",
"-neverinline class adaptresourcefilenames.B$Inner {",
" public void method();",
"}",
"-assumemayhavesideeffects class adaptresourcefilenames.pkg.C {",
" void <init>();",
"}",
"-assumemayhavesideeffects class adaptresourcefilenames.pkg.innerpkg.D {",
" void <init>();",
"}",
"-neverclassinline class *");
}
@Test
public void testEnabled() throws Exception {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(
getProguardConfigWithNeverInline(true, null),
dataResourceConsumer,
ToolHelper.consumeString(this::checkR8Renamings));
// Check that the generated resources have the expected names.
for (DataEntryResource dataResource : getOriginalDataResources()) {
assertNotNull(
"Resource not renamed as expected: " + dataResource.getName(),
dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
}
}
@Test
public void testEnabledWithFilter() throws Exception {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(
getProguardConfigWithNeverInline(true, "**.md"),
dataResourceConsumer,
ToolHelper.consumeString(this::checkR8Renamings));
// Check that the generated resources have the expected names.
Map<String, String> expectedRenamings =
ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
for (DataEntryResource dataResource : getOriginalDataResources()) {
assertNotNull(
"Resource not renamed as expected: " + dataResource.getName(),
dataResourceConsumer.get(
expectedRenamings.getOrDefault(dataResource.getName(), dataResource.getName())));
}
}
@Test
public void testDisabled() throws Exception {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
// Check that none of the resources were renamed.
for (DataEntryResource dataResource : getOriginalDataResources()) {
assertNotNull(
"Resource not renamed as expected: " + dataResource.getName(),
dataResourceConsumer.get(dataResource.getName()));
}
}
@Test
public void testCollisionBehavior() throws Exception {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(
getProguardConfigWithNeverInline(true, null),
dataResourceConsumer,
ToolHelper.consumeString(this::checkR8Renamings),
ImmutableList.<DataEntryResource>builder()
.addAll(getOriginalDataResources())
.add(
DataEntryResource.fromBytes(
new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
.build());
assertEquals(1, diagnosticsHandler.warnings.size());
assertThat(
diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
}
@Test
public void testProguardBehavior() throws Exception {
Path inputJar = addDataResourcesToExistingJar(TEST_JAR, getOriginalDataResources());
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
Path proguardMapFile = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
runProguard6Raw(proguardedJar, inputJar, getProguardConfig(true, null), proguardMapFile);
// Extract the names of the generated resources.
Set<String> filenames = new HashSet<>();
ArchiveResourceProvider.fromArchive(proguardedJar, true)
.accept(
new Visitor() {
@Override
public void visit(DataDirectoryResource directory) {}
@Override
public void visit(DataEntryResource file) {
filenames.add(file.getName());
}
});
// Check that the generated resources have the expected names.
ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapFile);
for (DataEntryResource dataResource : getOriginalDataResources()) {
String expectedName = getExpectedRenamingFor(dataResource.getName(), mapper);
assertTrue(
"Resource not renamed to '" + expectedName + "' as expected: " + dataResource.getName(),
filenames.contains(expectedName));
}
}
@Test
public void testProguardCollisionBehavior() throws Exception {
List<DataEntryResource> originalDataResources = getOriginalDataResources();
Path inputJar =
addDataResourcesToExistingJar(
TEST_JAR,
ImmutableList.<DataEntryResource>builder()
.addAll(originalDataResources)
.add(
DataEntryResource.fromBytes(
new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
.build());
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
runProguard6Raw(
proguardedJar,
inputJar,
getProguardConfig(true, null),
null,
processResult -> {
assertEquals(0, processResult.exitCode);
assertThat(
processResult.stderr,
containsString(
"Warning: can't write resource [adaptresourcefilenames/b.txt] "
+ "(Duplicate jar entry [adaptresourcefilenames/b.txt])"));
});
assertEquals(
originalDataResources.size(),
getDataResources(ArchiveResourceProvider.fromArchive(proguardedJar, true))
.stream()
.filter(dataResource -> !dataResource.getName().equals("META-INF/MANIFEST.MF"))
.count());
}
private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
throws CompilationFailedException, IOException {
return compileWithR8(proguardConfig, dataResourceConsumer, null);
}
private AndroidApp compileWithR8(
String proguardConfig,
DataResourceConsumer dataResourceConsumer,
StringConsumer proguardMapConsumer)
throws CompilationFailedException, IOException {
return compileWithR8(
proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
}
private AndroidApp compileWithR8(
String proguardConfig,
DataResourceConsumer dataResourceConsumer,
StringConsumer proguardMapConsumer,
List<DataEntryResource> dataResources)
throws CompilationFailedException, IOException {
R8Command command =
ToolHelper.allowTestProguardOptions(
ToolHelper.prepareR8CommandBuilder(
getAndroidApp(dataResources), emptyConsumer(backend), diagnosticsHandler)
.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
.addLibraryFiles(runtimeJar(backend))
.build();
return ToolHelper.runR8(
command,
options -> {
options.dataResourceConsumer = dataResourceConsumer;
options.proguardMapConsumer = proguardMapConsumer;
});
}
private void checkR8Renamings(String proguardMap) {
try {
// Check that the renamings are as expected. These exact renamings are not important as
// such, but the test expectations rely on them.
mapper = ClassNameMapper.mapperFromString(proguardMap);
assertEquals(
"adaptresourcefilenames.TestClass",
mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
assertEquals(
"adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
assertEquals(
"adaptresourcefilenames.B$Inner",
mapper.deobfuscateClassName("adaptresourcefilenames.a"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
dataResources.forEach(builder::addDataResource);
return builder.build();
}
private static List<DataEntryResource> getOriginalDataResources() {
List<String> filenames =
ImmutableList.of(
// Filename with simple name in root directory.
"TestClass",
"B",
// Filename with qualified name in root directory.
"adaptresourcefilenames.TestClass",
"adaptresourcefilenames.B",
// Filename with qualified directory name in root directory.
"adaptresourcefilenames/TestClass",
"adaptresourcefilenames/B",
// Filename with simple name in sub directory.
"foo/bar/baz/TestClass",
"foo/bar/baz/B",
// Filename with qualified name in sub directory.
"foo/bar/baz/adaptresourcefiles.TestClass",
"foo/bar/baz/adaptresourcefiles.B",
// Filename with qualified directory name in sub directory.
"foo/bar/baz/adaptresourcefilenames/TestClass",
"foo/bar/baz/adaptresourcefilenames/B",
//
// SUFFIX VARIANTS:
//
// Filename with simple name and extension in root directory.
"TestClass.txt",
"B.txt",
// Filename with qualified name and extension in root directory.
"adaptresourcefilenames.TestClass.txt",
"adaptresourcefilenames.B.txt",
// Filename with qualified directory name and extension in root directory.
"adaptresourcefilenames/TestClass.txt",
"adaptresourcefilenames/B.txt",
// Filename with simple name and extension in sub directory.
"foo/bar/baz/TestClass.txt",
"foo/bar/baz/B.txt",
// Filename with qualified name and extension in sub directory.
"foo/bar/baz/adaptresourcefiles.TestClass.txt",
"foo/bar/baz/adaptresourcefiles.B.txt",
// Filename with qualified directory name and extension in sub directory.
"foo/bar/baz/adaptresourcefilenames/TestClass.txt",
"foo/bar/baz/adaptresourcefilenames/B.txt",
// Filename with other extension (used to test filtering).
"adaptresourcefilenames/TestClass.md",
"adaptresourcefilenames/B.md",
// Filename with dot suffix only.
"adaptresourcefilenames/TestClass.",
"adaptresourcefilenames/B.",
// Filename with dot suffix and extension.
"adaptresourcefilenames/TestClass.suffix.txt",
"adaptresourcefilenames/B.suffix.txt",
// Filename with dash suffix and extension.
"adaptresourcefilenames/TestClass-suffix.txt",
"adaptresourcefilenames/B-suffix.txt",
// Filename with dollar suffix and extension.
"adaptresourcefilenames/TestClass$suffix.txt",
"adaptresourcefilenames/B$suffix.txt",
// Filename with dollar suffix matching inner class and extension.
"adaptresourcefilenames/TestClass$Inner.txt",
"adaptresourcefilenames/B$Inner.txt",
// Filename with underscore suffix and extension.
"adaptresourcefilenames/TestClass_suffix.txt",
"adaptresourcefilenames/B_suffix.txt",
// Filename with whitespace suffix and extension.
"adaptresourcefilenames/TestClass suffix.txt",
"adaptresourcefilenames/B suffix.txt",
// Filename with identifier suffix and extension.
"adaptresourcefilenames/TestClasssuffix.txt",
"adaptresourcefilenames/Bsuffix.txt",
// Filename with numeric suffix and extension.
"adaptresourcefilenames/TestClass42.txt",
"adaptresourcefilenames/B42.txt",
//
// PREFIX VARIANTS:
//
// Filename with dot prefix and extension.
"adaptresourcefilenames/prefix.TestClass.txt",
"adaptresourcefilenames/prefix.B.txt",
// Filename with dash prefix and extension.
"adaptresourcefilenames/prefix-TestClass.txt",
"adaptresourcefilenames/prefix-B.txt",
// Filename with dollar prefix and extension.
"adaptresourcefilenames/prefix$TestClass.txt",
"adaptresourcefilenames/prefix$B.txt",
// Filename with identifier prefix and extension.
"adaptresourcefilenames/prefixTestClass.txt",
"adaptresourcefilenames/prefixB.txt",
// Filename with numeric prefix and extension.
"adaptresourcefilenames/42TestClass.txt",
"adaptresourcefilenames/42B.txt",
//
// PACKAGE RENAMING TESTS:
//
// Filename that matches a type, but only the directory should be renamed.
"adaptresourcefilenames/pkg/C",
// Filename that matches a type that should be renamed.
"adaptresourcefilenames/pkg/C.txt",
// Filename that does not match a type, but where the directory should be renamed.
"adaptresourcefilenames/pkg/file.txt",
// Filename that does not match a type, but where a directory-prefix should be renamed.
"adaptresourcefilenames/pkg/directory/file.txt",
// Filename that matches a type, but only the directory should be renamed.
"adaptresourcefilenames/pkg/innerpkg/D",
// Filename that matches a type that should be renamed.
"adaptresourcefilenames/pkg/innerpkg/D.txt",
// Filename that does not match a type, but where the directory should be renamed.
"adaptresourcefilenames/pkg/innerpkg/file.txt",
// Filename that does not match a type, but where a directory-prefix should be renamed.
"adaptresourcefilenames/pkg/innerpkg/directory/file.txt"
);
return filenames
.stream()
.map(filename -> DataEntryResource.fromBytes(new byte[0], filename, Origin.unknown()))
.collect(Collectors.toList());
}
private static String getExpectedRenamingFor(String filename, ClassNameMapper mapper) {
String typeName = null;
String suffix = null;
switch (filename) {
// Filename with dot only.
case "adaptresourcefilenames/B.":
typeName = "adaptresourcefilenames.B";
suffix = ".";
break;
// Filename with extension.
case "adaptresourcefilenames/B.txt":
typeName = "adaptresourcefilenames.B";
suffix = ".txt";
break;
// Filename with other extension (used to test filtering).
case "adaptresourcefilenames/B.md":
typeName = "adaptresourcefilenames.B";
suffix = ".md";
break;
// Filename with dot suffix and extension.
case "adaptresourcefilenames/B.suffix.txt":
typeName = "adaptresourcefilenames.B";
suffix = ".suffix.txt";
break;
// Filename with dash suffix and extension.
case "adaptresourcefilenames/B-suffix.txt":
typeName = "adaptresourcefilenames.B";
suffix = "-suffix.txt";
break;
// Filename with dollar suffix and extension.
case "adaptresourcefilenames/B$suffix.txt":
typeName = "adaptresourcefilenames.B";
suffix = "$suffix.txt";
break;
// Filename with dollar suffix matching inner class and extension.
case "adaptresourcefilenames/B$Inner.txt":
typeName = "adaptresourcefilenames.B$Inner";
suffix = ".txt";
break;
// Filename with underscore suffix and extension.
case "adaptresourcefilenames/B_suffix.txt":
typeName = "adaptresourcefilenames.B";
suffix = "_suffix.txt";
break;
// Filename with whitespace suffix and extension.
case "adaptresourcefilenames/B suffix.txt":
typeName = "adaptresourcefilenames.B";
suffix = " suffix.txt";
break;
//
// PACKAGE RENAMING TESTS
//
case "adaptresourcefilenames/pkg/C.txt":
typeName = "adaptresourcefilenames.pkg.C";
suffix = ".txt";
break;
case "adaptresourcefilenames/pkg/innerpkg/D.txt":
typeName = "adaptresourcefilenames.pkg.innerpkg.D";
suffix = ".txt";
break;
}
if (typeName != null) {
String renamedName = mapper.getObfuscatedToOriginalMapping().inverse.get(typeName);
assertNotNull(renamedName);
assertNotEquals(typeName, renamedName);
return renamedName.replace('.', '/') + suffix;
}
// Renamings for files in directories that match packages that have been renamed,
// but where the filename itself should not be renamed.
String samePackageAsType = null;
switch (filename) {
case "adaptresourcefilenames/pkg/C":
samePackageAsType = "adaptresourcefilenames.pkg.C";
suffix = "C";
break;
case "adaptresourcefilenames/pkg/file.txt":
samePackageAsType = "adaptresourcefilenames.pkg.C";
suffix = "file.txt";
break;
case "adaptresourcefilenames/pkg/directory/file.txt":
samePackageAsType = "adaptresourcefilenames.pkg.C";
suffix = "directory/file.txt";
break;
case "adaptresourcefilenames/pkg/innerpkg/D":
samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
suffix = "D";
break;
case "adaptresourcefilenames/pkg/innerpkg/file.txt":
samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
suffix = "file.txt";
break;
case "adaptresourcefilenames/pkg/innerpkg/directory/file.txt":
samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
suffix = "directory/file.txt";
break;
}
if (samePackageAsType != null) {
String renamedName = mapper.getObfuscatedToOriginalMapping().inverse.get(samePackageAsType);
assertNotNull(renamedName);
assertNotEquals(samePackageAsType, renamedName);
if (renamedName.contains(".")) {
String renamedPackageName = renamedName.substring(0, renamedName.lastIndexOf('.'));
return renamedPackageName.replace('.', '/') + "/" + suffix;
}
}
return filename;
}
}