Support for -if rules in presence of access relaxation

Bug: 117330692, 117403482
Change-Id: I0083075ff92d53af5614310e99e22ccb52a82044
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index afbb13f..ebeae02 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -9,7 +9,7 @@
 import java.util.function.BooleanSupplier;
 
 /** Access flags common to classes, methods and fields. */
-public abstract class AccessFlags {
+public abstract class AccessFlags<T extends AccessFlags<T>> {
 
   protected static final int BASE_FLAGS
       = Constants.ACC_PUBLIC
@@ -46,26 +46,43 @@
   }
 
   protected int flags;
+  protected boolean isPublicized = false;
 
   protected AccessFlags(int flags) {
     this.flags = flags;
   }
 
+  public abstract T copy();
+
+  public abstract T self();
+
+  public int materialize() {
+    if (isPromotedToPublic()) {
+      return ((flags | Constants.ACC_PUBLIC) & ~Constants.ACC_PROTECTED) & ~Constants.ACC_PRIVATE;
+    }
+    return flags;
+  }
+
   public abstract int getAsCfAccessFlags();
 
   public abstract int getAsDexAccessFlags();
 
+  public final int getOriginalCfAccessFlags() {
+    return flags;
+  }
+
   @Override
-  public boolean equals(Object other) {
-    if (other instanceof AccessFlags) {
-      return flags == ((AccessFlags) other).flags;
+  public boolean equals(Object object) {
+    if (object instanceof AccessFlags) {
+      AccessFlags other = (AccessFlags) object;
+      return flags == other.flags && isPublicized == other.isPublicized;
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return flags;
+    return (flags << 1) | (isPublicized ? 1 : 0);
   }
 
   public boolean isMoreVisibleThan(AccessFlags other) {
@@ -92,7 +109,7 @@
   }
 
   public boolean isPublic() {
-    return isSet(Constants.ACC_PUBLIC);
+    return isSet(Constants.ACC_PUBLIC) || isPromotedToPublic();
   }
 
   public void setPublic() {
@@ -105,7 +122,7 @@
   }
 
   public boolean isPrivate() {
-    return isSet(Constants.ACC_PRIVATE);
+    return isSet(Constants.ACC_PRIVATE) && !isPromotedToPublic();
   }
 
   public void setPrivate() {
@@ -118,7 +135,7 @@
   }
 
   public boolean isProtected() {
-    return isSet(Constants.ACC_PROTECTED);
+    return isSet(Constants.ACC_PROTECTED) && !isPromotedToPublic();
   }
 
   public void setProtected() {
@@ -162,10 +179,21 @@
     unset(Constants.ACC_SYNTHETIC);
   }
 
+  public boolean isPromotedToPublic() {
+    return isPublicized;
+  }
+
+  public T setPromotedToPublic(boolean isPublicized) {
+    this.isPublicized = isPublicized;
+    return self();
+  }
+
   public void promoteToPublic() {
-    unsetProtected();
-    unsetPrivate();
-    setPublic();
+    isPublicized = true;
+  }
+
+  public void unsetPromotedToPublic() {
+    isPublicized = false;
   }
 
   protected boolean isSet(int flag) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 44d6726..6c7caf9 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -8,7 +8,7 @@
 import java.util.List;
 import java.util.function.BooleanSupplier;
 
-public class ClassAccessFlags extends AccessFlags {
+public class ClassAccessFlags extends AccessFlags<ClassAccessFlags> {
 
   // List of valid flags for both DEX and Java.
   private static final int SHARED_FLAGS
@@ -68,24 +68,30 @@
     return new ClassAccessFlags(access & CF_FLAGS);
   }
 
+  @Override
   public ClassAccessFlags copy() {
-    return new ClassAccessFlags(flags);
+    return new ClassAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+  }
+
+  @Override
+  public ClassAccessFlags self() {
+    return this;
   }
 
   @Override
   public int getAsDexAccessFlags() {
     // We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing
     // abstract to interfaces to work around a javac bug when generating package-info classes.
+    int flags = materialize() & ~Constants.ACC_SUPER;
     if (isInterface()) {
-      return (flags & ~Constants.ACC_SUPER) | Constants.ACC_ABSTRACT;
-    } else {
-      return flags & ~Constants.ACC_SUPER;
+      return flags | Constants.ACC_ABSTRACT;
     }
+    return flags;
   }
 
   @Override
   public int getAsCfAccessFlags() {
-    return flags;
+    return materialize();
   }
 
   /**
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 46738e2..79ef9d4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -211,7 +211,7 @@
 
   public boolean isPublicized() {
     checkIfObsolete();
-    return optimizationInfo.isPublicized();
+    return accessFlags.isPromotedToPublic();
   }
 
   public boolean isPublicMethod() {
@@ -889,11 +889,6 @@
     }
 
     @Override
-    public boolean isPublicized() {
-      return NOT_PUBLICZED;
-    }
-
-    @Override
     public boolean isInitializerEnablingJavaAssertions() {
       return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
     }
@@ -1044,11 +1039,6 @@
     }
 
     @Override
-    public boolean isPublicized() {
-      return publicized;
-    }
-
-    @Override
     public boolean isInitializerEnablingJavaAssertions() {
       return initializerEnablingJavaAssertions;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index b56e60f..e940269 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -8,7 +8,7 @@
 import java.util.List;
 import java.util.function.BooleanSupplier;
 
-public class FieldAccessFlags extends AccessFlags {
+public class FieldAccessFlags extends AccessFlags<FieldAccessFlags> {
 
   private static final int FLAGS
       = AccessFlags.BASE_FLAGS
@@ -40,8 +40,14 @@
     super(flags);
   }
 
+  @Override
   public FieldAccessFlags copy() {
-    return new FieldAccessFlags(flags);
+    return new FieldAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+  }
+
+  @Override
+  public FieldAccessFlags self() {
+    return this;
   }
 
   public static FieldAccessFlags fromSharedAccessFlags(int access) {
@@ -59,12 +65,12 @@
 
   @Override
   public int getAsCfAccessFlags() {
-    return flags;
+    return materialize();
   }
 
   @Override
   public int getAsDexAccessFlags() {
-    return flags;
+    return materialize();
   }
 
   public boolean isVolatile() {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 37a6fac..ca507b7 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -8,7 +8,7 @@
 import java.util.List;
 import java.util.function.BooleanSupplier;
 
-public class MethodAccessFlags extends AccessFlags {
+public class MethodAccessFlags extends AccessFlags<MethodAccessFlags> {
 
   private static final int SHARED_FLAGS
       = AccessFlags.BASE_FLAGS
@@ -57,8 +57,14 @@
     super(flags);
   }
 
+  @Override
   public MethodAccessFlags copy() {
-    return new MethodAccessFlags(flags);
+    return new MethodAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+  }
+
+  @Override
+  public MethodAccessFlags self() {
+    return this;
   }
 
   public static MethodAccessFlags fromSharedAccessFlags(int access, boolean isConstructor) {
@@ -88,12 +94,12 @@
       copy.unsetSynchronized();
       copy.setDeclaredSynchronized();
     }
-    return copy.flags;
+    return copy.materialize();
   }
 
   @Override
   public int getAsCfAccessFlags() {
-    return flags & ~Constants.ACC_CONSTRUCTOR;
+    return materialize() & ~Constants.ACC_CONSTRUCTOR;
   }
 
   public boolean isSynchronized() {
diff --git a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
index 4fc81d0..1a58eba 100644
--- a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
@@ -41,8 +41,6 @@
 
   long getReturnedConstant();
 
-  boolean isPublicized();
-
   boolean forceInline();
 
   boolean neverInline();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 605fdf9..8469004 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -53,7 +53,7 @@
     DexEncodedMethod mainMethod = null;
 
     for (DexEncodedMethod method : lambda.virtualMethods()) {
-      if (method.accessFlags.equals(MAIN_METHOD_FLAGS)) {
+      if (method.accessFlags.materialize() == MAIN_METHOD_FLAGS.materialize()) {
         if (mainMethod != null) {
           throw new LambdaStructureError("more than one main method found");
         }
@@ -208,7 +208,7 @@
   static <T extends AccessFlags> void checkAccessFlags(
       String message, T actual, T... expected) throws LambdaStructureError {
     for (T flag : expected) {
-      if (flag.equals(actual)) {
+      if (flag.materialize() == actual.materialize()) {
         return;
       }
     }
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 d39f81b..b59e3e9 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -7,36 +7,32 @@
 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.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.ir.optimize.MethodPoolCollection;
 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.Timing;
-import com.google.common.base.Equivalence;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 public final class ClassAndMemberPublicizer {
+
   private final DexApplication application;
   private final AppView appView;
   private final RootSet rootSet;
-  private final PublicizedLenseBuilder lenseBuilder;
-
-  private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
   private final MethodPoolCollection methodPoolCollection;
 
+  private final PublicizedLenseBuilder lenseBuilder = PublicizerLense.createBuilder();
+
   private ClassAndMemberPublicizer(DexApplication application, AppView appView, RootSet rootSet) {
     this.application = application;
     this.appView = appView;
     this.methodPoolCollection = new MethodPoolCollection(application);
     this.rootSet = rootSet;
-    lenseBuilder = PublicizerLense.createBuilder();
   }
 
   /**
@@ -99,15 +95,13 @@
     }
 
     if (!accessFlags.isPrivate()) {
-      accessFlags.unsetProtected();
-      accessFlags.setPublic();
+      accessFlags.promoteToPublic();
       return false;
     }
     assert accessFlags.isPrivate();
 
     if (appView.dexItemFactory().isConstructor(encodedMethod.method)) {
-      accessFlags.unsetPrivate();
-      accessFlags.setPublic();
+      accessFlags.promoteToPublic();
       return false;
     }
 
@@ -134,9 +128,8 @@
         return false;
       }
       lenseBuilder.add(encodedMethod.method);
-      accessFlags.unsetPrivate();
       accessFlags.setFinal();
-      accessFlags.setPublic();
+      accessFlags.promoteToPublic();
       // Although the current method became public, it surely has the single virtual target.
       encodedMethod.method.setSingleVirtualMethodCache(
           encodedMethod.method.getHolder(), encodedMethod);
@@ -148,8 +141,7 @@
     // even though JLS prevents from declaring static method in derived class if
     // an instance method with same signature exists in superclass, JVM actually
     // does not take into account access of the static methods.
-    accessFlags.unsetPrivate();
-    accessFlags.setPublic();
+    accessFlags.promoteToPublic();
     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
index b11393a..e51b38d 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -18,7 +18,7 @@
   private final AppView appView;
   private final Set<DexMethod> publicizedMethods;
 
-  PublicizerLense(AppView appView, Set<DexMethod> publicizedMethods) {
+  private PublicizerLense(AppView appView, 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(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
index 7cf8997..9dfddb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
@@ -54,15 +54,15 @@
   }
 
   public boolean containsAll(AccessFlags other) {
-    return containsAll(other.getAsCfAccessFlags());
+    return containsAll(other.getOriginalCfAccessFlags());
   }
 
   public boolean containsNone(AccessFlags other) {
-    return containsNone(other.getAsCfAccessFlags());
+    return containsNone(other.getOriginalCfAccessFlags());
   }
 
   public void setFlags(AccessFlags other) {
-    this.flags = other.getAsCfAccessFlags();
+    this.flags = other.getOriginalCfAccessFlags();
   }
 
   public void setPublic() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 5424cc2..ae54a80 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -171,7 +171,6 @@
       case ALL:
       case ALL_FIELDS:
         // Access flags check.
-        // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
         if (!getAccessFlags().containsAll(field.accessFlags)
             || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
           break;
@@ -185,7 +184,6 @@
           break;
         }
         // Access flags check.
-        // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
         if (!getAccessFlags().containsAll(field.accessFlags)
             || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
           break;
@@ -219,7 +217,6 @@
         // Fall through for all other methods.
       case ALL:
         // Access flags check.
-        // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
         if (!getAccessFlags().containsAll(method.accessFlags)
             || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
           break;
@@ -240,7 +237,6 @@
           break;
         }
         // Access flags check.
-        // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
         if (!getAccessFlags().containsAll(method.accessFlags)
             || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
           break;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 4dfee8e..284b03f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1146,11 +1146,11 @@
               ParameterAnnotationsList.empty(),
               code,
               method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
-      if (method.getOptimizationInfo().isPublicized()) {
+      if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
         // reflect that this method was publicized.
-        bridge.getMutableOptimizationInfo().markPublicized();
-        method.getMutableOptimizationInfo().unsetPublicized();
+        assert bridge.accessFlags.isPromotedToPublic();
+        method.accessFlags.unsetPromotedToPublic();
       }
       return bridge;
     }
@@ -1345,6 +1345,7 @@
 
   private static void makePrivate(DexEncodedMethod method) {
     assert !method.accessFlags.isAbstract();
+    method.accessFlags.unsetPromotedToPublic();
     method.accessFlags.unsetPublic();
     method.accessFlags.unsetProtected();
     method.accessFlags.setPrivate();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 592aeae..415376f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -10,13 +10,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
@@ -41,7 +38,6 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 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.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -50,10 +46,8 @@
 import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Set;
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 8a5975ea..473ca53 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,11 @@
     // 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");
-    assertEquals("privateFunc", naming.lookupName(p).toSourceString());
+    // method naming001.A.privateFunc is not private in the input and should therefore be renamed.
+    DexMethod p =
+        dexItemFactory.createMethod(
+            a, dexItemFactory.createProto(dexItemFactory.voidType), "privateFunc");
+    assertNotEquals("privateFunc", naming.lookupName(p).toSourceString());
   }
 
   private static void test001_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index a603f92..d300a4e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -102,12 +103,6 @@
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
     classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    if (shrinker.isR8()) {
-      // TODO(b/117330692): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
-      // at the 2nd tree shaking.
-      assertThat(classSubject, not(isPresent()));
-      return;
-    }
     assertThat(classSubject, isPresent());
     methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, isPresent());
@@ -141,18 +136,12 @@
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
     classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    if (shrinker.isR8()) {
-      // TODO(b/117330692): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
-      // at the 2nd tree shaking.
-      assertThat(classSubject, not(isPresent()));
-      return;
-    }
     assertThat(classSubject, isPresent());
     methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, not(isPresent()));
     methodSubject = classSubject.method(nonPublicMethod);
     assertThat(methodSubject, isPresent());
-    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+    assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
   }
 
   @Test
@@ -217,12 +206,6 @@
     assertThat(methodSubject, not(isPresent()));
     methodSubject = classSubject.method(nonPublicMethod);
     assertThat(methodSubject, isPresent());
-    if (shrinker.isR8()) {
-      // TODO(b/117330692): if kept in the 1st tree shaking, should not be publicized.
-      assertTrue(methodSubject.getMethod().accessFlags.isPublic());
-      return;
-    }
-    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+    assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
new file mode 100644
index 0000000..cf6d7c0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithAccessRelaxation extends TestBase {
+
+  private final Backend backend;
+
+  public IfRuleWithAccessRelaxation(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(IfRuleWithAccessRelaxation.class)
+            .addKeepRules(
+                "-keep class " + TestClass.class.getTypeName() + " { int field; void method(); }",
+                "-if class " + TestClass.class.getTypeName() + " { protected int field; }",
+                "-keep class " + Unused1.class.getTypeName(),
+                "-if class " + TestClass.class.getTypeName() + " { protected void method(); }",
+                "-keep class " + Unused2.class.getTypeName(),
+                "-allowaccessmodification")
+            .compile()
+            .inspector();
+
+    assertTrue(inspector.clazz(TestClass.class).isPublic());
+    assertTrue(inspector.clazz(TestClass.class).uniqueMethodWithName("method").isPublic());
+    assertTrue(inspector.clazz(TestClass.class).uniqueFieldWithName("field").isPublic());
+
+    assertThat(inspector.clazz(Unused1.class), isPresent());
+    assertThat(inspector.clazz(Unused2.class), isPresent());
+  }
+
+  protected static class TestClass {
+
+    protected int field = 42;
+
+    protected void method() {}
+  }
+
+  static class Unused1 {}
+
+  static class Unused2 {}
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 13e6232..0da4787 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -37,6 +37,11 @@
   }
 
   @Override
+  public FieldSubject uniqueFieldWithName(String name) {
+    return new AbsentFieldSubject();
+  }
+
+  @Override
   public boolean isAbstract() {
     return false;
   }
@@ -47,6 +52,11 @@
   }
 
   @Override
+  public boolean isPublic() {
+    return false;
+  }
+
+  @Override
   public DexClass getDexClass() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index daf64de..1c9fe9f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -70,6 +70,8 @@
 
   public abstract FieldSubject field(String type, String name);
 
+  public abstract FieldSubject uniqueFieldWithName(String name);
+
   public FoundClassSubject asFoundClassSubject() {
     return null;
   }
@@ -78,6 +80,8 @@
 
   public abstract boolean isAnnotation();
 
+  public abstract boolean isPublic();
+
   public String dumpMethods() {
     StringBuilder dump = new StringBuilder();
     forAllMethods(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ed58328..1167d8f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -144,6 +143,18 @@
   }
 
   @Override
+  public FieldSubject uniqueFieldWithName(String name) {
+    FieldSubject fieldSubject = null;
+    for (FoundFieldSubject candidate : allFields()) {
+      if (candidate.getOriginalName().equals(name)) {
+        assert fieldSubject == null;
+        fieldSubject = candidate;
+      }
+    }
+    return fieldSubject != null ? fieldSubject : new AbsentFieldSubject();
+  }
+
+  @Override
   public FoundClassSubject asFoundClassSubject() {
     return this;
   }
@@ -154,6 +165,11 @@
   }
 
   @Override
+  public boolean isPublic() {
+    return dexClass.accessFlags.isPublic();
+  }
+
+  @Override
   public boolean isAnnotation() {
     return dexClass.accessFlags.isAnnotation();
   }