| // 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.keepclassmembers.b115867670; |
| |
| 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.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| |
| import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.google.common.collect.ImmutableList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.function.Consumer; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @interface JsonClass {} |
| |
| @JsonClass |
| class Foo { |
| |
| final Interaction[] interactions; |
| |
| public Foo(Interaction[] interactions) { |
| this.interactions = interactions; |
| } |
| |
| @JsonClass |
| static class Interaction { |
| final Request request; |
| |
| Interaction(Request request) { |
| this.request = request; |
| } |
| } |
| |
| @JsonClass |
| static class Request { |
| final String path; |
| |
| Request(String path) { |
| this.path = path; |
| } |
| } |
| } |
| |
| class Main { |
| |
| public static void main(String[] args) { |
| System.out.println(Foo.class); |
| } |
| } |
| |
| @RunWith(Parameterized.class) |
| public class B115867670 extends ProguardCompatibilityTestBase { |
| private final String pkg = getClass().getPackage().getName(); |
| private final Shrinker shrinker; |
| private static final List<Class<?>> CLASSES = |
| ImmutableList.of( |
| JsonClass.class, Foo.class, Foo.Interaction.class, Foo.Request.class, Main.class); |
| |
| @Parameters(name = "shrinker: {0}") |
| public static Collection<Object> data() { |
| return ImmutableList.of( |
| Shrinker.PROGUARD6, Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF, Shrinker.R8, Shrinker.R8_CF); |
| } |
| |
| public B115867670(Shrinker shrinker) { |
| this.shrinker = shrinker; |
| } |
| |
| public void runTest(String additionalKeepRules, Consumer<CodeInspector> inspection) |
| throws Exception { |
| ImmutableList.Builder<String> builder = ImmutableList.builder(); |
| builder |
| .add("-keep @interface **.JsonClass") |
| .add("-keep class **.Main {") |
| .add(" public static void main(java.lang.String[]);") |
| .add("}") |
| .add(additionalKeepRules); |
| CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, builder.build()); |
| inspection.accept(inspector); |
| } |
| |
| private void checkNoKeepClassMembers(CodeInspector inspector) { |
| assertThat(inspector.clazz(Main.class), isPresent()); |
| assertThat(inspector.clazz(Foo.class), isPresent()); |
| assertThat(inspector.clazz(Foo.Interaction.class), not(isPresent())); |
| assertThat(inspector.clazz(Foo.Request.class), not(isPresent())); |
| } |
| |
| private void checkKeepClassMembers(CodeInspector inspector) { |
| assertThat(inspector.clazz(Main.class), isPresent()); |
| for (Class clazz : new Class[] {Foo.class, Foo.Interaction.class, Foo.Request.class}) { |
| ClassSubject cls = inspector.clazz(clazz); |
| assertThat(cls, isPresent()); |
| assertFalse("Class " + clazz.getSimpleName() + " should not be abstract", cls.isAbstract()); |
| assertEquals(1, cls.asFoundClassSubject().allFields().size()); |
| cls.forAllFields(field -> assertThat(field, not(isRenamed()))); |
| } |
| } |
| |
| private void checkKeepClassMembersRenamed(CodeInspector inspector) { |
| assertThat(inspector.clazz(Main.class), isPresent()); |
| for (Class clazz : new Class[] {Foo.class, Foo.Interaction.class, Foo.Request.class}) { |
| ClassSubject cls = inspector.clazz(clazz); |
| assertThat(cls, isPresent()); |
| assertThat(cls, isRenamed()); |
| assertFalse("Class " + clazz.getSimpleName() + " should not be abstract", cls.isAbstract()); |
| assertEquals(1, cls.asFoundClassSubject().allFields().size()); |
| cls.forAllFields(field -> assertThat(field, isRenamed())); |
| } |
| } |
| |
| @Test |
| public void testNoDependentRules() throws Exception { |
| runTest("", this::checkNoKeepClassMembers); |
| } |
| |
| @Test |
| public void testDependentWithKeepClass() throws Exception { |
| runTest( |
| "-keep @" + pkg + ".JsonClass class ** { <fields>; }", |
| this::checkKeepClassMembers); |
| } |
| |
| @Test |
| public void testDependentWithKeepClassAllowObfuscation() throws Exception { |
| runTest( |
| "-keep,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }", |
| this::checkKeepClassMembersRenamed); |
| } |
| |
| @Test |
| public void testDependentWithKeepClassMembers() throws Exception { |
| runTest( |
| // In full mode, keepclassmembers does not imply the class is instantiated. |
| addFullModeKeepClassIfReferenced( |
| "-keepclassmembers @" + pkg + ".JsonClass class ** { <fields>; }"), |
| this::checkKeepClassMembers); |
| } |
| |
| @Test |
| public void testDependentWithKeepClassMembersAllowObfuscation() throws Exception { |
| runTest( |
| // In full mode, keepclassmembers does not imply the class is instantiated. |
| addFullModeKeepClassIfReferenced( |
| "-keepclassmembers,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }"), |
| this::checkKeepClassMembersRenamed); |
| } |
| |
| @Test |
| public void testDependentWithIfKeepClassMembers() throws Exception { |
| runTest( |
| // In full mode, keepclassmembers does not imply the class is instantiated. |
| addFullModeKeepClassIfReferenced( |
| "-if @" + pkg + ".JsonClass class * -keepclassmembers class <1> { <fields>; }"), |
| this::checkKeepClassMembers); |
| } |
| |
| @Test |
| public void testDependentWithIfKeepClassMembersAllowObfuscation() throws Exception { |
| runTest( |
| // In full mode, keepclassmembers does not imply the class is instantiated. |
| addFullModeKeepClassIfReferenced( |
| "-if @" |
| + pkg |
| + ".JsonClass class * -keepclassmembers,allowobfuscation class <1> { <fields>; }"), |
| this::checkKeepClassMembersRenamed); |
| } |
| |
| @Test |
| public void testDependentWithIfKeep() throws Exception { |
| runTest( |
| addFullModeKeepAllAttributes( |
| "-if @" + pkg + ".JsonClass class * -keep class <1> { <fields>; }"), |
| this::checkKeepClassMembers); |
| } |
| |
| @Test |
| public void testDependentWithIfKeepAllowObfuscation() throws Exception { |
| runTest( |
| addFullModeKeepAllAttributes( |
| "-if @" + pkg + ".JsonClass class * -keep,allowobfuscation class <1> { <fields>; }"), |
| this::checkKeepClassMembersRenamed); |
| } |
| |
| // In full mode we need to explicitly keep the class (mark instantiated) if it is referenced. |
| String addFullModeKeepClassIfReferenced(String rules) { |
| return shrinker.isFullModeR8() |
| ? addFullModeKeepAllAttributes(rules |
| + "\n-if @" |
| + JsonClass.class.getTypeName() |
| + " class * -keep,allowobfuscation class <1>") |
| : rules; |
| } |
| |
| // TODO(b/132318609): Keep the attributes. Potentially remove this once resolved. |
| String addFullModeKeepAllAttributes(String rules) { |
| return shrinker.isFullModeR8() |
| ? rules + "\n-keepattributes *" |
| : rules; |
| } |
| } |