Merge "DexEncodedMethod.Builder: Also copy classFileVersion"
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 9e5d36a..93c0126 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -3,16 +3,79 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
public class D8CommandParser extends BaseCompilerCommandParser {
+ static class OrderedClassFileResourceProvider implements ClassFileResourceProvider {
+ static class Builder {
+ private final ImmutableList.Builder<ClassFileResourceProvider> builder =
+ ImmutableList.builder();
+ boolean empty = true;
+
+ OrderedClassFileResourceProvider build() {
+ return new OrderedClassFileResourceProvider(builder.build());
+ }
+
+ Builder addClassFileResourceProvider(ClassFileResourceProvider provider) {
+ builder.add(provider);
+ empty = false;
+ return this;
+ }
+
+ boolean isEmpty() {
+ return empty;
+ }
+ }
+
+ final List<ClassFileResourceProvider> providers;
+ final Set<String> descriptors = Sets.newHashSet();
+
+ private OrderedClassFileResourceProvider(ImmutableList<ClassFileResourceProvider> providers) {
+ this.providers = providers;
+ // Collect all descriptors that can be provided.
+ this.providers.forEach(provider -> this.descriptors.addAll(provider.getClassDescriptors()));
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return descriptors;
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ // Search the providers in order. Return the program resource from the first provider that
+ // can provide it.
+ for (ClassFileResourceProvider provider : providers) {
+ if (provider.getClassDescriptors().contains(descriptor)) {
+ return provider.getProgramResource(descriptor);
+ }
+ }
+ return null;
+ }
+ }
+
public static void main(String[] args) throws CompilationFailedException {
D8Command command = parse(args, Origin.root()).build();
if (command.isPrintHelp()) {
@@ -76,6 +139,8 @@
Path outputPath = null;
OutputMode outputMode = null;
boolean hasDefinedApiLevel = false;
+ OrderedClassFileResourceProvider.Builder classpathBuilder =
+ OrderedClassFileResourceProvider.builder();
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
try {
for (int i = 0; i < expandedArgs.length; i++) {
@@ -115,7 +180,22 @@
} else if (arg.equals("--lib")) {
builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
} else if (arg.equals("--classpath")) {
- builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
+ Path file = Paths.get(expandedArgs[++i]);
+ try {
+ if (!Files.exists(file)) {
+ throw new NoSuchFileException(file.toString());
+ }
+ if (isArchive(file)) {
+ classpathBuilder.addClassFileResourceProvider(new ArchiveClassFileProvider(file));
+ } else if (Files.isDirectory(file)) {
+ classpathBuilder.addClassFileResourceProvider(
+ DirectoryClassFileProvider.fromDirectory(file));
+ } else {
+ throw new CompilationError("Unsupported classpath file type", new PathOrigin(file));
+ }
+ } catch (IOException e) {
+ builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
+ }
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
} else if (arg.equals("--optimize-multidex-for-linearalloc")) {
@@ -140,6 +220,9 @@
builder.addProgramFiles(Paths.get(arg));
}
}
+ if (!classpathBuilder.isEmpty()) {
+ builder.addClasspathResourceProvider(classpathBuilder.build());
+ }
if (compilationMode != null) {
builder.setMode(compilationMode);
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e42d8b0..2e0b531 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.ClassAndMemberPublicizer;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -28,6 +27,7 @@
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.naming.SourceFileRewriter;
+import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
import com.android.tools.r8.origin.CommandLineOrigin;
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 56e381b..e3ec746 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -8,9 +8,11 @@
import static com.android.tools.r8.utils.DescriptorUtils.getPackageBinaryNameFromJavaType;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
@@ -18,16 +20,20 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.signature.GenericSignatureAction;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
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.Maps;
import com.google.common.collect.Sets;
+import java.lang.reflect.GenericSignatureFormatError;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -40,6 +46,7 @@
private final AppInfoWithLiveness appInfo;
private final RootSet rootSet;
+ private final Reporter reporter;
private final PackageObfuscationMode packageObfuscationMode;
private final boolean isAccessModificationAllowed;
private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
@@ -66,6 +73,7 @@
InternalOptions options) {
this.appInfo = appInfo;
this.rootSet = rootSet;
+ this.reporter = options.reporter;
this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
@@ -152,27 +160,75 @@
}
}
+ private void parseError(DexItem item, Origin origin, GenericSignatureFormatError e) {
+ StringBuilder message = new StringBuilder("Invalid class signature for ");
+ if (item instanceof DexClass) {
+ message.append("class ");
+ message.append(((DexClass) item).getType().toSourceString());
+ } else if (item instanceof DexEncodedField) {
+ message.append("field ");
+ message.append(item.toSourceString());
+ } else {
+ assert item instanceof DexEncodedMethod;
+ message.append("method ");
+ message.append(item.toSourceString());
+ }
+ message.append(".\n");
+ message.append(e.getMessage());
+ reporter.warning(new StringDiagnostic(message.toString(), origin));
+ }
+
private void renameTypesInGenericSignatures() {
for (DexClass clazz : appInfo.classes()) {
- rewriteGenericSignatures(clazz.annotations.annotations,
- genericSignatureParser::parseClassSignature);
+ clazz.annotations = rewriteGenericSignatures(clazz.annotations,
+ genericSignatureParser::parseClassSignature,
+ e -> parseError(clazz, clazz.getOrigin(), e));
clazz.forEachField(field -> rewriteGenericSignatures(
- field.annotations.annotations, genericSignatureParser::parseFieldSignature));
+ field.annotations, genericSignatureParser::parseFieldSignature,
+ e -> parseError(field, clazz.getOrigin(), e)));
clazz.forEachMethod(method -> rewriteGenericSignatures(
- method.annotations.annotations, genericSignatureParser::parseMethodSignature));
+ method.annotations, genericSignatureParser::parseMethodSignature,
+ e -> parseError(method, clazz.getOrigin(), e)));
}
}
- private void rewriteGenericSignatures(DexAnnotation[] annotations, Consumer<String> parser) {
- for (int i = 0; i < annotations.length; i++) {
- DexAnnotation annotation = annotations[i];
+ private DexAnnotationSet rewriteGenericSignatures(
+ DexAnnotationSet annotations,
+ Consumer<String> parser,
+ Consumer<GenericSignatureFormatError> parseError) {
+ // There can be no more than one signature annotation in an annotation set.
+ final int VALID = -1;
+ int invalid = VALID;
+ for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
+ DexAnnotation annotation = annotations.annotations[i];
if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
- parser.accept(DexAnnotation.getSignature(annotation));
- annotations[i] = DexAnnotation.createSignatureAnnotation(
- genericSignatureRewriter.getRenamedSignature(),
- appInfo.dexItemFactory);
+ try {
+ parser.accept(DexAnnotation.getSignature(annotation));
+ annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
+ genericSignatureRewriter.getRenamedSignature(),
+ appInfo.dexItemFactory);
+ } catch (GenericSignatureFormatError e) {
+ parseError.accept(e);
+ invalid = i;
+ }
}
}
+
+ // Return the rewritten signatures if it was valid and could be rewritten.
+ if (invalid == VALID) {
+ return annotations;
+ }
+ // Remove invalid signature if found.
+ DexAnnotation[] prunedAnnotations =
+ new DexAnnotation[annotations.annotations.length - 1];
+ int dest = 0;
+ for (int i = 0; i < annotations.annotations.length; i++) {
+ if (i != invalid) {
+ prunedAnnotations[dest++] = annotations.annotations[i];
+ }
+ }
+ assert dest == prunedAnnotations.length;
+ return new DexAnnotationSet(prunedAnnotations);
}
/**
@@ -453,7 +509,6 @@
String renamed =
getClassBinaryNameFromDescriptor(
renaming.getOrDefault(type, type.descriptor).toString());
- assert renamed.startsWith(enclosingRenamedBinaryName + Minifier.INNER_CLASS_SEPARATOR);
String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
renamedSignature.append(outName);
return type;
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
similarity index 87%
rename from src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
rename to src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 7a13348..297dd57 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -1,7 +1,13 @@
// Copyright (c) 2016, 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.graph;
+package com.android.tools.r8.optimize;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.MethodAccessFlags;
public final class ClassAndMemberPublicizer {
private final DexItemFactory factory;
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 1b846a5..0c278f7 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -6,13 +6,13 @@
import static com.android.tools.r8.R8CommandTest.getOutputPath;
import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import com.android.sdklib.AndroidVersion;
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
@@ -24,7 +24,6 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
-import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -263,13 +262,37 @@
tmpClassesDir.toString());
AndroidApp inputApp = ToolHelper.getApp(command);
assertEquals(1, inputApp.getClasspathResourceProviders().size());
+ OrderedClassFileResourceProvider classpathProvider =
+ (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+ assertEquals(1, classpathProvider.providers.size());
assertTrue(Files.isSameFile(tmpClassesDir,
- ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot()));
+ ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
assertEquals(1, inputApp.getLibraryResourceProviders().size());
assertTrue(Files.isSameFile(tmpClassesDir,
((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot()));
}
+ @Test
+ public void folderClasspathMultiple() throws Throwable {
+ Path inputFile =
+ Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION);
+ Path tmpClassesDir1 = temp.newFolder().toPath();
+ Path tmpClassesDir2 = temp.newFolder().toPath();
+ ZipUtils.unzip(inputFile.toString(), tmpClassesDir1.toFile());
+ ZipUtils.unzip(inputFile.toString(), tmpClassesDir2.toFile());
+ D8Command command = parse("--classpath", tmpClassesDir1.toString(), "--classpath",
+ tmpClassesDir2.toString());
+ AndroidApp inputApp = ToolHelper.getApp(command);
+ assertEquals(1, inputApp.getClasspathResourceProviders().size());
+ OrderedClassFileResourceProvider classpathProvider =
+ (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+ assertEquals(2, classpathProvider.providers.size());
+ assertTrue(Files.isSameFile(tmpClassesDir1,
+ ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
+ assertTrue(Files.isSameFile(tmpClassesDir2,
+ ((DirectoryClassFileProvider) classpathProvider.providers.get(1)).getRoot()));
+ }
+
@Test(expected = CompilationFailedException.class)
public void classFolderProgram() throws Throwable {
Path inputFile =
diff --git a/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
new file mode 100644
index 0000000..dc497c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
@@ -0,0 +1,113 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+
+public class OrderedClassFileResourceProviderTest extends TestBase {
+ class SimpleClassFileResourceProvider implements ClassFileResourceProvider {
+
+ private final Set<String> descriptors;
+ private final ProgramResource fixedProgramResource;
+
+ SimpleClassFileResourceProvider(int id, Set<String> descriptors) {
+ this.descriptors = descriptors;
+ this.fixedProgramResource = new SimpleProgramResource(id);
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return descriptors;
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ return fixedProgramResource;
+ }
+ }
+
+ class SimpleProgramResource implements ProgramResource {
+
+ private final Origin origin;
+
+ SimpleProgramResource(int id) {
+ origin = new SimpleOrigin(id);
+ }
+
+ @Override
+ public Kind getKind() {
+ return null;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return null;
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return null;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ }
+
+ public class SimpleOrigin extends Origin {
+
+ private final int id;
+
+ private SimpleOrigin(int index) {
+ super(root());
+ this.id = index;
+ }
+
+ int getId() {
+ return id;
+ }
+
+ @Override
+ public String part() {
+ return "Test";
+ }
+ }
+
+ @Test
+ public void test() {
+ OrderedClassFileResourceProvider.Builder builder = OrderedClassFileResourceProvider.builder();
+ builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(1, ImmutableSet.of(
+ "L/a/a/a", "L/a/a/b", "L/a/a/c"
+ )));
+ builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(2, ImmutableSet.of(
+ "L/a/a/b", "L/a/a/c", "L/a/a/d"
+ )));
+ ClassFileResourceProvider provider = builder.build();
+ assertEquals(
+ ImmutableSet.of("L/a/a/a", "L/a/a/b", "L/a/a/c", "L/a/a/d"),
+ provider.getClassDescriptors());
+
+ Map<String, Integer> expectations = ImmutableMap.of(
+ "L/a/a/a", 1,
+ "L/a/a/b", 1,
+ "L/a/a/c", 1,
+ "L/a/a/d", 2
+ );
+ expectations.forEach((descriptor, id) ->
+ assertEquals(
+ (int) id,
+ ((SimpleOrigin) provider.getProgramResource(descriptor).getOrigin()).getId()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
new file mode 100644
index 0000000..68cd36f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -0,0 +1,496 @@
+// 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 com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+public class MinifierClassSignatureTest extends TestBase {
+ /*
+
+ class Simple {
+ }
+ class Base<T> {
+ }
+ class Outer<T> {
+ class Inner {
+ class InnerInner {
+ }
+ class ExtendsInnerInner extends InnerInner {
+ }
+ }
+ class ExtendsInner extends Inner {
+ }
+ }
+
+ */
+
+ String baseSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
+ String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
+ String extendsInnerSignature = "LOuter<TT;>.Inner;";
+ String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+
+ private byte[] dumpSimple(String classSignature) throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ String signature = classSignature;
+ cw.visit(V1_8, ACC_SUPER, "Simple", signature, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpBase(String classSignature) throws Exception {
+
+ final String javacClassSignature = baseSignature;
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Base", signature, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+
+ private byte[] dumpOuter(String classSignature) {
+
+ final String javacClassSignature = outerSignature;
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Outer", signature, "java/lang/Object", null);
+
+ cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
+
+ cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpInner(String classSignature) {
+
+ final String javacClassSignature = null;
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Outer$Inner", signature, "java/lang/Object", null);
+
+ cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+ cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
+
+ cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Outer$Inner", "this$0", "LOuter;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpExtendsInner(String classSignature) throws Exception {
+
+ final String javacClassSignature = extendsInnerSignature;
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Outer$ExtendsInner", signature, "Outer$Inner", null);
+
+ cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+ cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Outer$ExtendsInner", "this$0", "LOuter;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner", "<init>", "(LOuter;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpInnerInner(String classSignature) throws Exception {
+
+ final String javacClassSignature = null;
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Outer$Inner$InnerInner", signature, "java/lang/Object", null);
+
+ cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+ cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Outer$Inner$InnerInner", "this$1", "LOuter$Inner;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpExtendsInnerInner(String classSignature) throws Exception {
+
+ final String javacClassSignature = extendsInnerInnerSignature;
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ String signature = classSignature != null ? classSignature : javacClassSignature;
+ cw.visit(V1_8, ACC_SUPER, "Outer$Inner$ExtendsInnerInner", signature, "Outer$Inner$InnerInner",
+ null);
+
+ cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+ cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+ cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Outer$Inner$ExtendsInnerInner", "this$1", "LOuter$Inner;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner$InnerInner", "<init>", "(LOuter$Inner;)V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public void runTest(
+ ImmutableMap<String, String> signatures,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspect)
+ throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ DexInspector inspector = new DexInspector(
+ ToolHelper.runR8(R8Command.builder(checker)
+ .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+ .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+ .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+ .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInnerInner(
+ signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
+ .addProguardConfiguration(ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class **"
+ ), Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build()));
+ // All classes are kept, and renamed.
+ assertThat(inspector.clazz("Simple"), isRenamed());
+ assertThat(inspector.clazz("Base"), isRenamed());
+ assertThat(inspector.clazz("Outer"), isRenamed());
+ assertThat(inspector.clazz("Outer$Inner"), isRenamed());
+ assertThat(inspector.clazz("Outer$ExtendsInner"), isRenamed());
+ assertThat(inspector.clazz("Outer$Inner$InnerInner"), isRenamed());
+ assertThat(inspector.clazz("Outer$Inner$ExtendsInnerInner"), isRenamed());
+
+ // Test that classes with have their original signature if the default was provided.
+ if (!signatures.containsKey("Simple")) {
+ assertNull(inspector.clazz("Simple").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Base")) {
+ assertEquals(baseSignature, inspector.clazz("Base").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Outer")) {
+ assertEquals(outerSignature, inspector.clazz("Outer").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Outer$Inner")) {
+ assertNull(inspector.clazz("Outer$Inner").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Outer$ExtendsInner")) {
+ assertEquals(extendsInnerSignature,
+ inspector.clazz("Outer$ExtendsInner").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Outer$Inner$InnerInner")) {
+ assertNull(inspector.clazz("Outer$Inner$InnerInner").getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("Outer$Inner$ExtendsInnerInner")) {
+ assertEquals(extendsInnerInnerSignature,
+ inspector.clazz("Outer$Inner$ExtendsInnerInner").getOriginalSignatureAttribute());
+ }
+
+ diagnostics.accept(checker);
+ inspect.accept(inspector);
+ }
+
+ private void testSingleClass(String name, String signature,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspector)
+ throws Exception {
+ ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
+ runTest(signatures, diagnostics, inspector);
+ }
+
+ private void isOriginUnknown(Origin origin) {
+ assertSame(Origin.unknown(), origin);
+ }
+
+ private void noWarnings(DiagnosticsChecker checker) {
+ assertEquals(0, checker.warnings.size());
+ }
+
+ private void noInspection(DexInspector inspector) {
+ }
+
+ private void noSignatureAttribute(ClassSubject clazz) {
+ assertNull(clazz.getFinalSignatureAttribute());
+ assertNull(clazz.getOriginalSignatureAttribute());
+ }
+
+ @Test
+ public void originalJavacSignatures() throws Exception {
+ // Test using the signatures generated by javac.
+ runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+ }
+
+ @Test
+ public void classSignature_empty() throws Exception {
+ testSingleClass("Outer", "", this::noWarnings, inspector -> {
+ ClassSubject outer = inspector.clazz("Outer");
+ assertNull(outer.getFinalSignatureAttribute());
+ assertNull(outer.getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void classSignatureOuter_valid() throws Exception {
+ // class Outer<T extends Simple> extends Base<T>
+ String signature = "<T:LSimple;>LBase<TT;>;";
+ testSingleClass("Outer", signature, this::noWarnings, inspector -> {
+ ClassSubject outer = inspector.clazz("Outer");
+ ClassSubject simple = inspector.clazz("Simple");
+ ClassSubject base = inspector.clazz("Base");
+ String baseDescriptorWithoutSemicolon =
+ base.getFinalDescriptor().substring(0, base.getFinalDescriptor().length() - 1);
+ String minifiedSignature =
+ "<T:" + simple.getFinalDescriptor() + ">" + baseDescriptorWithoutSemicolon + "<TT;>;";
+ assertEquals(minifiedSignature, outer.getFinalSignatureAttribute());
+ assertEquals(signature, outer.getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void classSignatureExtendsInner_valid() throws Exception {
+ String signature = "LOuter<TT;>.Inner;";
+ testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+ ClassSubject extendsInner = inspector.clazz("Outer$ExtendsInner");
+ ClassSubject outer = inspector.clazz("Outer");
+ ClassSubject inner = inspector.clazz("Outer$Inner");
+ String outerDescriptorWithoutSemicolon =
+ outer.getFinalDescriptor().substring(0, outer.getFinalDescriptor().length() - 1);
+ String innerFinalDescriptor = inner.getFinalDescriptor();
+ String innerLastPart =
+ innerFinalDescriptor.substring(innerFinalDescriptor.indexOf("$") + 1);
+ String minifiedSignature = outerDescriptorWithoutSemicolon + "<TT;>." + innerLastPart;
+ assertEquals(minifiedSignature, extendsInner.getFinalSignatureAttribute());
+ assertEquals(signature, extendsInner.getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void classSignatureOuter_classNotFound() throws Exception {
+ String signature = "<T:LNotFound;>LNotFound;";
+ testSingleClass("Outer", signature, this::noWarnings, inspector -> {
+ assertThat(inspector.clazz("NotFound"), not(isPresent()));
+ ClassSubject outer = inspector.clazz("Outer");
+ assertEquals(signature, outer.getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void classSignatureOuter_invalid() throws Exception {
+ testSingleClass("Outer", "X", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid class signature for class Outer", "Expected L at position 1");
+ }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+ }
+
+ @Test
+ public void classSignatureOuter_invalidEnd() throws Exception {
+ testSingleClass("Outer", "<L", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid class signature for class Outer", "Unexpected end of signature at position 3");
+ }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+ }
+
+ @Test
+ public void classSignatureExtendsInner_invalid() throws Exception {
+ testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid class signature for class Outer$ExtendsInner", "Expected L at position 1");
+ }, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
+ }
+
+ @Test
+ public void classSignatureExtendsInnerInner_invalid() throws Exception {
+ testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid class signature for class Outer$Inner$ExtendsInnerInner",
+ "Expected L at position 1");
+ }, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
+ }
+
+ @Test
+ public void multipleWarnings() throws Exception {
+ runTest(ImmutableMap.of(
+ "Outer", "X",
+ "Outer$ExtendsInner", "X",
+ "Outer$Inner$ExtendsInnerInner", "X"), diagnostics -> {
+ assertEquals(3, diagnostics.warnings.size());
+ }, inspector -> {
+ noSignatureAttribute(inspector.clazz("Outer"));
+ noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
+ noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner"));
+ });
+ }
+ @Test
+ public void regress80029761() throws Exception {
+ String signature = "LOuter<TT;>.com/example/Inner;";
+ testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid class signature for class Outer$ExtendsInner", "Expected ; at position 16");
+ }, inspector -> {
+ noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index d9808c0..f311c15 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -5,9 +5,9 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.ClassAndMemberPublicizer;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardRuleParserException;