Ensure pretended members indeed do not exist while applying mappings.
Also, avoid apply mappings for non-overridden members.
Bug: 117237053
Change-Id: I1254ac3250e69befbdeffa36c0fb83a59a656b2e
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 5dcc7b8..ba7963b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -151,8 +151,8 @@
MemberNaming lookupByOriginalItem(DexField field) {
for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) {
FieldSignature signature = entry.getKey();
- if (signature.name.equals(field.name.toString())
- && signature.type.equals(field.type.getName())) {
+ if (signature.name.equals(field.name.toSourceString())
+ && signature.type.equals(field.type.toSourceString())) {
return entry.getValue();
}
}
@@ -162,11 +162,11 @@
protected MemberNaming lookupByOriginalItem(DexMethod method) {
for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) {
MethodSignature signature = entry.getKey();
- if (signature.name.equals(method.name.toString())
- && signature.type.equals(method.proto.returnType.toString())
+ if (signature.name.equals(method.name.toSourceString())
+ && signature.type.equals(method.proto.returnType.toSourceString())
&& Arrays.equals(signature.parameters,
Arrays.stream(method.proto.parameters.values)
- .map(DexType::toString).toArray(String[]::new))) {
+ .map(DexType::toSourceString).toArray(String[]::new))) {
return entry.getValue();
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 25df058..bbf8259 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -22,8 +22,8 @@
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -128,42 +128,80 @@
private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) {
DexClass clazz = appInfo.definitionFor(from);
- if (clazz == null) return;
+ if (clazz == null) {
+ return;
+ }
- final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
+ // We regard mappings as _complete_ if they cover literally everything, but that's too ideal.
+ // When visiting members with member mappings, obviously, there are two incomplete cases:
+ // no matched member or no matched mapping.
+ //
+ // 1. No matched member
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // @Override void foo(); // no mapping
+ // }
+ //
+ // For this case, we have chained class naming and move upward to search for super class's
+ // member mapping. One corner case we should be careful here is to resolve on the correct
+ // mapping, e.g.,
+ //
+ // class B extends A { // : Y
+ // private void foo(); // no mapping, should be not renamed to a
+ // }
+
+ final Set<MemberNaming.Signature> appliedMemberSignature = new HashSet<>();
clazz.forEachField(encodedField -> {
MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyFieldMapping(encodedField.field, memberNaming);
}
});
clazz.forEachMethod(encodedMethod -> {
- MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method);
+ MemberNaming memberNaming =
+ classNaming.lookupByOriginalItem(encodedMethod.method, encodedMethod.isPrivateMethod());
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyMethodMapping(encodedMethod.method, memberNaming);
}
});
+ // 2. No matched mapping
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // // no overriding, but has mapping: void foo() -> a
+ // }
+ //
// We need to handle a class that extends another class where some members are not overridden,
// resulting in absence of definitions. References to those members need to be redirected via
// the lense as well.
+ // The caveat is, since such members don't exist, we pretend to see their definitions.
+ // We should ensure that they indeed don't exist. Otherwise, legitimately different members,
+ // e.g., private methods with same names, could be mapped to a wrong renamed name.
classNaming.forAllFieldNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexField pretendedOriginalField =
- ((FieldSignature) memberNaming.getOriginalSignature())
- .toDexField(appInfo.dexItemFactory, from);
- applyFieldMapping(pretendedOriginalField, memberNaming);
+ FieldSignature signature = (FieldSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexField pretendedOriginalField = signature.toDexField(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalField) == null) {
+ applyFieldMapping(pretendedOriginalField, memberNaming);
+ }
}
});
classNaming.forAllMethodNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexMethod pretendedOriginalMethod =
- ((MethodSignature) memberNaming.getOriginalSignature())
- .toDexMethod(appInfo.dexItemFactory, from);
- applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ MethodSignature signature = (MethodSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexMethod pretendedOriginalMethod = signature.toDexMethod(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalMethod) == null) {
+ applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ }
}
});
}
@@ -229,6 +267,15 @@
}
}
+ protected MemberNaming lookupByOriginalItem(DexMethod method, boolean isPrivate) {
+ // If the current method is overridable, use chained mappings.
+ if (!isPrivate) {
+ return lookupByOriginalItem(method);
+ }
+ // Otherwise, just look up the current class's mappings only.
+ return super.lookupByOriginalItem(method);
+ }
+
@Override
protected MemberNaming lookupByOriginalItem(DexMethod method) {
MemberNaming memberNaming = super.lookupByOriginalItem(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
index ce659e1..684c79b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
@@ -19,20 +19,20 @@
static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + type + createMessageForConflict(keptName, rename));
+ type + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename));
+ method.toSourceString() + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename));
+ field.toSourceString() + createMessageForConflict(keptName, rename));
}
private static String createMessageForConflict(String keptName, String rename) {
- return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'";
+ return " is not being kept as " + keptName + ", but remapped to " + rename;
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
new file mode 100644
index 0000000..86b0541
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
@@ -0,0 +1,216 @@
+// 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.applymapping;
+
+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.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberResolutionAsmTest extends AsmTestBase {
+ private final Backend backend;
+
+ @Parameterized.Parameters(name = "backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionAsmTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ // class HasMapping { // : X
+ // HasMapping() {
+ // foo();
+ // }
+ //
+ // void foo() { // : a
+ // System.out.println("HasMapping#foo");
+ // }
+ // }
+ //
+ // class NoMapping extends HasMapping { // : Y
+ // NoMapping() {
+ // super();
+ // foo();
+ // }
+ //
+ // private void foo() { // no mapping
+ // System.out.println("NoMapping#foo");
+ // }
+ // }
+ //
+ // class NoMappingMain {
+ // public static void main(String[] args) {
+ // new NoMapping();
+ // }
+ // }
+ @Test
+ public void test_noMapping() throws Exception {
+ String main = "NoMappingMain";
+ AndroidApp input = buildAndroidApp(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "HasMapping -> X:",
+ " void foo() -> a",
+ "NoMapping -> Y:"
+ // Intentionally missing a mapping for `private` foo().
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertEquals(0, outputBefore.exitCode);
+ String outputAfter = runOnVM(processedApp, main, backend);
+ assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("HasMapping");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "foo", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("a", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("NoMapping");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject y = sub.method("void", "foo", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, not(isRenamed()));
+ assertEquals("foo", y.getFinalName());
+ }
+
+ // class A { // : X
+ // A() {
+ // x();
+ // y();
+ // }
+ //
+ // private void x() { // : y
+ // System.out.println("A#x");
+ // }
+ //
+ // public void y() { // : x
+ // System.out.println("A#y");
+ // }
+ // }
+ //
+ // class B extends A { // : Y
+ // }
+ //
+ // class Main {
+ // public static void main(String[] args) {
+ // new B().x(); // IllegalAccessError
+ // }
+ // }
+ @Test
+ public void test_swapping() throws Exception {
+ String main = "Main";
+ AndroidApp input = buildAndroidApp(
+ ADump.dump(), BDump.dump(), MainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "A -> X:",
+ " void x() -> y",
+ " void y() -> x",
+ "B -> Y:"
+ // Intentionally missing mappings for non-overridden members
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertNotEquals(0, outputBefore.exitCode);
+ assertThat(outputBefore.stderr, containsString("IllegalAccessError"));
+ assertThat(outputBefore.stderr, containsString("A.x()"));
+ ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
+ assertNotEquals(0, outputAfter.exitCode);
+ assertThat(outputAfter.stderr, containsString("IllegalAccessError"));
+ assertThat(outputAfter.stderr, containsString("X.y()"));
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("A");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "x", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("y", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("B");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject subX = sub.method("void", "x", ImmutableList.of());
+ assertThat(subX, not(isPresent()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
new file mode 100644
index 0000000..61e51be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
@@ -0,0 +1,155 @@
+// 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.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+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.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// AbstractChecker -> X:
+abstract class AbstractChecker {
+ // String tag -> p
+ private String tag = "PrivateInitialTag_AbstractChecker";
+
+ // check() -> x
+ private void check() {
+ System.out.println("AbstractChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ protected void foo() {
+ check();
+ }
+}
+
+// ConcreteChecker -> Y:
+class ConcreteChecker extends AbstractChecker {
+ // This should not be conflict with AbstractChecker#tag due to the access control.
+ // String tag -> q
+ private String tag = "PrivateInitialTag_ConcreteChecker";
+
+ ConcreteChecker(String tag){
+ this.tag = tag;
+ }
+
+ // This should not be conflict with AbstractChecker#check due to the access control.
+ // check() -> y
+ private void check() {
+ System.out.println("ConcreteChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ @Override
+ protected void foo() {
+ super.foo();
+ check();
+ }
+}
+
+class MemberResolutionTestMain {
+ public static void main(String[] args) {
+ ConcreteChecker c = new ConcreteChecker("NewTag");
+ c.foo();
+ }
+}
+
+@RunWith(Parameterized.class)
+public class MemberResolutionTest extends TestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class);
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testPrivateMethodsWithSameName() throws Exception {
+ String pkg = this.getClass().getPackage().getName();
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ pkg + ".AbstractChecker -> " + pkg + ".X:",
+ " java.lang.String tag -> p",
+ " void check() -> x",
+ " void foo() -> a",
+ pkg + ".ConcreteChecker -> " + pkg + ".Y:",
+ " java.lang.String tag -> q",
+ " void check() -> y",
+ " void foo() -> a"
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ AndroidApp app = readClasses(CLASSES);
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(MemberResolutionTestMain.class),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ String outputBefore = runOnJava(MemberResolutionTestMain.class);
+ String outputAfter = runOnVM(processedApp, MemberResolutionTestMain.class, backend);
+ assertEquals(outputBefore, outputAfter);
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz(AbstractChecker.class);
+ assertThat(base, isPresent());
+ FieldSubject p = base.field("java.lang.String", "tag");
+ assertThat(p, isPresent());
+ assertThat(p, isRenamed());
+ assertEquals("p", p.getFinalName());
+ MethodSubject x = base.method("void", "check", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("x", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz(ConcreteChecker.class);
+ assertThat(sub, isPresent());
+ FieldSubject q = sub.field("java.lang.String", "tag");
+ assertThat(q, isPresent());
+ assertThat(q, isRenamed());
+ assertEquals("q", q.getFinalName());
+ MethodSubject y = sub.method("void", "check", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, isRenamed());
+ assertEquals("y", y.getFinalName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
new file mode 100644
index 0000000..ae46c2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
@@ -0,0 +1,197 @@
+// 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.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class HasMapping {
+// HasMapping() {
+// foo();
+// }
+//
+// void foo() {
+// System.out.println("HasMapping#foo");
+// }
+// }
+class HasMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "HasMapping", null, "java/lang/Object", null);
+
+ classWriter.visitSource("HasMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "HasMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("HasMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class NoMapping extends HasMapping {
+// NoMapping() {
+// super();
+// bar();
+// }
+//
+// private void bar() {
+// System.out.println("NoMapping#foo");
+// }
+// }
+//
+// then renamed bar() to foo() to introduce name clash.
+class NoMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "NoMapping", null, "HasMapping", null);
+
+ classWriter.visitSource("NoMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(12, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "HasMapping", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(13, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(14, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("NoMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class NoMappingMain {
+// public static void main(String[] args) {
+// new NoMapping();
+// }
+// }
+class NoMappingMainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8, ACC_PUBLIC | ACC_SUPER, "NoMappingMain", null, "java/lang/Object", null);
+
+ classWriter.visitSource("NoMappingMain.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(19, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(22, label0);
+ methodVisitor.visitTypeInsn(NEW, "NoMapping");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "<init>", "()V", false);
+ methodVisitor.visitInsn(POP);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(23, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
new file mode 100644
index 0000000..0dea699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
@@ -0,0 +1,191 @@
+// 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.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class A {
+// A() {
+// x();
+// y();
+// }
+//
+// private void x() {
+// System.out.println("A#x");
+// }
+//
+// public void y() {
+// System.out.println("A#y");
+// }
+// }
+class ADump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "x", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "A", "y", "()V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(5, label3);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "x", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(7, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#x");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(8, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "y", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#y");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class B extends A {
+// }
+class BDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "B", null, "A", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(15, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class Main {
+// public static void main(String[] args) {
+// new B().y();
+// }
+// }
+//
+// then replaced use of y() with x() to introduce IllegalAccessError.
+class MainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(20, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "x", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(21, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+