Merge "Pass lense from LambdaRewriter to Outliner."
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 4eead29..99c97a4 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -87,7 +87,7 @@
this.include = include;
}
- private void readArchive(ArchiveEntryConsumer consumer) throws IOException {
+ void readArchive(ArchiveEntryConsumer consumer) throws IOException {
try (ZipFile zipFile = supplier.open()) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5980a0c..1a59ef8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -136,6 +136,12 @@
mode = defaultCompilationMode();
}
+ // Internal constructor for testing.
+ Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ super(AndroidApp.builder(app, new Reporter(diagnosticsHandler)));
+ mode = defaultCompilationMode();
+ }
+
/**
* Get current compilation mode.
*/
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index b6443a4..6b8f9c5 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -35,6 +35,10 @@
return new ZipDataEntryResource(zip, entry);
}
+ default DataEntryResource withName(String name) {
+ return new NestedDataEntryResource(name, null, this);
+ }
+
class ByteDataEntryResource implements DataEntryResource {
private final byte[] bytes;
@@ -124,4 +128,35 @@
}
}
}
+
+ /**
+ * A resource that has the same contents as another resource, but with a different name or origin.
+ */
+ class NestedDataEntryResource implements DataEntryResource {
+
+ private final String name;
+ private final Origin origin;
+ private final DataEntryResource resource;
+
+ public NestedDataEntryResource(String name, Origin origin, DataEntryResource resource) {
+ this.name = name;
+ this.origin = origin;
+ this.resource = resource;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return resource.getByteStream();
+ }
+
+ @Override
+ public String getName() {
+ return name != null ? name : resource.getName();
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin != null ? origin : resource.getOrigin();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4eb4b37..e3904fb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -83,6 +83,10 @@
super(app);
}
+ private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ super(app, diagnosticsHandler);
+ }
+
// Internal
void internalForceProguardCompatibility() {
@@ -455,6 +459,11 @@
return new Builder(app);
}
+ // Internal builder to start from an existing AndroidApp.
+ static Builder builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ return new Builder(app, diagnosticsHandler);
+ }
+
/**
* Parse the R8 command-line.
*
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 3d18ea3..f9e95ec 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -34,15 +34,18 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ObjectArrays;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -309,7 +312,6 @@
}
DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
if (dataResourceConsumer != null) {
-
List<DataResourceProvider> dataResourceProviders = application.programResourceProviders
.stream()
.map(ProgramResourceProvider::getDataResourceProvider)
@@ -318,6 +320,7 @@
ResourceAdapter resourceAdapter =
new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+ Set<String> generatedResourceNames = new HashSet<>();
for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
try {
@@ -331,8 +334,13 @@
@Override
public void visit(DataEntryResource file) {
- dataResourceConsumer.accept(
- resourceAdapter.adaptFileContentsIfNeeded(file), options.reporter);
+ DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
+ if (generatedResourceNames.add(adapted.getName())) {
+ dataResourceConsumer.accept(adapted, options.reporter);
+ } else {
+ options.reporter.warning(
+ new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+ }
options.reporter.failIfPendingErrors();
}
});
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index e325933..673fa6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -18,6 +19,8 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntStack;
import java.io.InputStream;
import java.nio.charset.Charset;
@@ -39,28 +42,59 @@
this.options = options;
}
- public DataEntryResource adaptFileContentsIfNeeded(DataEntryResource file) {
- ProguardPathFilter filter = options.proguardConfiguration.getAdaptResourceFileContents();
- return filter.isEnabled()
- && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
- && filter.matches(file.getName())
- ? adaptFileContents(file)
- : file;
+ public DataEntryResource adaptIfNeeded(DataEntryResource file) {
+ // Adapt name, if needed.
+ ProguardPathFilter adaptResourceFileNamesFilter =
+ options.proguardConfiguration.getAdaptResourceFilenames();
+ String name =
+ adaptResourceFileNamesFilter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && adaptResourceFileNamesFilter.matches(file.getName())
+ ? adaptFilename(file)
+ : file.getName();
+ assert name != null;
+ // Adapt contents, if needed.
+ ProguardPathFilter adaptResourceFileContentsFilter =
+ options.proguardConfiguration.getAdaptResourceFileContents();
+ byte[] contents =
+ adaptResourceFileContentsFilter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && adaptResourceFileContentsFilter.matches(file.getName())
+ ? adaptFileContents(file)
+ : null;
+ // Return a new resource if the name or contents changed. Otherwise return the original
+ // resource as it was.
+ if (contents != null) {
+ // File contents was adapted. Return a new resource that has the new contents, and a new name,
+ // if the filename was adapted.
+ return DataEntryResource.fromBytes(contents, name, file.getOrigin());
+ }
+ if (!name.equals(file.getName())) {
+ // File contents was not adapted, but filename was.
+ return file.withName(name);
+ }
+ // Neither file contents nor filename was adapted.
+ return file;
+ }
+
+ private String adaptFilename(DataEntryResource file) {
+ FilenameAdapter adapter = new FilenameAdapter(file.getName());
+ if (adapter.run()) {
+ return adapter.getResult();
+ }
+ return file.getName();
}
// According to the Proguard documentation, the resource files should be parsed and written using
// the platform's default character set.
- private DataEntryResource adaptFileContents(DataEntryResource file) {
+ private byte[] adaptFileContents(DataEntryResource file) {
try (InputStream in = file.getByteStream()) {
byte[] bytes = ByteStreams.toByteArray(in);
String contents = new String(bytes, Charset.defaultCharset());
FileContentsAdapter adapter = new FileContentsAdapter(contents);
if (adapter.run()) {
- return DataEntryResource.fromBytes(
- adapter.getResult().getBytes(Charset.defaultCharset()),
- file.getName(),
- file.getOrigin());
+ return adapter.getResult().getBytes(Charset.defaultCharset());
}
} catch (ResourceException e) {
options.reporter.error(
@@ -68,12 +102,14 @@
} catch (Exception e) {
options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
}
- return file;
+ // Return null to signal that the file contents did not change. Otherwise we would have to copy
+ // the original file for no reason.
+ return null;
}
- private class FileContentsAdapter {
+ private abstract class StringAdapter {
- private final String contents;
+ protected final String contents;
private final StringBuilder result = new StringBuilder();
// If any type names in `contents` have been updated. If this flag is still true in the end,
@@ -82,8 +118,21 @@
private int outputFrom = 0;
private int position = 0;
- public FileContentsAdapter(String contents) {
+ // When renaming Java type names, the adapter always looks for the longest name to rewrite.
+ // For example, if there is a resource with the name "foo/bar/C$X$Y.txt", then the adapter will
+ // check if there is a renaming for the type "foo.bar.C$X$Y". If there is no such renaming, then
+ // -adaptresourcefilenames works in such a way that "foo/bar/C$X" should be rewritten if there
+ // is a renaming for the type "foo.bar.C$X". Therefore, when scanning forwards to read the
+ // substring "foo/bar/C$X$Y", this adapter records the positions of the two '$' characters in
+ // the stack `prefixEndPositionsExclusive`, such that it can easily backtrack to the previously
+ // valid, but shorter Java type name.
+ //
+ // Note that there is no backtracking for -adaptresourcefilecontents.
+ private final IntStack prefixEndPositionsExclusive;
+
+ public StringAdapter(String contents) {
this.contents = contents;
+ this.prefixEndPositionsExclusive = allowRenamingOfPrefixes() ? new IntArrayList() : null;
}
public boolean run() {
@@ -123,17 +172,29 @@
return;
}
+ assert !allowRenamingOfPrefixes() || prefixEndPositionsExclusive.isEmpty();
+
assert Character.isJavaIdentifierPart(contents.charAt(position));
int start = position++;
while (!eof()) {
char currentChar = contents.charAt(position);
if (Character.isJavaIdentifierPart(currentChar)) {
+ if (allowRenamingOfPrefixes()
+ && shouldRecordPrefix(currentChar)
+ && isRenamingCandidate(start, position)) {
+ prefixEndPositionsExclusive.push(position);
+ }
position++;
continue;
}
- if (currentChar == '.'
+ if (currentChar == getClassNameSeparator()
&& !eof(position + 1)
&& Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+ if (allowRenamingOfPrefixes()
+ && shouldRecordPrefix(currentChar)
+ && isRenamingCandidate(start, position)) {
+ prefixEndPositionsExclusive.push(position);
+ }
// Consume the dot and the Java identifier part that follows the dot.
position += 2;
continue;
@@ -143,13 +204,29 @@
break;
}
- if ((start > 0 && isDashOrDot(contents.charAt(start - 1)))
- || (!eof(position) && isDashOrDot(contents.charAt(position)))) {
- // The Java type starts with '-' or '.', and should not be renamed.
- return;
+ boolean renamingSucceeded =
+ isRenamingCandidate(start, position) && renameJavaTypeInRange(start, position);
+ if (!renamingSucceeded && allowRenamingOfPrefixes()) {
+ while (!prefixEndPositionsExclusive.isEmpty() && !renamingSucceeded) {
+ int prefixEndExclusive = prefixEndPositionsExclusive.popInt();
+ assert isRenamingCandidate(start, prefixEndExclusive);
+ renamingSucceeded = handlePrefix(start, prefixEndExclusive);
+ }
}
- String javaType = contents.substring(start, position);
+ if (allowRenamingOfPrefixes()) {
+ while (!prefixEndPositionsExclusive.isEmpty()) {
+ prefixEndPositionsExclusive.popInt();
+ }
+ }
+ }
+
+ // Returns true if the Java type in the range [from; toExclusive[ was renamed.
+ protected boolean renameJavaTypeInRange(int from, int toExclusive) {
+ String javaType = contents.substring(from, toExclusive);
+ if (getClassNameSeparator() != '.') {
+ javaType = javaType.replace(getClassNameSeparator(), '.');
+ }
DexString descriptor =
dexItemFactory.lookupString(
DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
@@ -157,36 +234,143 @@
if (dexType != null) {
DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
if (!descriptor.equals(renamedDescriptor)) {
- // Need to flush all changes up to and excluding 'start', and then output the renamed
+ String renamedJavaType =
+ DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString());
+ // Need to flush all changes up to and excluding 'from', and then output the renamed
// type.
- outputRangeFromInput(outputFrom, start);
- outputString(DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString()));
- outputFrom = position;
+ outputRangeFromInput(outputFrom, from);
+ outputJavaType(
+ getClassNameSeparator() != '.'
+ ? renamedJavaType.replace('.', getClassNameSeparator())
+ : renamedJavaType);
+ outputFrom = toExclusive;
changed = true;
+ return true;
}
}
+ return false;
}
- private boolean isDashOrDot(char c) {
- return c == '.' || c == '-';
+ // Returns true if the Java package in the range [from; toExclusive[ was renamed.
+ protected boolean renameJavaPackageInRange(int from, int toExclusive) {
+ String javaPackage = contents.substring(from, toExclusive);
+ if (getClassNameSeparator() != '/') {
+ javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
+ }
+ String minifiedJavaPackage = namingLense.lookupPackageName(javaPackage);
+ if (!javaPackage.equals(minifiedJavaPackage)) {
+ outputRangeFromInput(outputFrom, from);
+ outputJavaType(
+ getClassNameSeparator() != '/'
+ ? minifiedJavaPackage.replace('/', getClassNameSeparator())
+ : minifiedJavaPackage);
+ outputFrom = toExclusive;
+ changed = true;
+ return true;
+ }
+ return false;
}
+ protected abstract char getClassNameSeparator();
+
+ protected abstract boolean allowRenamingOfPrefixes();
+
+ protected abstract boolean shouldRecordPrefix(char c);
+
+ protected abstract boolean handlePrefix(int from, int toExclusive);
+
+ protected abstract boolean isRenamingCandidate(int from, int toExclusive);
+
private void outputRangeFromInput(int from, int toExclusive) {
if (from < toExclusive) {
result.append(contents.substring(from, toExclusive));
}
}
- private void outputString(String s) {
+ private void outputJavaType(String s) {
result.append(s);
}
- private boolean eof() {
+ protected boolean eof() {
return eof(position);
}
- private boolean eof(int position) {
+ protected boolean eof(int position) {
return position == contents.length();
}
}
+
+ private class FileContentsAdapter extends StringAdapter {
+
+ public FileContentsAdapter(String fileContents) {
+ super(fileContents);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '.';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldRecordPrefix(char c) {
+ throw new Unreachable();
+ }
+
+ @Override
+ protected boolean handlePrefix(int from, int toExclusive) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ // If the Java type starts with '-' or '.', it should not be renamed.
+ return (from <= 0 || !isDashOrDot(contents.charAt(from - 1)))
+ && (eof(toExclusive) || !isDashOrDot(contents.charAt(toExclusive)));
+ }
+
+ private boolean isDashOrDot(char c) {
+ return c == '.' || c == '-';
+ }
+ }
+
+ private class FilenameAdapter extends StringAdapter {
+
+ public FilenameAdapter(String filename) {
+ super(filename);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '/';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldRecordPrefix(char c) {
+ return !Character.isLetterOrDigit(c);
+ }
+
+ @Override
+ protected boolean handlePrefix(int from, int toExclusive) {
+ assert !eof(toExclusive);
+ if (contents.charAt(toExclusive) == '/') {
+ return renameJavaPackageInRange(from, toExclusive);
+ }
+ return renameJavaTypeInRange(from, toExclusive);
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ return from == 0 && !eof(toExclusive);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 01fff01..bf5dd5d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.lang.reflect.GenericSignatureFormatError;
@@ -38,6 +39,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -92,7 +94,18 @@
states.computeIfAbsent("", k -> topLevelState);
}
- Map<DexType, DexString> computeRenaming(Timing timing) {
+ static class ClassRenaming {
+ protected final Map<String, String> packageRenaming;
+ protected final Map<DexType, DexString> classRenaming;
+
+ private ClassRenaming(
+ Map<DexType, DexString> classRenaming, Map<String, String> packageRenaming) {
+ this.classRenaming = classRenaming;
+ this.packageRenaming = packageRenaming;
+ }
+ }
+
+ ClassRenaming computeRenaming(Timing timing) {
// Use deterministic class order to make sure renaming is deterministic.
Iterable<DexProgramClass> classes = appInfo.classesWithDeterministicOrder();
// Collect names we have to keep.
@@ -128,7 +141,19 @@
appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
timing.end();
- return Collections.unmodifiableMap(renaming);
+ return new ClassRenaming(Collections.unmodifiableMap(renaming), getPackageRenaming());
+ }
+
+ private Map<String, String> getPackageRenaming() {
+ ImmutableMap.Builder<String, String> packageRenaming = ImmutableMap.builder();
+ for (Entry<String, Namespace> entry : states.entrySet()) {
+ String originalPackageName = entry.getKey();
+ String minifiedPackageName = entry.getValue().getPackageName();
+ if (!minifiedPackageName.equals(originalPackageName)) {
+ packageRenaming.put(originalPackageName, minifiedPackageName);
+ }
+ }
+ return packageRenaming.build();
}
private void renameDanglingTypes(DexClass clazz) {
@@ -406,6 +431,7 @@
private class Namespace {
+ private final String packageName;
private final char[] packagePrefix;
private int typeCounter = 1;
private int packageCounter = 1;
@@ -417,6 +443,7 @@
}
Namespace(String packageName, String separator) {
+ this.packageName = packageName;
this.packagePrefix = ("L" + packageName
// L or La/b/ (or La/b/C$)
+ (packageName.isEmpty() ? "" : separator))
@@ -425,6 +452,10 @@
this.classDictionaryIterator = classDictionary.iterator();
}
+ public String getPackageName() {
+ return packageName;
+ }
+
private String nextSuggestedNameForClass() {
StringBuilder nextName = new StringBuilder();
if (classDictionaryIterator.hasNext()) {
@@ -466,7 +497,6 @@
usedPackagePrefixes.add(candidate);
return candidate;
}
-
}
private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 382ac92..10aa8af 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -42,8 +43,8 @@
public NamingLens run(Timing timing) {
assert options.enableMinification;
timing.begin("MinifyClasses");
- Map<DexType, DexString> classRenaming =
- new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
+ ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
+ ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
timing.end();
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
@@ -53,7 +54,12 @@
Map<DexField, DexString> fieldRenaming =
new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
- NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ NamingLens lens =
+ new MinifiedRenaming(
+ classRenaming,
+ methodRenaming,
+ fieldRenaming,
+ appInfo);
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
.run();
@@ -64,21 +70,28 @@
private static class MinifiedRenaming extends NamingLens {
private final AppInfo appInfo;
+ private final Map<String, String> packageRenaming;
private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
private MinifiedRenaming(
- Map<DexType, DexString> classRenaming,
+ ClassRenaming classRenaming,
MethodRenaming methodRenaming,
Map<DexField, DexString> fieldRenaming,
AppInfo appInfo) {
this.appInfo = appInfo;
- renaming.putAll(classRenaming);
+ this.packageRenaming = classRenaming.packageRenaming;
+ renaming.putAll(classRenaming.classRenaming);
renaming.putAll(methodRenaming.renaming);
renaming.putAll(methodRenaming.callSiteRenaming);
renaming.putAll(fieldRenaming);
}
@Override
+ public String lookupPackageName(String packageName) {
+ return packageRenaming.getOrDefault(packageName, packageName);
+ }
+
+ @Override
public DexString lookupDescriptor(DexType type) {
return renaming.getOrDefault(type, type.descriptor);
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1f13b3..47459c0 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -30,6 +30,8 @@
*/
public abstract class NamingLens {
+ public abstract String lookupPackageName(String packageName);
+
public abstract DexString lookupDescriptor(DexType type);
public abstract String lookupSimpleName(DexType inner, DexString innerName);
@@ -99,6 +101,11 @@
}
@Override
+ public String lookupPackageName(String packageName) {
+ return packageName;
+ }
+
+ @Override
void forAllRenamedTypes(Consumer<DexType> consumer) {
// Intentionally left empty.
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 05539fe..599d444 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -55,7 +55,8 @@
private Origin keepParameterNamesOptionOrigin;
private Position keepParameterNamesOptionPosition;
private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
- private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
+ private final ProguardPathFilter.Builder adaptResourceFilenames =
+ ProguardPathFilter.builder().disable();
private final ProguardPathFilter.Builder adaptResourceFileContents =
ProguardPathFilter.builder().disable();
private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
@@ -231,6 +232,10 @@
adaptClassStrings.addPattern(pattern);
}
+ public void enableAdaptResourceFilenames() {
+ adaptResourceFilenames.enable();
+ }
+
public void addAdaptResourceFilenames(ProguardPathList pattern) {
adaptResourceFilenames.addPattern(pattern);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 855ba98..d1a0b3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -364,10 +364,7 @@
} else if (acceptString("adaptclassstrings")) {
parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
} else if (acceptString("adaptresourcefilenames")) {
- // TODO(76377381): Report an error until it's fully supported.
- if (failOnPartiallyImplementedOptions) {
- failPartiallyImplementedOption("-adaptresourcefilenames", optionStart);
- }
+ configurationBuilder.enableAdaptResourceFilenames();
parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
} else if (acceptString("adaptresourcefilecontents")) {
configurationBuilder.enableAdaptResourceFileContents();
@@ -1265,6 +1262,7 @@
private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
StringBuilder currentAsterisks = null;
+ int asteriskCount = 0;
StringBuilder currentBackreference = null;
skipWhitespace();
int start = position;
@@ -1306,19 +1304,35 @@
}
} else if (currentAsterisks != null) {
if (current == '*') {
+ // only '*', '**', and '***' are allowed.
+ // E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
+ if (asteriskCount >= 3) {
+ wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+ currentAsterisks = new StringBuilder();
+ asteriskCount = 0;
+ }
currentAsterisks.append((char) current);
+ asteriskCount++;
end += Character.charCount(current);
continue;
} else {
wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
currentAsterisks = null;
+ asteriskCount = 0;
}
}
// From now on, neither in asterisk collecting state nor back reference collecting state.
assert currentAsterisks == null && currentBackreference == null;
if (current == '*') {
- currentAsterisks = new StringBuilder();
- currentAsterisks.append((char) current);
+ if (kind == IdentifierType.CLASS_NAME) {
+ // '**' and '***' are only allowed in type name.
+ currentAsterisks = new StringBuilder();
+ currentAsterisks.append((char) current);
+ asteriskCount = 1;
+ } else {
+ // For member names, regard '**' or '***' as separate single-asterisk wildcards.
+ wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+ }
end += Character.charCount(current);
} else if (current == '?' || current == '%') {
wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
diff --git a/src/test/examples/adaptresourcefilenames/A.java b/src/test/examples/adaptresourcefilenames/A.java
new file mode 100644
index 0000000..c83e8c5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/A.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames;
+
+public class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/B.java b/src/test/examples/adaptresourcefilenames/B.java
new file mode 100644
index 0000000..79725cb
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/B.java
@@ -0,0 +1,23 @@
+// 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 adaptresourcefilenames;
+
+public class B extends A {
+
+ private Inner inner = new Inner();
+
+ public static class Inner {
+
+ public void method() {
+ System.out.println("In Inner.method()");
+ }
+ }
+
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ inner.method();
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/TestClass.java b/src/test/examples/adaptresourcefilenames/TestClass.java
new file mode 100644
index 0000000..a5e96e5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/TestClass.java
@@ -0,0 +1,17 @@
+// 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 adaptresourcefilenames;
+
+import adaptresourcefilenames.pkg.C;
+import adaptresourcefilenames.pkg.innerpkg.D;
+
+public class TestClass {
+
+ public static void main(String[] args) {
+ new B().method();
+ new C().method();
+ new D().method();
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/C.java b/src/test/examples/adaptresourcefilenames/pkg/C.java
new file mode 100644
index 0000000..93d4446
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/C.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames.pkg;
+
+public class C {
+
+ public void method() {
+ System.out.println("In C.method()");
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
new file mode 100644
index 0000000..2107547
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames.pkg.innerpkg;
+
+public class D {
+
+ public void method() {
+ System.out.println("In D.method()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 738f29c..d6abe44 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -230,6 +230,26 @@
}
}
+ /**
+ * Creates a new, temporary JAR that contains all the entries from the given JAR as well as the
+ * specified data resources. The given JAR is left unchanged.
+ */
+ protected Path addDataResourcesToExistingJar(
+ Path existingJar, List<DataEntryResource> dataResources) throws IOException {
+ Path newJar = File.createTempFile("app", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ try (JarOutputStream out = new JarOutputStream(new FileOutputStream(newJar.toFile()))) {
+ ArchiveProgramResourceProvider.fromArchive(existingJar)
+ .readArchive(
+ (entry, stream) -> {
+ out.putNextEntry(new ZipEntry(entry.getEntryName()));
+ ByteStreams.copy(stream, out);
+ out.closeEntry();
+ });
+ addDataResourcesToJar(out, dataResources);
+ }
+ return newJar;
+ }
+
/** Returns a list containing all the data resources in the given app. */
public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index afd407e..ecaae31 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -80,6 +80,7 @@
public static final String EXAMPLES_KOTLIN_DIR = TESTS_DIR + "examplesKotlin/";
public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+ public static final String EXAMPLES_CF_DIR = EXAMPLES_BUILD_DIR + "classes/";
public static final String EXAMPLES_KOTLIN_BUILD_DIR = TESTS_BUILD_DIR + "examplesKotlin/";
public static final String EXAMPLES_KOTLIN_RESOURCE_DIR =
TESTS_BUILD_DIR + "kotlinR8TestResources/";
@@ -753,8 +754,11 @@
}
public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
- Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
- return Files.walk(dir)
+ return getClassFilesForTestDirectory(ToolHelper.getPackageDirectoryForTestPackage(pkg));
+ }
+
+ public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+ return Files.walk(directory)
.filter(path -> path.toString().endsWith(".class"))
.collect(Collectors.toList());
}
@@ -823,6 +827,13 @@
.setProguardMapConsumer(StringConsumer.emptyConsumer());
}
+ public static R8Command.Builder prepareR8CommandBuilder(
+ AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ return R8Command.builder(app, diagnosticsHandler)
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer());
+ }
+
public static AndroidApp runR8(AndroidApp app) throws IOException {
return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 74148c7..e3f6eac 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -42,7 +43,7 @@
public class AdaptResourceFileContentsTest extends ProguardCompatabilityTestBase {
- private static class CustomDataResourceConsumer implements DataResourceConsumer {
+ protected static class CustomDataResourceConsumer implements DataResourceConsumer {
private final Map<String, ImmutableList<String>> resources = new HashMap<>();
@@ -53,6 +54,7 @@
@Override
public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ assertFalse(resources.containsKey(file.getName()));
try {
byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
String contents = new String(bytes, Charset.defaultCharset());
@@ -68,6 +70,10 @@
public ImmutableList<String> get(String name) {
return resources.get(name);
}
+
+ public int size() {
+ return resources.size();
+ }
}
private static final ImmutableList<String> originalAllChangedResource =
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
new file mode 100644
index 0000000..dbe6541
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -0,0 +1,505 @@
+// 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+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.DiagnosticsHandler;
+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.CustomDataResourceConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+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.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;
+
+public class AdaptResourceFileNamesTest extends ProguardCompatabilityTestBase {
+
+ 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.class.getName() + " {",
+ " public static void main(...);",
+ "}");
+ }
+
+ private static String getProguardConfigWithNeverInline(
+ boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+ return String.join(
+ System.lineSeparator(),
+ getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
+ "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
+ " public void method();",
+ "}",
+ "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
+ " public void method();",
+ "}");
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, null), dataResourceConsumer, 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 {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, "**.md"),
+ dataResourceConsumer,
+ 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 {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ 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 {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, null),
+ dataResourceConsumer,
+ 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), diagnosticsHandler)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+ .build();
+ return ToolHelper.runR8(
+ command,
+ options -> {
+ // TODO(christofferqa): Class inliner should respect -neverinline.
+ options.enableClassInlining = false;
+ options.enableClassMerging = true;
+ options.dataResourceConsumer = dataResourceConsumer;
+ options.proguardMapConsumer = proguardMapConsumer;
+ });
+ }
+
+ private void checkR8Renamings(String proguardMap, DiagnosticsHandler handler) {
+ 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;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
new file mode 100644
index 0000000..05d0b6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -0,0 +1,176 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class B111974287 {
+ B111974287 self;
+ B111974287[] clones;
+
+ B111974287() {
+ self = this;
+ clones = new B111974287[1];
+ clones[0] = self;
+ }
+
+ B111974287 fooX() {
+ System.out.println("fooX");
+ return self;
+ }
+
+ B111974287 fooYY() {
+ System.out.println("fooYY");
+ return self;
+ }
+
+ B111974287 fooZZZ() {
+ System.out.println("fooZZZ");
+ return self;
+ }
+}
+
+@RunWith(Parameterized.class)
+public class AsterisksTest extends ProguardCompatabilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(B111974287.class);
+ private final Shrinker shrinker;
+
+ public AsterisksTest(Shrinker shrinker) {
+ this.shrinker = shrinker;
+ }
+
+ @Parameters(name = "shrinker: {0}")
+ public static Collection<Object> data() {
+ return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+ }
+
+ @Test
+ public void doubleAsterisksInField() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " ** **;",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+ // TODO(b/111974287): Proguard6 kept and renamed the field with array type.
+ if (shrinker == Shrinker.PROGUARD6) {
+ return;
+ }
+ assertThat(fieldSubject, not(isPresent()));
+ }
+
+ @Test
+ public void doubleAsterisksInMethod() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " ** foo**(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+ @Test
+ public void tripleAsterisksInField() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " *** ***;",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ }
+
+ @Test
+ public void tripleAsterisksInMethod() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " *** foo***(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+ @Test
+ public void quadrupleAsterisksInType() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **** {",
+ " **** foo***(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 758e4f0..f79e01f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1810,7 +1810,6 @@
@Test
public void parse_partiallyImplemented_notSupported() {
testNotSupported("-keepdirectories");
- testNotSupported("-adaptresourcefilenames");
}
private void checkRulesSourceSnippet(List<String> sourceRules) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 770bdbd..c907276 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -158,11 +158,31 @@
Path proguardMap,
List<DataEntryResource> dataResources)
throws Exception {
+ return runProguard6Raw(
+ destination,
+ jarTestClasses(programClasses, dataResources),
+ proguardConfig,
+ proguardMap,
+ null);
+ }
+
+ protected ProcessResult runProguard6Raw(
+ Path destination, Path jar, String proguardConfig, Path proguardMap) throws Exception {
+ return runProguard6Raw(destination, jar, proguardConfig, proguardMap, null);
+ }
+
+ protected ProcessResult runProguard6Raw(
+ Path destination,
+ Path jar,
+ String proguardConfig,
+ Path proguardMap,
+ Consumer<ProcessResult> processResultConsumer)
+ throws Exception {
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
ProcessResult result =
ToolHelper.runProguard6Raw(
- jarTestClasses(programClasses, dataResources),
+ jar,
destination,
ToolHelper.getAndroidJar(AndroidApiLevel.N),
proguardConfigFile,
@@ -170,6 +190,9 @@
if (result.exitCode != 0) {
fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
}
+ if (processResultConsumer != null) {
+ processResultConsumer.accept(result);
+ }
return result;
}