Merge "Publicize private instance methods to public instance methods, phase 1."
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 13f2404..3d2cc25 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -320,16 +320,17 @@
         timing.end();
       }
 
+      GraphLense graphLense = GraphLense.getIdentityLense();
+
       if (options.proguardConfiguration.isAccessModificationAllowed()) {
-        ClassAndMemberPublicizer.run(application, appInfo.dexItemFactory);
+        graphLense = ClassAndMemberPublicizer.run(
+            executorService, timing, application, appInfo, rootSet, graphLense);
         // We can now remove visibility bridges. Note that we do not need to update the
         // invoke-targets here, as the existing invokes will simply dispatch to the now
         // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
         application = new VisibilityBridgeRemover(appInfo, application).run();
       }
 
-      GraphLense graphLense = GraphLense.getIdentityLense();
-
       if (appInfo.hasLiveness()) {
         if (options.proguardConfiguration.hasApplyMappingFile()) {
           SeedMapper seedMapper =
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 2c188da..64c85f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -13,6 +13,7 @@
 import com.google.common.collect.Iterators;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public abstract class DexClass extends DexItem {
@@ -158,6 +159,32 @@
     return result;
   }
 
+  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    int vLen = virtualMethods.length;
+    int dLen = directMethods.length;
+    int mLen = privateInstanceMethods.size();
+    assert mLen <= dLen;
+
+    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen];
+    int index = 0;
+    for (int i = 0; i < dLen; i++) {
+      DexEncodedMethod encodedMethod = directMethods[i];
+      if (!privateInstanceMethods.contains(encodedMethod)) {
+        newDirectMethods[index++] = encodedMethod;
+      }
+    }
+    assert index == dLen - mLen;
+    setDirectMethods(newDirectMethods);
+
+    DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen];
+    System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen);
+    index = vLen;
+    for (DexEncodedMethod encodedMethod : privateInstanceMethods) {
+      newVirtualMethods[index++] = encodedMethod;
+    }
+    setVirtualMethods(newVirtualMethods);
+  }
+
   /**
    * For all annotations on the class and all annotations on its methods and fields apply the
    * specified consumer.
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 83cb824..845eecd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -160,6 +160,10 @@
     return isVirtualMethod() && !accessFlags.isAbstract();
   }
 
+  public boolean isPublicized() {
+    return optimizationInfo.isPublicized();
+  }
+
   public boolean isPublicMethod() {
     return accessFlags.isPublic();
   }
@@ -669,6 +673,7 @@
     private boolean neverReturnsNormally = false;
     private boolean returnsConstant = false;
     private long returnedConstant = 0;
+    private boolean publicized = false;
     private boolean forceInline = false;
     private boolean useIdentifierNameString = false;
     private boolean checksNullReceiverBeforeAnySideEffect = false;
@@ -689,6 +694,7 @@
       neverReturnsNull = template.neverReturnsNull;
       returnsConstant = template.returnsConstant;
       returnedConstant = template.returnedConstant;
+      publicized = template.publicized;
       forceInline = template.forceInline;
       useIdentifierNameString = template.useIdentifierNameString;
       checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
@@ -752,6 +758,10 @@
       return returnedConstant;
     }
 
+    public boolean isPublicized() {
+      return publicized;
+    }
+
     public boolean forceInline() {
       return forceInline;
     }
@@ -792,6 +802,10 @@
       forceInline = true;
     }
 
+    private void markPublicized() {
+      publicized = true;
+    }
+
     private void markUseIdentifierNameString() {
       useIdentifierNameString = true;
     }
@@ -865,6 +879,10 @@
     ensureMutableOI().markForceInline();
   }
 
+  synchronized public void markPublicized() {
+    ensureMutableOI().markPublicized();
+  }
+
   synchronized public void markUseIdentifierNameString() {
     ensureMutableOI().markUseIdentifierNameString();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 297dd57..eaeb1ee 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -3,65 +3,179 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.graph.AppInfo;
 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.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 public final class ClassAndMemberPublicizer {
-  private final DexItemFactory factory;
   private final DexApplication application;
+  private final AppInfo appInfo;
+  private final RootSet rootSet;
+  private final GraphLense previuosLense;
+  private final PublicizedLenseBuilder lenseBuilder;
 
-  private ClassAndMemberPublicizer(DexApplication application, DexItemFactory factory) {
+  private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+  // TODO(b/72109068): finer-grained naming spaces, e.g., per-tree.
+  private final Set<Wrapper<DexMethod>> methodPool = Sets.newConcurrentHashSet();
+
+  private ClassAndMemberPublicizer(
+      DexApplication application,
+      AppInfo appInfo,
+      RootSet rootSet,
+      GraphLense previousLense) {
     this.application = application;
-    this.factory = factory;
+    this.appInfo = appInfo;
+    this.rootSet = rootSet;
+    this.previuosLense = previousLense;
+    lenseBuilder = PublicizerLense.createBuilder();
   }
 
   /**
-   * Marks all package private and protected methods and fields as public. Also makes
-   * all private static methods public.
+   * Marks all package private and protected methods and fields as public.
+   * Makes all private static methods public.
+   * Makes private instance methods public final instance methods, if possible.
    * <p>
    * This will destructively update the DexApplication passed in as argument.
    */
