| // Copyright (c) 2017, 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.forceproguardcompatibility; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.CompatProguardCommandBuilder; |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.R8Command; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.invokesuper.Consumer; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.shaking.ProguardClassNameList; |
| import com.android.tools.r8.shaking.ProguardConfiguration; |
| import com.android.tools.r8.shaking.ProguardConfigurationParser; |
| import com.android.tools.r8.shaking.ProguardConfigurationRule; |
| import com.android.tools.r8.shaking.ProguardKeepAttributes; |
| import com.android.tools.r8.shaking.ProguardKeepRule; |
| import com.android.tools.r8.shaking.ProguardKeepRuleType; |
| import com.android.tools.r8.shaking.ProguardMemberRule; |
| import com.android.tools.r8.shaking.ProguardMemberType; |
| import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface; |
| import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.InterfaceWithDefaultMethods; |
| import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.TestClass; |
| import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.DefaultDiagnosticsHandler; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.FieldSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; |
| import it.unimi.dsi.fastutil.objects.Object2BooleanMap; |
| import java.io.File; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class ForceProguardCompatibilityTest extends TestBase { |
| |
| private Backend backend; |
| |
| @Parameterized.Parameters(name = "Backend: {0}") |
| public static Collection<Backend> data() { |
| return Arrays.asList(Backend.values()); |
| } |
| |
| public ForceProguardCompatibilityTest(Backend backend) { |
| this.backend = backend; |
| } |
| |
| private void test(Class mainClass, Class mentionedClass, boolean forceProguardCompatibility) |
| throws Exception { |
| String proguardConfig = keepMainProguardConfiguration(mainClass, true, false); |
| CodeInspector inspector = |
| new CodeInspector( |
| compileWithR8( |
| readClasses(ImmutableList.of(mainClass, mentionedClass)), |
| proguardConfig, |
| options -> options.forceProguardCompatibility = forceProguardCompatibility, |
| backend)); |
| assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent()); |
| ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClass)); |
| assertTrue(clazz.isPresent()); |
| MethodSubject defaultInitializer = clazz.method(MethodSignature.initializer(new String[]{})); |
| assertEquals(forceProguardCompatibility, defaultInitializer.isPresent()); |
| } |
| |
| @Test |
| public void testKeepDefaultInitializer() throws Exception { |
| test(TestMain.class, TestMain.MentionedClass.class, true); |
| test(TestMain.class, TestMain.MentionedClass.class, false); |
| } |
| |
| @Test |
| public void testKeepDefaultInitializerArrayType() throws Exception { |
| test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, true); |
| test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, false); |
| } |
| |
| private void runAnnotationsTest(boolean forceProguardCompatibility, boolean keepAnnotations) |
| throws Exception { |
| R8Command.Builder builder = new CompatProguardCommandBuilder(forceProguardCompatibility); |
| // Add application classes including the annotation class. |
| Class mainClass = TestMain.class; |
| Class mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class; |
| Class annotationClass = TestAnnotation.class; |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestMain.MentionedClass.class)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mentionedClassWithAnnotations)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(annotationClass)); |
| // Keep main class and the annotation class. |
| builder.addProguardConfiguration( |
| ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown()); |
| builder.addProguardConfiguration( |
| ImmutableList.of("-keep class " + annotationClass.getCanonicalName() + " { }"), |
| Origin.unknown()); |
| if (keepAnnotations) { |
| builder.addProguardConfiguration(ImmutableList.of("-keepattributes *Annotation*"), |
| Origin.unknown()); |
| } |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent()); |
| ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClassWithAnnotations)); |
| assertTrue(clazz.isPresent()); |
| |
| // The test contains only a member class so the enclosing-method attribute will be null. |
| assertEquals( |
| forceProguardCompatibility, |
| !clazz.getDexClass().getInnerClasses().isEmpty()); |
| assertEquals(forceProguardCompatibility || keepAnnotations, |
| clazz.annotation(annotationClass.getCanonicalName()).isPresent()); |
| } |
| |
| @Test |
| public void testAnnotations() throws Exception { |
| runAnnotationsTest(true, true); |
| runAnnotationsTest(true, false); |
| runAnnotationsTest(false, true); |
| runAnnotationsTest(false, false); |
| } |
| |
| private void runDefaultConstructorTest(boolean forceProguardCompatibility, |
| Class<?> testClass, boolean hasDefaultConstructor) throws Exception { |
| CompatProguardCommandBuilder builder = |
| new CompatProguardCommandBuilder(forceProguardCompatibility); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(testClass)); |
| List<String> proguardConfig = ImmutableList.of( |
| "-keep class " + testClass.getCanonicalName() + " {", |
| " public void method();", |
| "}"); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass)); |
| assertTrue(clazz.isPresent()); |
| assertEquals(forceProguardCompatibility && hasDefaultConstructor, |
| clazz.init(ImmutableList.of()).isPresent()); |
| |
| // Check the Proguard compatibility rules generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| if (forceProguardCompatibility && hasDefaultConstructor) { |
| assertEquals(1, configuration.getRules().size()); |
| ProguardClassNameList classNames = configuration.getRules().get(0).getClassNames(); |
| assertEquals(1, classNames.size()); |
| assertEquals(testClass.getCanonicalName(), |
| classNames.asSpecificDexTypes().get(0).toSourceString()); |
| List<ProguardMemberRule> memberRules = configuration.getRules().get(0).getMemberRules(); |
| assertEquals(1, memberRules.size()); |
| assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType()); |
| } else { |
| assertEquals(0, configuration.getRules().size()); |
| } |
| |
| if (isRunProguard()) { |
| Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath(); |
| Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath(); |
| Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath(); |
| FileUtils.writeTextFile(proguardConfigFile, proguardConfig); |
| ToolHelper.runProguard(jarTestClasses(testClass), |
| proguardedJar, proguardConfigFile, proguardMapFile); |
| } |
| } |
| |
| @Test |
| public void testDefaultConstructor() throws Exception { |
| runDefaultConstructorTest(true, TestClassWithDefaultConstructor.class, true); |
| runDefaultConstructorTest(true, TestClassWithoutDefaultConstructor.class, false); |
| runDefaultConstructorTest(false, TestClassWithDefaultConstructor.class, true); |
| runDefaultConstructorTest(false, TestClassWithoutDefaultConstructor.class, false); |
| } |
| |
| public void testCheckCast(boolean forceProguardCompatibility, Class mainClass, |
| Class instantiatedClass, boolean containsCheckCast) |
| throws Exception { |
| R8Command.Builder builder = new CompatProguardCommandBuilder(forceProguardCompatibility); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(instantiatedClass)); |
| List<String> proguardConfig = ImmutableList.of( |
| "-keep class " + mainClass.getCanonicalName() + " {", |
| " public static void main(java.lang.String[]);", |
| "}", |
| "-dontobfuscate"); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent()); |
| ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(instantiatedClass)); |
| assertEquals(containsCheckCast, clazz.isPresent()); |
| assertEquals(containsCheckCast, clazz.isPresent()); |
| if (clazz.isPresent()) { |
| assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract()); |
| } |
| |
| if (isRunProguard()) { |
| Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath(); |
| Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath(); |
| FileUtils.writeTextFile(proguardConfigFile, proguardConfig); |
| ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)), |
| proguardedJar, proguardConfigFile, null); |
| CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar)); |
| assertTrue(proguardInspector.clazz(mainClass).isPresent()); |
| assertEquals( |
| containsCheckCast, proguardInspector.clazz(instantiatedClass).isPresent()); |
| } |
| } |
| |
| @Test |
| public void checkCastTest() throws Exception { |
| testCheckCast(true, TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true); |
| testCheckCast( |
| true, TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false); |
| testCheckCast( |
| false, TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true); |
| testCheckCast( |
| false, TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false); |
| } |
| |
| public void testClassForName( |
| boolean forceProguardCompatibility, boolean allowObfuscation) throws Exception { |
| CompatProguardCommandBuilder builder = |
| new CompatProguardCommandBuilder(forceProguardCompatibility); |
| Class mainClass = TestMainWithClassForName.class; |
| Class forNameClass1 = TestClassWithDefaultConstructor.class; |
| Class forNameClass2 = TestClassWithoutDefaultConstructor.class; |
| List<Class> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(forNameClass1)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(forNameClass2)); |
| ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder(); |
| proguardConfigurationBuilder.add( |
| "-keep class " + mainClass.getCanonicalName() + " {", |
| " <init>();", // Add <init>() so it does not become a compatibility rule below. |
| " public static void main(java.lang.String[]);", |
| "}"); |
| if (!allowObfuscation) { |
| proguardConfigurationBuilder.add("-dontobfuscate"); |
| } |
| List<String> proguardConfig = proguardConfigurationBuilder.build(); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| if (allowObfuscation) { |
| builder.setProguardMapOutputPath(temp.newFile().toPath()); |
| } |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent()); |
| forNameClasses.forEach(clazz -> { |
| ClassSubject subject = inspector.clazz(getJavacGeneratedClassName(clazz)); |
| assertTrue(subject.isPresent()); |
| assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed()); |
| }); |
| |
| // Check the Proguard compatibility rules generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| if (forceProguardCompatibility) { |
| List<ProguardConfigurationRule> rules = configuration.getRules(); |
| assertEquals(2, rules.size()); |
| for (ProguardConfigurationRule r : rules) { |
| assertTrue(r instanceof ProguardKeepRule); |
| ProguardKeepRule rule = (ProguardKeepRule) r; |
| assertEquals(ProguardKeepRuleType.KEEP, rule.getType()); |
| assertTrue(rule.getModifiers().allowsObfuscation); |
| assertTrue(rule.getModifiers().allowsOptimization); |
| List<ProguardMemberRule> memberRules = rule.getMemberRules(); |
| ProguardClassNameList classNames = rule.getClassNames(); |
| assertEquals(1, classNames.size()); |
| DexType type = classNames.asSpecificDexTypes().get(0); |
| if (type.toSourceString().equals(forNameClass1.getCanonicalName())) { |
| assertEquals(1, memberRules.size()); |
| assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType()); |
| } else { |
| assertTrue(type.toSourceString().equals(forNameClass2.getCanonicalName())); |
| // During parsing we add in the default constructor if there are otherwise no single |
| // member rule. |
| assertEquals(1, memberRules.size()); |
| assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType()); |
| } |
| } |
| } else { |
| assertEquals(0, configuration.getRules().size()); |
| } |
| |
| if (isRunProguard()) { |
| Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath(); |
| Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath(); |
| Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath(); |
| FileUtils.writeTextFile(proguardConfigFile, proguardConfig); |
| ToolHelper.runProguard(jarTestClasses( |
| ImmutableList.of(mainClass, forNameClass1, forNameClass2)), |
| proguardedJar, proguardConfigFile, proguardMapFile); |
| CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile); |
| assertEquals(3, proguardedInspector.allClasses().size()); |
| assertTrue(proguardedInspector.clazz(mainClass).isPresent()); |
| for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) { |
| assertTrue(proguardedInspector.clazz(clazz).isPresent()); |
| assertEquals(allowObfuscation, proguardedInspector.clazz(clazz).isRenamed()); |
| } |
| } |
| } |
| |
| @Test |
| public void classForNameTest() throws Exception { |
| testClassForName(true, false); |
| testClassForName(false, false); |
| testClassForName(true, true); |
| testClassForName(false, true); |
| } |
| |
| public void testClassGetMembers( |
| boolean forceProguardCompatibility, boolean allowObfuscation) throws Exception { |
| CompatProguardCommandBuilder builder = |
| new CompatProguardCommandBuilder(forceProguardCompatibility); |
| Class mainClass = TestMainWithGetMembers.class; |
| Class withMemberClass = TestClassWithMembers.class; |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(withMemberClass)); |
| ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder(); |
| proguardConfigurationBuilder.add( |
| "-keep class " + mainClass.getCanonicalName() + " {", |
| " <init>();", // Add <init>() so it does not become a compatibility rule below. |
| " public static void main(java.lang.String[]);", |
| "}"); |
| if (!allowObfuscation) { |
| proguardConfigurationBuilder.add("-dontobfuscate"); |
| } |
| List<String> proguardConfig = proguardConfigurationBuilder.build(); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| if (allowObfuscation) { |
| builder.setProguardMapOutputPath(temp.newFile().toPath()); |
| } |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent()); |
| ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withMemberClass)); |
| // Due to the direct usage of .class |
| assertTrue(classSubject.isPresent()); |
| assertEquals(allowObfuscation, classSubject.isRenamed()); |
| FieldSubject foo = classSubject.field("java.lang.String", "foo"); |
| assertTrue(foo.isPresent()); |
| assertEquals(foo.isPresent() && allowObfuscation, foo.isRenamed()); |
| MethodSubject bar = |
| classSubject.method("java.lang.String", "bar", ImmutableList.of("java.lang.String")); |
| assertTrue(bar.isPresent()); |
| assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed()); |
| |
| // Check the Proguard compatibility rules generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| if (forceProguardCompatibility) { |
| List<ProguardConfigurationRule> rules = configuration.getRules(); |
| assertEquals(2, rules.size()); |
| for (ProguardConfigurationRule r : rules) { |
| assertTrue(r instanceof ProguardKeepRule); |
| ProguardKeepRule rule = (ProguardKeepRule) r; |
| assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType()); |
| assertTrue(rule.getModifiers().allowsObfuscation); |
| assertTrue(rule.getModifiers().allowsOptimization); |
| List<ProguardMemberRule> memberRules = rule.getMemberRules(); |
| ProguardClassNameList classNames = rule.getClassNames(); |
| assertEquals(1, classNames.size()); |
| DexType type = classNames.asSpecificDexTypes().get(0); |
| assertEquals(withMemberClass.getCanonicalName(), type.toSourceString()); |
| assertEquals(1, memberRules.size()); |
| ProguardMemberRule memberRule = memberRules.iterator().next(); |
| if (memberRule.getRuleType() == ProguardMemberType.FIELD) { |
| assertTrue(memberRule.getName().matches("foo")); |
| } else { |
| assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType()); |
| assertTrue(memberRule.getName().matches("bar")); |
| } |
| } |
| } else { |
| assertEquals(0, configuration.getRules().size()); |
| } |
| |
| if (isRunProguard()) { |
| Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath(); |
| Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath(); |
| Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath(); |
| FileUtils.writeTextFile(proguardConfigFile, proguardConfig); |
| ToolHelper.runProguard(jarTestClasses( |
| ImmutableList.of(mainClass, withMemberClass)), |
| proguardedJar, proguardConfigFile, proguardMapFile); |
| CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile); |
| assertEquals(2, proguardedInspector.allClasses().size()); |
| assertTrue(proguardedInspector.clazz(mainClass).isPresent()); |
| classSubject = proguardedInspector.clazz(withMemberClass); |
| assertTrue(classSubject.isPresent()); |
| assertEquals(allowObfuscation, classSubject.isRenamed()); |
| foo = classSubject.field("java.lang.String", "foo"); |
| assertTrue(foo.isPresent()); |
| assertEquals(allowObfuscation, foo.isRenamed()); |
| bar = classSubject.method("java.lang.String", "bar", ImmutableList.of("java.lang.String")); |
| assertTrue(bar.isPresent()); |
| assertEquals(allowObfuscation, bar.isRenamed()); |
| } |
| } |
| |
| @Test |
| public void classGetMembersTest() throws Exception { |
| testClassGetMembers(true, false); |
| testClassGetMembers(false, false); |
| testClassGetMembers(true, true); |
| testClassGetMembers(false, true); |
| } |
| |
| public void testAtomicFieldUpdaters( |
| boolean forceProguardCompatibility, boolean allowObfuscation) throws Exception { |
| CompatProguardCommandBuilder builder = |
| new CompatProguardCommandBuilder(forceProguardCompatibility); |
| Class mainClass = TestMainWithAtomicFieldUpdater.class; |
| Class withVolatileFields = TestClassWithVolatileFields.class; |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass)); |
| builder.addProgramFiles(ToolHelper.getClassFileForTestClass(withVolatileFields)); |
| ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder(); |
| proguardConfigurationBuilder.add( |
| "-keep class " + mainClass.getCanonicalName() + " {", |
| " <init>();", // Add <init>() so it does not become a compatibility rule below. |
| " public static void main(java.lang.String[]);", |
| "}"); |
| if (!allowObfuscation) { |
| proguardConfigurationBuilder.add("-dontobfuscate"); |
| } |
| List<String> proguardConfig = proguardConfigurationBuilder.build(); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| if (allowObfuscation) { |
| builder.setProguardMapOutputPath(temp.newFile().toPath()); |
| } |
| |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build())); |
| assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent()); |
| ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withVolatileFields)); |
| // Due to the direct usage of .class |
| assertTrue(classSubject.isPresent()); |
| assertEquals(allowObfuscation, classSubject.isRenamed()); |
| FieldSubject f = classSubject.field("int", "intField"); |
| assertTrue(f.isPresent()); |
| assertEquals(f.isPresent() && allowObfuscation, f.isRenamed()); |
| f = classSubject.field("long", "longField"); |
| assertTrue(f.isPresent()); |
| assertEquals(f.isPresent() && allowObfuscation, f.isRenamed()); |
| f = classSubject.field("java.lang.Object", "objField"); |
| assertTrue(f.isPresent()); |
| assertEquals(f.isPresent() && allowObfuscation, f.isRenamed()); |
| |
| // Check the Proguard compatibility rules generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| if (forceProguardCompatibility) { |
| List<ProguardConfigurationRule> rules = configuration.getRules(); |
| assertEquals(3, rules.size()); |
| Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>(); |
| for (ProguardConfigurationRule r : rules) { |
| assertTrue(r instanceof ProguardKeepRule); |
| ProguardKeepRule rule = (ProguardKeepRule) r; |
| assertEquals(ProguardKeepRuleType.KEEP, rule.getType()); |
| assertTrue(rule.getModifiers().allowsObfuscation); |
| assertTrue(rule.getModifiers().allowsOptimization); |
| List<ProguardMemberRule> memberRules = rule.getMemberRules(); |
| ProguardClassNameList classNames = rule.getClassNames(); |
| assertEquals(1, classNames.size()); |
| DexType type = classNames.asSpecificDexTypes().get(0); |
| assertEquals(withVolatileFields.getCanonicalName(), type.toSourceString()); |
| assertEquals(1, memberRules.size()); |
| ProguardMemberRule memberRule = memberRules.iterator().next(); |
| assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType()); |
| keptFields.put(memberRule.getName().toString(), true); |
| } |
| assertEquals(3, keptFields.size()); |
| assertTrue(keptFields.containsKey("intField")); |
| assertTrue(keptFields.containsKey("longField")); |
| assertTrue(keptFields.containsKey("objField")); |
| } else { |
| assertEquals(0, configuration.getRules().size()); |
| } |
| |
| if (isRunProguard()) { |
| Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath(); |
| Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath(); |
| Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath(); |
| FileUtils.writeTextFile(proguardConfigFile, proguardConfig); |
| ToolHelper.runProguard(jarTestClasses( |
| ImmutableList.of(mainClass, withVolatileFields)), |
| proguardedJar, proguardConfigFile, proguardMapFile); |
| CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile); |
| assertEquals(2, proguardedInspector.allClasses().size()); |
| assertTrue(proguardedInspector.clazz(mainClass).isPresent()); |
| classSubject = proguardedInspector.clazz(withVolatileFields); |
| assertTrue(classSubject.isPresent()); |
| assertEquals(allowObfuscation, classSubject.isRenamed()); |
| f = classSubject.field("int", "intField"); |
| assertTrue(f.isPresent()); |
| assertEquals(allowObfuscation, f.isRenamed()); |
| f = classSubject.field("long", "longField"); |
| assertTrue(f.isPresent()); |
| assertEquals(allowObfuscation, f.isRenamed()); |
| f = classSubject.field("java.lang.Object", "objField"); |
| assertTrue(f.isPresent()); |
| assertEquals(allowObfuscation, f.isRenamed()); |
| } |
| } |
| |
| @Test |
| public void atomicFieldUpdaterTest() throws Exception { |
| testAtomicFieldUpdaters(true, false); |
| testAtomicFieldUpdaters(false, false); |
| testAtomicFieldUpdaters(true, true); |
| testAtomicFieldUpdaters(false, true); |
| } |
| |
| public void testKeepAttributes(boolean forceProguardCompatibility, |
| boolean innerClasses, boolean enclosingMethod) throws Exception { |
| CompatProguardCommandBuilder builder = |
| new CompatProguardCommandBuilder(forceProguardCompatibility); |
| Class mainClass = TestKeepAttributes.class; |
| builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())); |
| ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder(); |
| String keepAttributes = ""; |
| if (innerClasses || enclosingMethod) { |
| List<String> attributes = new ArrayList<>(); |
| if (innerClasses) { |
| attributes.add(ProguardKeepAttributes.INNER_CLASSES); |
| } |
| if (enclosingMethod) { |
| attributes.add(ProguardKeepAttributes.ENCLOSING_METHOD); |
| } |
| keepAttributes = "-keepattributes " + String.join(",", attributes); |
| } |
| proguardConfigurationBuilder.add( |
| "-keep class " + mainClass.getCanonicalName() + " {", |
| " <init>();", // Add <init>() so it does not become a compatibility rule below. |
| " public static void main(java.lang.String[]);", |
| "}", |
| keepAttributes); |
| List<String> proguardConfig = proguardConfigurationBuilder.build(); |
| builder.addProguardConfiguration(proguardConfig, Origin.unknown()); |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| |
| AndroidApp app; |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| try { |
| app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false); |
| } catch (CompilationFailedException e) { |
| assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod)); |
| return; |
| } |
| CodeInspector inspector = new CodeInspector(app); |
| assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent()); |
| String result; |
| if (backend == Backend.DEX) { |
| result = runOnArt(app, mainClass); |
| } else { |
| assert backend == Backend.CF; |
| result = runOnJava(app, mainClass); |
| } |
| assertEquals(innerClasses || enclosingMethod ? "1" : "0", result); |
| |
| // Check the Proguard compatibility configuration generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| System.out.println(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| assertEquals(0, configuration.getRules().size()); |
| if (innerClasses ^ enclosingMethod) { |
| assertTrue(configuration.getKeepAttributes().innerClasses); |
| assertTrue(configuration.getKeepAttributes().enclosingMethod); |
| } else { |
| assertFalse(configuration.getKeepAttributes().innerClasses); |
| assertFalse(configuration.getKeepAttributes().enclosingMethod); |
| } |
| } |
| |
| @Test |
| public void keepAttributesTest() throws Exception { |
| testKeepAttributes(true, false, false); |
| testKeepAttributes(true, true, false); |
| testKeepAttributes(true, false, true); |
| testKeepAttributes(true, true, true); |
| testKeepAttributes(false, false, false); |
| testKeepAttributes(false, true, false); |
| testKeepAttributes(false, false, true); |
| testKeepAttributes(false, true, true); |
| } |
| |
| private void runKeepDefaultMethodsTest( |
| List<String> additionalKeepRules, |
| Consumer<CodeInspector> inspection, |
| Consumer<ProguardConfiguration> compatInspection) throws Exception { |
| Class mainClass = TestClass.class; |
| CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(); |
| builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())); |
| builder.addProguardConfiguration(ImmutableList.of( |
| "-keep class " + mainClass.getCanonicalName() + "{", |
| " public <init>();", |
| " public static void main(java.lang.String[]);", |
| "}", |
| "-dontobfuscate"), |
| Origin.unknown()); |
| builder.addProguardConfiguration(additionalKeepRules, Origin.unknown()); |
| builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend)); |
| if (backend == Backend.DEX) { |
| builder.setMinApiLevel(AndroidApiLevel.O.getLevel()); |
| } |
| Path proguardCompatibilityRules = temp.newFile().toPath(); |
| builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules); |
| AndroidApp app = |
| ToolHelper.runR8( |
| builder.build(), |
| o -> { |
| o.enableClassInlining = false; |
| |
| // Prevent InterfaceWithDefaultMethods from being merged into |
| // ClassImplementingInterface. |
| o.enableVerticalClassMerging = false; |
| }); |
| inspection.accept(new CodeInspector(app)); |
| // Check the Proguard compatibility configuration generated. |
| ProguardConfigurationParser parser = |
| new ProguardConfigurationParser(new DexItemFactory(), |
| new Reporter(new DefaultDiagnosticsHandler())); |
| parser.parse(proguardCompatibilityRules); |
| ProguardConfiguration configuration = parser.getConfigRawForTesting(); |
| compatInspection.accept(configuration); |
| } |
| |
| private void noCompatibilityRules(ProguardConfiguration configuration) { |
| assertEquals(0, configuration.getRules().size()); |
| } |
| |
| private void defaultMethodKept(CodeInspector inspector) { |
| ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class); |
| assertTrue(clazz.isPresent()); |
| MethodSubject method = clazz.method("int", "method", ImmutableList.of()); |
| assertTrue(method.isPresent()); |
| assertFalse(method.isAbstract()); |
| } |
| |
| private void defaultMethodCompatibilityRules(ProguardConfiguration configuration) { |
| assertEquals(1, configuration.getRules().size()); |
| ProguardConfigurationRule rule = configuration.getRules().get(0); |
| List<ProguardMemberRule> memberRules = rule.getMemberRules(); |
| ProguardClassNameList classNames = rule.getClassNames(); |
| assertEquals(1, classNames.size()); |
| DexType type = classNames.asSpecificDexTypes().get(0); |
| assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName()); |
| assertEquals(1, memberRules.size()); |
| ProguardMemberRule memberRule = memberRules.iterator().next(); |
| assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType()); |
| assertTrue(memberRule.getName().matches("method")); |
| assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().intType)); |
| assertEquals(0, memberRule.getArguments().size()); |
| } |
| |
| private void defaultMethod2Kept(CodeInspector inspector) { |
| ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class); |
| assertTrue(clazz.isPresent()); |
| MethodSubject method = |
| clazz.method("void", "method2", ImmutableList.of("java.lang.String", "int")); |
| assertTrue(method.isPresent()); |
| assertFalse(method.isAbstract()); |
| } |
| |
| private void defaultMethod2CompatibilityRules(ProguardConfiguration configuration) { |
| assertEquals(1, configuration.getRules().size()); |
| ProguardConfigurationRule rule = configuration.getRules().get(0); |
| List<ProguardMemberRule> memberRules = rule.getMemberRules(); |
| ProguardClassNameList classNames = rule.getClassNames(); |
| assertEquals(1, classNames.size()); |
| DexType type = classNames.asSpecificDexTypes().get(0); |
| assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName()); |
| assertEquals(1, memberRules.size()); |
| ProguardMemberRule memberRule = memberRules.iterator().next(); |
| assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType()); |
| assertTrue(memberRule.getName().matches("method2")); |
| assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().voidType)); |
| assertEquals(2, memberRule.getArguments().size()); |
| assertTrue( |
| memberRule.getArguments().get(0).matches(configuration.getDexItemFactory().stringType)); |
| assertTrue(memberRule.getArguments().get(1).matches(configuration.getDexItemFactory().intType)); |
| } |
| |
| @Test |
| public void keepDefaultMethodsTest() throws Exception { |
| runKeepDefaultMethodsTest(ImmutableList.of( |
| "-keep interface " + InterfaceWithDefaultMethods.class.getCanonicalName() + "{", |
| " public int method();", |
| "}" |
| ), this::defaultMethodKept, this::noCompatibilityRules); |
| runKeepDefaultMethodsTest(ImmutableList.of( |
| "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{", |
| " <methods>;", |
| "}", |
| "-keep class " + TestClass.class.getCanonicalName() + "{", |
| " public void useInterfaceMethod();", |
| "}" |
| ), this::defaultMethodKept, this::defaultMethodCompatibilityRules); |
| runKeepDefaultMethodsTest(ImmutableList.of( |
| "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{", |
| " <methods>;", |
| "}", |
| "-keep class " + TestClass.class.getCanonicalName() + "{", |
| " public void useInterfaceMethod2();", |
| "}" |
| ), this::defaultMethod2Kept, this::defaultMethod2CompatibilityRules); |
| } |
| } |