Merge "Ensure pretended members indeed do not exist while applying mappings."
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();
+  }
+}
+