blob: f2f673131b64ed1694106a964e649329365299db [file] [log] [blame]
// 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;
}
}