-  public static DexApplication run(DexApplication application, DexItemFactory factory) {
-    return new ClassAndMemberPublicizer(application, factory).run();
+  public static GraphLense run(
+      ExecutorService executorService,
+      Timing timing,
+      DexApplication application,
+      AppInfo appInfo,
+      RootSet rootSet,
+      GraphLense previousLense) throws ExecutionException {
+    return new ClassAndMemberPublicizer(application, appInfo, rootSet, previousLense)
+        .run(executorService, timing);
   }
 
-  private DexApplication run() {
-    for (DexClass clazz : application.classes()) {
+  private GraphLense run(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    // Phase 1: Collect methods to check if private instance methods don't have conflicts.
+    timing.begin("Phase 1: collectMethods");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      // TODO(b/72109068): finer-grained naming spaces will need a different class visiting.
+      application.classes().forEach(clazz ->
+          futures.add(executorService.submit(collectMethodPerClass(clazz))));
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+
+    // Phase 2: Visit classes and promote class/member to public if possible.
+    timing.begin("Phase 2: promoteToPublic");
+    DexType.forAllInterfaces(appInfo.dexItemFactory, this::publicizeType);
+    publicizeType(appInfo.dexItemFactory.objectType);
+    timing.end();
+
+    return lenseBuilder.build(appInfo, previuosLense);
+  }
+
+  private Runnable collectMethodPerClass(DexClass clazz) {
+    return () -> {
+      clazz.forEachMethod(encodedMethod -> {
+        // We will add private instance methods when we promote them.
+        if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
+          methodPool.add(equivalence.wrap(encodedMethod.method));
+        }
+      });
+    };
+  }
+
+  private void publicizeType(DexType type) {
+    DexClass clazz = application.definitionFor(type);
+    if (clazz != null && clazz.isProgramClass()) {
       clazz.accessFlags.promoteToPublic();
-      clazz.forEachMethod(this::publicizeMethod);
       clazz.forEachField(field -> field.accessFlags.promoteToPublic());
+      Set<DexEncodedMethod> privateInstanceEncodedMethods = new LinkedHashSet<>();
+      clazz.forEachMethod(encodedMethod -> {
+        if (publicizeMethod(clazz, encodedMethod)) {
+          privateInstanceEncodedMethods.add(encodedMethod);
+        }
+      });
+      if (!privateInstanceEncodedMethods.isEmpty()) {
+        clazz.virtualizeMethods(privateInstanceEncodedMethods);
+      }
     }
-    return application;
+
+    // TODO(b/72109068): Can process sub types in parallel.
+    type.forAllExtendsSubtypes(this::publicizeType);
   }
 
-  private void publicizeMethod(DexEncodedMethod method) {
-    MethodAccessFlags accessFlags = method.accessFlags;
+  private boolean publicizeMethod(DexClass holder, DexEncodedMethod encodedMethod) {
+    MethodAccessFlags accessFlags = encodedMethod.accessFlags;
     if (accessFlags.isPublic()) {
-      return;
+      return false;
     }
 
-    if (factory.isClassConstructor(method.method)) {
-      return;
+    if (appInfo.dexItemFactory.isClassConstructor(encodedMethod.method)) {
+      return false;
     }
 
     if (!accessFlags.isPrivate()) {
       accessFlags.unsetProtected();
       accessFlags.setPublic();
-      return;
+      return false;
     }
     assert accessFlags.isPrivate();
 
-    if (factory.isConstructor(method.method)) {
-      // TODO: b/72211928
-      return;
+    if (appInfo.dexItemFactory.isConstructor(encodedMethod.method)) {
+      // TODO(b/72211928)
+      return false;
     }
 
     if (!accessFlags.isStatic()) {
-      // TODO: b/72109068
-      return;
+      // If this method is mentioned in keep rules, do not transform (rule applications changed).
+      if (rootSet.noShrinking.containsKey(encodedMethod)) {
+        return false;
+      }
+
+      // We can't publicize private instance methods in interfaces or methods that are copied from
+      // interfaces to lambda-desugared classes because this will be added as a new default method.
+      // TODO(b/72109068): It might be possible to transform it into static methods, though.
+      if (holder.isInterface() || accessFlags.isSynthetic()) {
+        return false;
+      }
+
+      Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
+      if (methodPool.contains(key)) {
+        // We can't do anything further because even renaming is not allowed due to the keep rule.
+        if (rootSet.noObfuscation.contains(encodedMethod)) {
+          return false;
+        }
+        // TODO(b/72109068): Renaming will enable more private instance methods to be publicized.
+        return false;
+      }
+      methodPool.add(key);
+      lenseBuilder.add(encodedMethod.method);
+      accessFlags.unsetPrivate();
+      accessFlags.setFinal();
+      accessFlags.setPublic();
+      // Although the current method became public, it surely has the single virtual target.
+      encodedMethod.method.setSingleVirtualMethodCache(
+          encodedMethod.method.getHolder(), encodedMethod);
+      encodedMethod.markPublicized();
+      return true;
     }
 
     // For private static methods we can just relax the access to private, since
@@ -70,5 +184,6 @@
     // does not take into account access of the static methods.
     accessFlags.unsetPrivate();
     accessFlags.setPublic();
+    return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
new file mode 100644
index 0000000..835135e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -0,0 +1,68 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+final class PublicizerLense extends NestedGraphLense {
+  private final AppInfo appInfo;
+  private final Set<DexMethod> publicizedMethods;
+
+  PublicizerLense(
+      AppInfo appInfo, GraphLense previousLense, Set<DexMethod> publicizedMethods) {
+    // This lense does not map any DexItem's at all.
+    // It will just tweak invoke type for publicized methods from invoke-direct to invoke-virtual.
+    super(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(),
+        previousLense, appInfo.dexItemFactory);
+    this.appInfo = appInfo;
+    this.publicizedMethods = publicizedMethods;
+  }
+
+  @Override
+  public GraphLenseLookupResult lookupMethod(
+      DexMethod method, DexEncodedMethod context, Type type) {
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    method = previous.getMethod();
+    type = previous.getType();
+    if (type == Type.DIRECT && publicizedMethods.contains(method)) {
+      DexClass holderClass = appInfo.definitionFor(method.holder);
+      if (holderClass != null) {
+        DexEncodedMethod actualEncodedTarget = holderClass.lookupVirtualMethod(method);
+        if (actualEncodedTarget != null
+            && actualEncodedTarget.isPublicized()) {
+          return new GraphLenseLookupResult(method, Type.VIRTUAL);
+        }
+      }
+    }
+    return super.lookupMethod(method, context, type);
+  }
+
+  static PublicizedLenseBuilder createBuilder() {
+    return new PublicizedLenseBuilder();
+  }
+
+  static class PublicizedLenseBuilder {
+    private final ImmutableSet.Builder<DexMethod> methodSetBuilder = ImmutableSet.builder();
+
+    private PublicizedLenseBuilder() {
+    }
+
+    public GraphLense build(AppInfo appInfo, GraphLense previousLense) {
+      return new PublicizerLense(appInfo, previousLense, methodSetBuilder.build());
+    }
+
+    public void add(DexMethod publicizedMethod) {
+      methodSetBuilder.add(publicizedMethod);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 34b92b0..027b7e8 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -4,35 +4,89 @@
 
 package com.android.tools.r8.accessrelaxation;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPublic;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.accessrelaxation.privateinstance.Base;
+import com.android.tools.r8.accessrelaxation.privateinstance.Sub1;
+import com.android.tools.r8.accessrelaxation.privateinstance.Sub2;
+import com.android.tools.r8.accessrelaxation.privateinstance.TestMain;
 import com.android.tools.r8.accessrelaxation.privatestatic.A;
 import com.android.tools.r8.accessrelaxation.privatestatic.B;
 import com.android.tools.r8.accessrelaxation.privatestatic.BB;
 import com.android.tools.r8.accessrelaxation.privatestatic.C;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(VmTestRunner.class)
 public class AccessRelaxationTest extends TestBase {
-  @Test
-  public void testStaticMethodRelaxation() throws Exception {
+  private static final String STRING = "java.lang.String";
+
+  private static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
     R8Command.Builder builder = R8Command.builder();
-    builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(A.class.getPackage()));
+    builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
+    for (Class clazz : classes) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+    }
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
     builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    return builder;
+  }
+
+  private void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
+    // Run on Jvm.
+    String jvmOutput = runOnJava(mainClass);
+
+    // Run on Art to check generated code against verifier.
+    String artOutput = runOnArt(app, mainClass);
+
+    String adjustedArtOutput = artOutput.replace(
+        "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
+    assertEquals(jvmOutput, adjustedArtOutput);
+  }
+
+  private static void assertPublic(
+      DexInspector dexInspector, Class clazz, MethodSignature signature) {
+    ClassSubject classSubject = dexInspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(signature);
+    assertThat(methodSubject, isPublic());
+  }
+
+  private static void assertNotPublic(
+      DexInspector dexInspector, Class clazz, MethodSignature signature) {
+    ClassSubject classSubject = dexInspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(signature);
+    assertThat(methodSubject, not(isPublic()));
+  }
+
+  @Test
+  public void testStaticMethodRelaxation() throws Exception {
+    Class mainClass = C.class;
+    R8Command.Builder builder = loadProgramFiles(mainClass.getPackage());
 
     // Note: we use '-checkdiscard' to indirectly check that the access relaxation is
     // done which leads to inlining of all pB*** methods so they are removed. Without
     // access relaxation inlining is not performed and method are kept.
     builder.addProguardConfiguration(
         ImmutableList.of(
-            "-keep class " + C.class.getCanonicalName() + "{",
+            "-keep class " + mainClass.getCanonicalName() + "{",
             "  public static void main(java.lang.String[]);",
             "}",
             "",
@@ -57,15 +111,71 @@
         Origin.unknown());
 
     AndroidApp app = ToolHelper.runR8(builder.build());
+    compareJvmAndArt(app, mainClass);
 
-    // Run on Jvm.
-    String jvmOutput = runOnJava(C.class);
+    DexInspector dexInspector = new DexInspector(app);
+    assertPublic(dexInspector, A.class,
+        new MethodSignature("baz", STRING, ImmutableList.of()));
+    assertPublic(dexInspector, A.class,
+        new MethodSignature("bar", STRING, ImmutableList.of()));
+    assertPublic(dexInspector, A.class,
+        new MethodSignature("bar", STRING, ImmutableList.of("int")));
+    assertPublic(dexInspector, A.class,
+        new MethodSignature("blah", STRING, ImmutableList.of("int")));
 
-    // Run on Art to check generated code against verifier.
-    String artOutput = runOnArt(app, C.class);
+    assertPublic(dexInspector, B.class,
+        new MethodSignature("blah", STRING, ImmutableList.of("int")));
 
-    String adjustedArtOutput = artOutput.replace(
-        "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
-    assertEquals(jvmOutput, adjustedArtOutput);
+    assertPublic(dexInspector, BB.class,
+        new MethodSignature("blah", STRING, ImmutableList.of("int")));
+  }
+
+  @Test
+  public void testInstanceMethodRelaxation() throws Exception {
+    Class mainClass = TestMain.class;
+    R8Command.Builder builder = loadProgramFiles(mainClass.getPackage());
+
+    builder.addProguardConfiguration(
+        ImmutableList.of(
+            "-keep class " + mainClass.getCanonicalName() + "{",
+            "  public static void main(java.lang.String[]);",
+            "}",
+            "",
+            "-checkdiscard class " + Base.class.getCanonicalName() + "{",
+            "  *** p*();",
+            "}",
+            "",
+            "-checkdiscard class " + Sub1.class.getCanonicalName() + "{",
+            "  *** p*();",
+            "}",
+            "",
+            "-checkdiscard class " + Sub2.class.getCanonicalName() + "{",
+            "  *** p*();",
+            "}",
+            "",
+            "-dontobfuscate",
+            "-allowaccessmodification"
+        ),
+        Origin.unknown());
+
+    AndroidApp app = ToolHelper.runR8(builder.build());
+    compareJvmAndArt(app, mainClass);
+
+    DexInspector dexInspector = new DexInspector(app);
+    assertPublic(dexInspector, Base.class,
+        new MethodSignature("foo", STRING, ImmutableList.of()));
+
+    // Base#foo?() can't be publicized due to Itf<1>#foo<1>().
+    assertNotPublic(dexInspector, Base.class,
+        new MethodSignature("foo1", STRING, ImmutableList.of()));
+    assertNotPublic(dexInspector, Base.class,
+        new MethodSignature("foo2", STRING, ImmutableList.of()));
+
+    // Sub1#bar1(int) can't be publicized due to Base#bar1(int).
+    assertNotPublic(dexInspector, Sub1.class,
+        new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+
+    assertPublic(dexInspector, Sub2.class,
+        new MethodSignature("bar2", STRING, ImmutableList.of("int")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
new file mode 100644
index 0000000..fce1f4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -0,0 +1,143 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class InvokeTypeConversionTest extends SmaliTestBase {
+  private final String CLASS_NAME = "Example";
+  private MethodSignature main;
+
+  private SmaliBuilder buildTestClass(String invokeLine) {
+    SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
+    builder.addDefaultConstructor();
+    builder.addPrivateInstanceMethod("int", "foo", ImmutableList.of(), 1,
+        "  const/4 v0, 0",
+        "  return v0");
+    builder.addPrivateStaticMethod("int", "bar", ImmutableList.of(), 1,
+        "  const/4 v0, 0",
+        "  return v0");
+    main = builder.addMainMethod(2,
+        "new-instance v1, L" + CLASS_NAME + ";",
+        "invoke-direct { v1 }, L" + CLASS_NAME + ";-><init>()V",
+        invokeLine,
+        "move-result v1",
+        "invoke-static { v1 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;",
+        "move-result-object v1",
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V",
+        "return-void");
+    return builder;
+  }
+
+  private void run(
+      SmaliBuilder builder,
+      String expectedException,
+      Consumer<DexInspector> inspectorConsumer) throws Exception {
+    AndroidApp app = buildApplication(builder);
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        "-printmapping",
+        "-dontobfuscate",
+        "-allowaccessmodification");
+    R8Command.Builder command = ToolHelper.prepareR8CommandBuilder(app);
+    command.addProguardConfiguration(pgConfigs, Origin.unknown());
+    AndroidApp processedApp = ToolHelper.runR8(command.build(), o -> {
+      o.enableInlining = false;
+    });
+    ProcessResult artResult = runOnArtRaw(processedApp, CLASS_NAME);
+    if (expectedException == null) {
+      assertEquals(0, artResult.exitCode);
+      assertEquals("0", artResult.stdout);
+    } else {
+      assertEquals(1, artResult.exitCode);
+      assertThat(artResult.stderr, containsString(expectedException));
+    }
+    DexInspector inspector = new DexInspector(processedApp);
+    inspectorConsumer.accept(inspector);
+  }
+
+  // The following test checks invoke-direct, which refers to the private static method, is *not*
+  // rewritten by publicizer lense, resulting in IncompatibleClassChangeError, which is expected.
+  //
+  // class Example {
+  //   private int foo() { return 0; }
+  //   private static int bar() { return 0; }
+  //   public static void main(String[] args) {
+  //     Example instance = new Example();
+  //     "invoke-direct instance, Example->bar()"  <- should yield IncompatibleClassChangeError
+  //     ...
+  //   }
+  // }
+  @Test
+  public void invokeDirectToAlreadyStaticMethod() throws Exception {
+    SmaliBuilder builder = buildTestClass(
+        "invoke-direct { v1 }, L" + CLASS_NAME + ";->bar()I");
+    run(builder, "IncompatibleClassChangeError", dexInspector -> {
+      ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+      assertThat(clazz, isPresent());
+      DexEncodedMethod method = getMethod(dexInspector, main);
+      assertNotNull(method);
+      DexCode code = method.getCode().asDexCode();
+      // The given invoke line is remained as-is.
+      assertTrue(code.instructions[2] instanceof InvokeDirect);
+    });
+  }
+
+  // The following test checks invoke-direct, which refers to the private instance method, *is*
+  // rewritten by publicizer lense, as the target method will be publicized.
+  //
+  // class Example {
+  //   private int foo() { return 0; }
+  //   private static int bar() { return 0; }
+  //   public static void main(String[] args) {
+  //     Example instance = new Example();
+  //     instance.foo();  // which was "invoke-direct instance, Example->foo()"
+  //                      // will be "invoke-virtual instance, Example->foo()"
+  //     ...
+  //   }
+  // }
+  @Test
+  public void invokeDirectToPublicizedMethod() throws Exception {
+    SmaliBuilder builder = buildTestClass(
+        "invoke-direct { v1 }, L" + CLASS_NAME + ";->foo()I");
+    run(builder, null, dexInspector -> {
+      ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+      assertThat(clazz, isPresent());
+      DexEncodedMethod method = getMethod(dexInspector, main);
+      assertNotNull(method);
+      DexCode code = method.getCode().asDexCode();
+      // The given invoke line is changed to invoke-virtual
+      assertTrue(code.instructions[2] instanceof InvokeVirtual);
+    });
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
new file mode 100644
index 0000000..fd1fd1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -0,0 +1,48 @@
+// 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.accessrelaxation.privateinstance;
+
+public class Base {
+
+  // NOTE: here and below 'synchronized' is supposed to disable inlining of this method.
+  private synchronized String foo() {
+    return "Base::foo()";
+  }
+
+  public String pFoo() {
+    return foo();
+  }
+
+  private synchronized String foo1() {
+    return "Base::foo1()";
+  }
+
+  public String pFoo1() {
+    return foo1();
+  }
+
+  private synchronized String foo2() {
+    return "Base::foo2()";
+  }
+
+  public String pFoo2() {
+    return foo2();
+  }
+
+  private synchronized String bar1(int i) {
+    throw new AssertionError("Sub1#bar1(int) will not use this signature.");
+  }
+
+  public void dump() {
+    System.out.println(foo());
+    System.out.println(foo1());
+    System.out.println(foo2());
+    try {
+      bar1(0);
+    } catch (AssertionError e) {
+      // expected
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf1.java
new file mode 100644
index 0000000..bc44973
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf1.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.accessrelaxation.privateinstance;
+
+public interface Itf1 {
+  String foo1();
+
+  default String foo1(int i) {
+    return "Itf1::foo1(" + i + ") >> " + foo1();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf2.java
new file mode 100644
index 0000000..233a327
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Itf2.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.accessrelaxation.privateinstance;
+
+public interface Itf2 {
+  String foo2();
+
+  default String foo2(int i) {
+    return "Itf2::foo2(" + i + ") >> " + foo2();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
new file mode 100644
index 0000000..4cbbf11
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -0,0 +1,32 @@
+// 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.accessrelaxation.privateinstance;
+
+public class Sub1 extends Base implements Itf1 {
+
+  @Override
+  public String foo1() {
+    return "Sub1::foo1()";
+  }
+
+  private synchronized String bar1(int i) {
+    return "Sub1::bar1(" + i + ")";
+  }
+
+  public String pBar1() {
+    return bar1(1);
+  }
+
+  @Override
+  public void dump() {
+    System.out.println(foo1());
+    System.out.println(foo1(0));
+    try {
+      System.out.println(bar1(0));
+    } catch (Throwable e) {
+      System.out.println(e.getClass().getName());
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
new file mode 100644
index 0000000..8c4e01e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -0,0 +1,28 @@
+// 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.accessrelaxation.privateinstance;
+
+public class Sub2 extends Base implements Itf2 {
+
+  @Override
+  public String foo2() {
+    return "Sub2::foo2()";
+  }
+
+  private synchronized String bar2(int i) {
+    return "Sub2::bar2(" + i + ")";
+  }
+
+  public String pBar2() {
+    return bar2(2);
+  }
+
+  @Override
+  public void dump() {
+    System.out.println(foo2());
+    System.out.println(foo2(0));
+    System.out.println(bar2(0));
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/TestMain.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/TestMain.java
new file mode 100644
index 0000000..bfd322b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/TestMain.java
@@ -0,0 +1,13 @@
+// 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.accessrelaxation.privateinstance;
+
+public class TestMain {
+
+  public static void main(String[] args) {
+    new Base().dump();
+    new Sub1().dump();
+    new Sub2().dump();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierTest.java b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
index 4b42880..8a5975ea 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
@@ -98,10 +98,10 @@
     // method naming001.A.m should be kept, according to the keep rule.
     assertEquals("m", naming.lookupName(m).toSourceString());
 
+    // method naming001.A.privateFunc is transformed to a public method, and then kept.
     DexMethod p = dexItemFactory.createMethod(
         a, dexItemFactory.createProto(dexItemFactory.voidType), "privateFunc");
-    // method naming001.A.privateFunc would be renamed.
-    assertNotEquals("privateFunc", naming.lookupName(p).toSourceString());
+    assertEquals("privateFunc", naming.lookupName(p).toSourceString());
   }
 
   private static void test001_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
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 f311c15..6d16184 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -57,7 +58,7 @@
   }
 
   @Before
-  public void readApp() throws IOException, ExecutionException, ProguardRuleParserException {
+  public void readApp() throws IOException, ExecutionException {
     program = ToolHelper.buildApplication(ImmutableList.of(appFileName));
     dexItemFactory = program.dexItemFactory;
     appInfo = new AppInfoWithSubtyping(program);
@@ -70,13 +71,18 @@
     InternalOptions options = new InternalOptions(configuration,
         new Reporter(new DefaultDiagnosticsHandler()));
 
-    if (options.proguardConfiguration.isAccessModificationAllowed()) {
-      ClassAndMemberPublicizer.run(program, dexItemFactory);
-    }
-
     ExecutorService executor = ThreadUtils.getExecutorService(1);
+
     RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
         .run(executor);
+
+    if (options.proguardConfiguration.isAccessModificationAllowed()) {
+      ClassAndMemberPublicizer.run(
+          executor, timing, program, appInfo, rootSet, GraphLense.getIdentityLense());
+      rootSet =
+          new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
+    }
+
     Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
     appInfo = enqueuer.traceApplication(rootSet, executor, timing);
     return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 1f9c20f..3e46098 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.function.BiConsumer;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class NeverReturnsNormallyTest extends TestBase {
@@ -127,6 +128,7 @@
     return instructions.next();
   }
 
+  @Ignore("b/110736241")
   @Test
   public void test() throws Exception {
     runTest(this::validate, CompilationMode.DEBUG);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index e818801..1dffcee 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -257,6 +257,12 @@
     return addStaticMethod(returnType, name, parameters, locals, buildCode(instructions));
   }
 
+  public MethodSignature addPrivateStaticMethod(
+      String returnType, String name, List<String> parameters, int locals, String... instructions) {
+    return addMethod(
+        "private static", returnType, name, parameters, locals, buildCode(instructions));
+  }
+
   public MethodSignature addStaticMethod(
       String returnType, String name, List<String> parameters, int locals, String code) {
     return addStaticMethod("", returnType, name, parameters, locals, code);
@@ -301,6 +307,11 @@
         "public constructor", "void", "<init>", parameters, locals, buildCode(instructions));
   }
 
+  public MethodSignature addPrivateInstanceMethod(
+      String returnType, String name, List<String> parameters, int locals, String... instructions) {
+    return addMethod("private", returnType, name, parameters, locals, buildCode(instructions));
+  }
+
   public MethodSignature addInstanceMethod(
       String returnType, String name, int locals, String... instructions) {
     return addInstanceMethod(returnType, name, ImmutableList.of(), locals, buildCode(instructions));
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index fd32ec7..0b8de12 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -715,6 +715,8 @@
 
   public abstract class MemberSubject extends Subject {
 
+    public abstract boolean isPublic();
+
     public abstract boolean isStatic();
 
     public abstract boolean isFinal();
@@ -773,6 +775,11 @@
     }
 
     @Override
+    public boolean isPublic() {
+      return false;
+    }
+
+    @Override
     public boolean isStatic() {
       return false;
     }
@@ -849,6 +856,11 @@
     }
 
     @Override
+    public boolean isPublic() {
+      return dexMethod.accessFlags.isPublic();
+    }
+
+    @Override
     public boolean isStatic() {
       return dexMethod.accessFlags.isStatic();
     }
@@ -966,6 +978,11 @@
   public class AbsentFieldSubject extends FieldSubject {
 
     @Override
+    public boolean isPublic() {
+      return false;
+    }
+
+    @Override
     public boolean isStatic() {
       return false;
     }
@@ -1032,6 +1049,11 @@
     }
 
     @Override
+    public boolean isPublic() {
+      return dexField.accessFlags.isPublic();
+    }
+
+    @Override
     public boolean isStatic() {
       return dexField.accessFlags.isStatic();
     }
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
index 8e21bea..1986bbc 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -138,4 +138,24 @@
       }
     };
   }
+
+  public static Matcher<MethodSubject> isPublic() {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      public boolean matchesSafely(final MethodSubject method) {
+        return method.isPresent() && method.isPublic();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("method public");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject method, Description description) {
+        description
+            .appendText("method ").appendValue(method.getOriginalName()).appendText(" was not");
+      }
+    };
+  }
 }