Only mark the class of keepclassmembers instantiated in compatibility mode.
Bug: 119076934
Bug: 132318609
Change-Id: I587af948494a6732369473a4e71c8da3ec2c6c4a
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2501f9f..7582343 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -888,11 +888,10 @@
} else {
assert !deferredAnnotations.containsKey(holder.type);
}
-
- Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
- enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
- // Add all dependent members to the workqueue.
- enqueueRootItems(dependentItems);
+ rootSet.forEachDependentStaticMember(holder, appView, this::enqueueRootItem);
+ if (forceProguardCompatibility) {
+ enqueueHolderIfDependentNonStaticMember(holder, rootSet.getDependentItems(holder));
+ }
}
}
@@ -1052,8 +1051,8 @@
transitionMethodsForInstantiatedClass(clazz.type);
// For all instance fields visible from the class, mark them live if we have seen a read.
transitionFieldsForInstantiatedClass(clazz.type);
- // Add all dependent members to the workqueue.
- enqueueRootItems(rootSet.getDependentItems(clazz));
+ // Add all dependent instance members to the workqueue.
+ rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueRootItem);
}
/**
@@ -1686,7 +1685,7 @@
// and enqueue it as well. This is -if version of workaround for b/115867670.
consequentRootSet.dependentNoShrinking.forEach(
(precondition, dependentItems) -> {
- if (precondition.isDexType()) {
+ if (precondition.isDexType() && forceProguardCompatibility) {
DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 40152ab..fa8b7ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -57,6 +57,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -1199,6 +1200,30 @@
dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
}
+ public void forEachDependentStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ BiConsumer<DexDefinition, Set<ProguardKeepRule>> fn) {
+ getDependentItems(item).forEach((reference, reasons) -> {
+ DexDefinition definition = appView.definitionFor(reference);
+ if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
+ fn.accept(definition, reasons);
+ }
+ });
+ }
+
+ public void forEachDependentNonStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ BiConsumer<DexDefinition, Set<ProguardKeepRule>> fn) {
+ getDependentItems(item).forEach((reference, reasons) -> {
+ DexDefinition definition = appView.definitionFor(reference);
+ if (definition != null && !definition.isDexClass() && !definition.isStaticMember()) {
+ fn.accept(definition, reasons);
+ }
+ });
+ }
+
public void copy(DexReference original, DexReference rewritten) {
if (noShrinking.containsKey(original)) {
noShrinking.put(rewritten, noShrinking.get(original));
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index d63626a..45b369a 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -257,10 +257,15 @@
return createRunResult(result);
}
+ @Deprecated
public Dex2OatTestRunResult runDex2Oat() throws IOException {
return runDex2Oat(ToolHelper.getDexVm());
}
+ public Dex2OatTestRunResult runDex2Oat(TestRuntime runtime) throws IOException {
+ return runDex2Oat(runtime.asDex().getVm());
+ }
+
public Dex2OatTestRunResult runDex2Oat(DexVm vm) throws IOException {
assert getBackend() == DEX;
Path tmp = state.getNewTempFolder();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
index efd4467..44e3756 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -37,13 +37,16 @@
private static Class<?> BAR_CLASS = CompatKeepClassMemberNamesTest.Bar.class;
private static Collection<Class<?>> CLASSES = ImmutableList.of(MAIN_CLASS, BAR_CLASS);
- private static String EXPLICIT_RULE =
+ private static String KEEP_RULE =
"class "
+ Bar.class.getTypeName()
+ " { static "
+ Bar.class.getTypeName()
+ " instance(); void <init>(); int i; }";
+ private static String KEEP_RULE_NON_STATIC =
+ "class " + Bar.class.getTypeName() + " { void <init>(); int i; }";
+
private static String EXPECTED = StringUtils.lines("42", "null");
@Parameters(name = "{0}")
@@ -84,6 +87,7 @@
}
private static void assertBarGetInstanceIsNotInlined(CodeInspector inspector) {
+ assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithName("instance").isPresent());
assertTrue(
inspector
.clazz(MAIN_CLASS)
@@ -142,6 +146,7 @@
assertTrue(inspector.clazz(BAR_CLASS).isPresent());
assertBarGetInstanceIsNotInlined(inspector);
assertTrue(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+ assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
})
.run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED);
@@ -159,11 +164,60 @@
}
@Test
- @Ignore("b/119076934")
- // TODO(b/119076934): Fails because the compat rule is applied regardless of mode, keeping Bar.
public void testWithMembersRuleFullR8() throws Exception {
- // In full mode for R8 we do *not* expect a -keepclassmembers to cause retention of the class.
- assertBarIsAbsent(buildWithMembersRule(testForR8(parameters.getBackend())).compile());
+ // When a class is only referenced (not instantiated), full mode R8 will only keep the static
+ // members specified by the -keepclassmembers rule.
+ buildWithMembersRule(testForR8(parameters.getBackend()))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
+ assertTrue(inspector.clazz(BAR_CLASS).isPresent());
+ assertBarGetInstanceIsNotInlined(inspector);
+ assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+ assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+ });
+ }
+
+ @Test
+ public void testWithMembersRuleAndKeepBarRuleFullR8() throws Exception {
+ // If we keep the Bar class too, we get the same behavior in full as in PG/Compat.
+ assertMembersRuleCompatResult(
+ buildWithMembersRule(testForR8(parameters.getBackend()))
+ .addKeepClassRules(BAR_CLASS)
+ .compile());
+ }
+
+ // Tests for non-static -keepclassmembers and *no* minification.
+
+ private <
+ C extends BaseCompilerCommand,
+ B extends BaseCompilerCommand.Builder<C, B>,
+ CR extends TestCompileResult<CR, RR>,
+ RR extends TestRunResult<RR>,
+ T extends TestShrinkerBuilder<C, B, CR, RR, T>>
+ T buildWithNonStaticMembersRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) {
+ return builder
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(MAIN_CLASS)
+ .addKeepRules("-keepclassmembers " + KEEP_RULE_NON_STATIC)
+ .noMinification();
+ }
+
+ @Test
+ public void testWithNonStaticMembersRulePG() throws Exception {
+ assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard()).compile());
+ }
+
+ @Test
+ public void testWithNonStaticMembersRuleCompatR8() throws Exception {
+ assertBarIsAbsent(
+ buildWithNonStaticMembersRule(testForR8Compat(parameters.getBackend())).compile());
+ }
+
+ @Test
+ public void testWithNonStaticMembersRuleFullR8() throws Exception {
+ assertBarIsAbsent(buildWithNonStaticMembersRule(testForR8(parameters.getBackend())).compile());
}
// Tests for -keepclassmembers and minification.
@@ -178,7 +232,7 @@
return builder
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
- .addKeepRules("-keepclassmembers " + EXPLICIT_RULE);
+ .addKeepRules("-keepclassmembers " + KEEP_RULE);
}
private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
@@ -221,11 +275,19 @@
}
@Test
- @Ignore("b/119076934")
- // TODO(b/119076934): Fails because the compat rule is applied regardless of mode, keeping Bar.
public void testWithMembersRuleEnableMinificationFullR8() throws Exception {
- assertBarIsAbsent(
- buildWithMembersRuleEnableMinification(testForR8(parameters.getBackend())).compile());
+ // When a class is only referenced (not instantiated), full mode R8 will only keep the static
+ // members specified by the -keepclassmembers rule.
+ buildWithMembersRuleEnableMinification(testForR8(parameters.getBackend()))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
+ assertTrue(inspector.clazz(BAR_CLASS).isPresent());
+ assertBarGetInstanceIsNotInlined(inspector);
+ assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+ assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+ });
}
// Tests for "-keepclassmembers class Bar", i.e, with no members specified.
@@ -325,7 +387,7 @@
return builder
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
- .addKeepRules("-keepclassmembernames " + EXPLICIT_RULE);
+ .addKeepRules("-keepclassmembernames " + KEEP_RULE);
}
private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index e24eb44..c1b4039 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -44,9 +44,12 @@
Class<?> mainClass = genericTypeLive ? MainGenericTypeLive.class : MainGenericTypeNotLive.class;
testForR8(backend)
.minification(minify)
- .addProgramClasses(A.class, GenericType.class, mainClass)
+ .addProgramClasses(GetClassUtil.class, A.class, GenericType.class, mainClass)
.addKeepMainRule(mainClass)
.addKeepRules(
+ "-keep class " + GetClassUtil.class.getTypeName() + " {",
+ " static java.lang.Class getClass(java.lang.Object);",
+ "}",
"-keepclassmembers @" + Marker.class.getTypeName() + " class * {",
" <fields>;",
"}",
@@ -96,17 +99,25 @@
}
+// GetClassUtil is used below to ensure that the types remain instantiated.
+class GetClassUtil {
+
+ public static Class<?> getClass(Object o) {
+ return o.getClass();
+ }
+}
+
class MainGenericTypeNotLive {
public static void main(String[] args) {
- System.out.println(A.class);
+ System.out.println(GetClassUtil.getClass(new A()));
}
}
class MainGenericTypeLive {
public static void main(String[] args) {
- System.out.println(A.class);
- System.out.println(GenericType.class);
+ System.out.println(GetClassUtil.getClass(new A()));
+ System.out.println(GetClassUtil.getClass(new GenericType()));
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
index 3737ea0..f1d0cda 100644
--- a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
@@ -3,63 +3,71 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.b113138046;
-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.assertNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
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.List;
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 NativeMethodTest extends TestBase {
- private void test(List<String> config) throws Exception {
- R8Command.Builder builder = R8Command.builder();
- List<Path> classes = ToolHelper.getClassFilesForTestDirectory(
- ToolHelper.getPackageDirectoryForTestPackage(Outer.class.getPackage()),
- p -> !p.getFileName().toString().startsWith(this.getClass().getSimpleName()));
- builder.addProgramFiles(classes);
- builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- builder.addProguardConfiguration(config, Origin.unknown());
- Path appOut = temp.newFile("out.zip").toPath();
- builder.setOutput(appOut, OutputMode.DexIndexed);
- AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
- options.enableInlining = false;
- });
- Path oatOut = temp.newFile("out.oat").toPath();
- ToolHelper.runDex2Oat(appOut, oatOut);
+ @Parameters(name = "{0}, compat:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+ BooleanUtils.values());
+ }
- CodeInspector inspector = new CodeInspector(processedApp);
+ private final TestParameters parameters;
+ private final boolean compatMode;
+
+ public NativeMethodTest(TestParameters parameters, boolean compatMode) {
+ this.parameters = parameters;
+ this.compatMode = compatMode;
+ }
+
+ private void test(List<String> config, boolean expectedFooPresence) throws Exception {
+ R8TestCompileResult compileResult =
+ (compatMode ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClassesAndInnerClasses(Keep.class, Data.class, Handler.class, Outer.class)
+ .addKeepRules(config)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .compile();
+ compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
+ CodeInspector inspector = compileResult.inspector();
boolean innerFound = false;
for (ClassSubject clazz : inspector.allClasses()) {
innerFound = clazz.getOriginalName().endsWith("Inner");
if (!innerFound) {
continue;
}
- MethodSubject nativeFoo = clazz.method("void", "foo",
- ImmutableList.of(Handler.class.getCanonicalName()));
- assertThat(nativeFoo, isPresent());
- assertThat(nativeFoo, not(isRenamed()));
- DexEncodedMethod method = nativeFoo.getMethod();
- assertTrue(method.accessFlags.isNative());
- assertNull(method.getCode());
+ MethodSubject nativeFoo = clazz.method(
+ "void", "foo", ImmutableList.of(Handler.class.getCanonicalName()));
+ assertEquals(expectedFooPresence, nativeFoo.isPresent());
+ if (expectedFooPresence) {
+ assertThat(nativeFoo, not(isRenamed()));
+ DexEncodedMethod method = nativeFoo.getMethod();
+ assertTrue(method.accessFlags.isNative());
+ assertNull(method.getCode());
+ }
break;
}
assertTrue(innerFound);
@@ -80,7 +88,7 @@
"-printmapping",
"-keepattributes InnerClasses,EnclosingMethod,Signature",
"-allowaccessmodification");
- test(config);
+ test(config, compatMode);
}
@Test
@@ -101,6 +109,6 @@
"-printmapping",
"-keepattributes InnerClasses,EnclosingMethod,Signature",
"-allowaccessmodification");
- test(config);
+ test(config, true);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index fc6738b..5c74e28 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.DataResource;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -49,6 +50,11 @@
|| this == R8_CF;
}
+ public boolean isFullModeR8() {
+ return this == R8
+ || this == R8_CF;
+ }
+
public boolean generatesDex() {
return this == PROGUARD6_THEN_D8
|| this == R8_COMPAT
@@ -192,6 +198,7 @@
Backend backend)
throws Exception {
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+ builder.setProguardMapConsumer(StringConsumer.emptyConsumer());
ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(
ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index 6dc7ac0..bc3c3bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -287,6 +287,18 @@
" java.lang.Object readResolve();",
"}");
+ if (shrinker.isFullModeR8()) {
+ // R8 full mode does not keep the default constructor unless explicitly specified.
+ config = ImmutableList.<String>builder()
+ .addAll(config)
+ .addAll(ImmutableList.of(
+ "-keepclassmembers class * implements java.io.Externalizable {",
+ " public void <init>();",
+ "}"
+ ))
+ .build();
+ }
+
AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_EXTERNALIZABLE, config);
// TODO(b/117302947): Need to update ART binary.
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 9596513..ba67472 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
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 com.android.tools.r8.graph.invokesuper.Consumer;
@@ -69,7 +70,8 @@
@Parameters(name = "shrinker: {0}")
public static Collection<Object> data() {
- return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+ return ImmutableList.of(
+ Shrinker.PROGUARD6, Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF, Shrinker.R8, Shrinker.R8_CF);
}
public B115867670(Shrinker shrinker) {
@@ -101,6 +103,7 @@
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())));
}
@@ -112,6 +115,7 @@
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()));
}
@@ -139,44 +143,71 @@
@Test
public void testDependentWithKeepClassMembers() throws Exception {
runTest(
- "-keepclassmembers @" + pkg + ".JsonClass class ** { <fields>; }",
+ // 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(
- "-keepclassmembers,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }",
+ // 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(
- "-if @" + pkg + ".JsonClass class * -keepclassmembers class <1> { <fields>; }",
+ // 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>; }",
+ + ".JsonClass class * -keepclassmembers,allowobfuscation class <1> { <fields>; }"),
this::checkKeepClassMembersRenamed);
}
@Test
public void testDependentWithIfKeep() throws Exception {
runTest(
- "-if @" + pkg + ".JsonClass class * -keep class <1> { <fields>; }",
+ addFullModeKeepAllAttributes(
+ "-if @" + pkg + ".JsonClass class * -keep class <1> { <fields>; }"),
this::checkKeepClassMembers);
}
@Test
public void testDependentWithIfKeepAllowObfuscation() throws Exception {
runTest(
- "-if @" + pkg + ".JsonClass class * -keep,allowobfuscation class <1> { <fields>; }",
+ 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;
+ }
}