Prioritize noSideEffects in side-effect analysis of invocations.
Bug: 130804193, 130561746
Change-Id: I2469b4f0988d86f54a2e11042ee6af57f67a023d
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index c1f12a1..bddfc6a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -183,23 +183,18 @@
assert false : "Expected to be able to find the enclosing class of a method definition";
return true;
}
-
boolean targetMayHaveSideEffects;
- if (clazz.isProgramClass()) {
- targetMayHaveSideEffects =
- target.getOptimizationInfo().mayHaveSideEffects()
- && !appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method);
+ if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
+ targetMayHaveSideEffects = false;
+ } else if (clazz.isProgramClass()) {
+ targetMayHaveSideEffects = target.getOptimizationInfo().mayHaveSideEffects();
} else {
+ assert clazz.isLibraryClass();
targetMayHaveSideEffects =
!appView.dexItemFactory().libraryMethodsWithoutSideEffects.contains(target.method);
}
- if (targetMayHaveSideEffects) {
- return true;
- }
-
- // Success, the instruction does not have any side effects.
- return false;
+ return targetMayHaveSideEffects;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index ed1ba571..e659c39 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -164,18 +164,21 @@
}
// Verify that the target method does not have side-effects.
- boolean targetMayHaveSideEffects =
- target.getOptimizationInfo().mayHaveSideEffects()
- && !appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method);
- if (targetMayHaveSideEffects) {
- return true;
+ boolean targetMayHaveSideEffects;
+ if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
+ targetMayHaveSideEffects = false;
+ } else {
+ targetMayHaveSideEffects =
+ target.getOptimizationInfo().mayHaveSideEffects()
+ // Verify that calling the target method won't lead to class initialization.
+ || target.method.holder.classInitializationMayHaveSideEffects(
+ appView.appInfo(),
+ // Types that are a super type of `context` are guaranteed to be initialized
+ // already.
+ type -> appView.isSubtype(context, type).isTrue());
}
- // Verify that calling the target method won't lead to class initialization.
- return target.method.holder.classInitializationMayHaveSideEffects(
- appView.appInfo(),
- // Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue());
+ return targetMayHaveSideEffects;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 04453b9..acfb6da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -121,6 +121,10 @@
@Override
public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+ if (!appView.enableWholeProgramOptimizations()) {
+ return true;
+ }
+
if (appView.options().debug) {
return true;
}
@@ -136,6 +140,25 @@
return false;
}
+ // Find the target and check if the invoke may have side effects.
+ if (appView.appInfo().hasLiveness()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
+ if (target == null) {
+ return true;
+ }
+
+ // Verify that the target method does not have side-effects.
+ boolean targetMayHaveSideEffects;
+ if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
+ targetMayHaveSideEffects = false;
+ } else {
+ targetMayHaveSideEffects = target.getOptimizationInfo().mayHaveSideEffects();
+ }
+
+ return targetMayHaveSideEffects;
+ }
+
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index b1e36a0..f5d6d47 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -206,7 +206,9 @@
}
// TODO(b/130804193): search for all call targets and apply -assumenosideeffects if one of
// call targets has a matching rule?
- DexEncodedMethod definition = current.lookupSingleTarget(appView, callingContext);
+ // TODO(b/130804193): using refined receiver type for InvokeMethodWithReceiver?
+ DexEncodedMethod definition =
+ appView.appInfo().lookupSingleTarget(current.getType(), invokedMethod, callingContext);
ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
boolean invokeReplaced = false;
if (lookup != null) {
diff --git a/src/test/examples/annotationremoval/keep-rules-keep-innerannotation.txt b/src/test/examples/annotationremoval/keep-rules-keep-innerannotation.txt
index ce89fb2..e2f4953 100644
--- a/src/test/examples/annotationremoval/keep-rules-keep-innerannotation.txt
+++ b/src/test/examples/annotationremoval/keep-rules-keep-innerannotation.txt
@@ -14,5 +14,5 @@
-keepattributes InnerClasses,EnclosingMethod
-# allow access modification to enable minification
--allowaccessmodification
+# Examples are too simple and side-effects free, and thus can be easily inlined/pruned.
+-dontoptimize
diff --git a/src/test/examples/annotationremoval/keep-rules.txt b/src/test/examples/annotationremoval/keep-rules.txt
index b5aa38d..c450322 100644
--- a/src/test/examples/annotationremoval/keep-rules.txt
+++ b/src/test/examples/annotationremoval/keep-rules.txt
@@ -8,5 +8,5 @@
public static void main(...);
}
-# allow access modification to enable minification
--allowaccessmodification
+# Examples are too simple and side-effects free, and thus can be easily inlined/pruned.
+-dontoptimize
diff --git a/src/test/examples/naming001/keep-rules-105.txt b/src/test/examples/naming001/keep-rules-105.txt
index 634edb6..011596b 100644
--- a/src/test/examples/naming001/keep-rules-105.txt
+++ b/src/test/examples/naming001/keep-rules-105.txt
@@ -9,4 +9,8 @@
public void keep();
}
+-keep,allowobfuscation class naming001.E {
+ <methods>;
+}
+
-applymapping mapping-105.txt
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
index 787568b..5c04f51 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -5,9 +5,9 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8Command;
@@ -66,7 +66,8 @@
AndroidApp app = buildApplication(builder);
List<String> pgConfigs = ImmutableList.of(
keepMainProguardConfiguration(CLASS_NAME),
- "-printmapping",
+ // We're testing lense-based invocation type conversions.
+ "-dontoptimize",
"-dontobfuscate",
"-allowaccessmodification");
R8Command.Builder command = ToolHelper.prepareR8CommandBuilder(app);
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index 7c09fa2..6593110 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -38,9 +38,9 @@
private String checkDiscardRule(boolean member, Class annotation) {
if (member) {
- return "-checkdiscard class * { @" + annotation.getCanonicalName() + " *; }";
+ return "-checkdiscard class * { @" + annotation.getName() + " *; }";
} else {
- return "-checkdiscard @" + annotation.getCanonicalName() + " class *";
+ return "-checkdiscard @" + annotation.getName() + " class *";
}
}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
index 253c114..ee1cbca 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
@@ -8,6 +8,7 @@
@WillStay
public String hello() {
+ System.out.println("side-effect!");
return "hello";
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index 8f227e3..b7ccc01 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -154,7 +154,7 @@
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
// TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
- test(result, 1, 0, 0, 1);
+ test(result, 1, 1, 0, 1);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
index 031a8b3..5c89be9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -84,7 +84,8 @@
@NeverInline
public String toString(Object unused) {
- return "Hello world!";
+ System.out.print("Hello ");
+ return "world!";
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
index 5b87e3a..a3f1b9b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
@@ -90,7 +90,8 @@
@NeverInline
@Override
public String greeting(String unused) {
- return "Hello world!";
+ System.out.print("Hello ");
+ return "world!";
}
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
index b5e57b5..f64424e 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
@@ -5,9 +5,9 @@
package com.android.tools.r8.naming.applymapping.sourcelibrary;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8Command;
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index c02e401..624b2e4 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -8,6 +8,8 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -18,15 +20,15 @@
@RunWith(Parameterized.class)
public class Regress69825683Test extends TestBase {
- private Backend backend;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- public Regress69825683Test(Backend backend) {
- this.backend = backend;
+ public Regress69825683Test(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -39,17 +41,18 @@
innerName = innerName.substring(0, index) + "$" + innerName.substring(index + 1);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(outer.getPackage()))
.addKeepMainRule(outer)
.enableSideEffectAnnotations()
.addKeepRules(
- "-assumemayhavesideeffects class " + inner.getTypeName() + " {",
+ "-assumemayhavesideeffects class " + inner.getName() + " {",
" synthetic void <init>(...);",
"}")
.addOptionsModification(options -> options.enableClassInlining = false)
.noMinification()
- .run(outer)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), outer)
// Run code to check that the constructor with synthetic class as argument is present.
.assertSuccessWithOutputThatMatches(startsWith(innerName))
.inspector();
@@ -69,15 +72,21 @@
public void innerConstructsOuter() throws Exception {
Class<?> clazz = com.android.tools.r8.regress.b69825683.innerconstructsouter.Outer.class;
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(clazz.getPackage()))
.addKeepMainRule(clazz)
.enableInliningAnnotations()
+ .enableSideEffectAnnotations()
+ .addKeepRules(
+ "-assumemayhavesideeffects class " + clazz.getName() + " {",
+ " void <init>(...);",
+ "}")
.noMinification()
.addOptionsModification(o -> o.enableClassInlining = false)
+ .setMinApi(parameters.getRuntime())
// Run code to check that the constructor with synthetic class as argument is present.
- .run(clazz)
- .assertSuccessWithOutputThatMatches(startsWith(clazz.getTypeName()))
+ .run(parameters.getRuntime(), clazz)
+ .assertSuccessWithOutputThatMatches(startsWith(clazz.getName()))
.inspector();
List<FoundClassSubject> classes = inspector.allClasses();
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 7937f4e..f0270f1 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -3,16 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.staticvalues;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.SgetBoolean;
-import com.android.tools.r8.code.Sput;
-import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -105,7 +103,7 @@
assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
assertTrue(value instanceof DexValueBoolean);
- assertEquals(true, ((DexValueBoolean) value).getValue());
+ assertTrue(((DexValueBoolean) value).getValue());
assertTrue(inspector.clazz("Test").field("byte", "byteField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
@@ -387,7 +385,7 @@
AndroidApp processedApplication = processApplication(originalApplication);
CodeInspector inspector = new CodeInspector(processedApplication);
- assertTrue(inspector.clazz("Test").clinit().isPresent());
+ assertThat(inspector.clazz("Test").clinit(), isPresent());
assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
DexValue value = inspector.clazz("Test").field("int", "intField").getStaticValue();
@@ -466,7 +464,7 @@
AndroidApp processedApplication = processApplication(originalApplication);
CodeInspector inspector = new CodeInspector(processedApplication);
- assertTrue(inspector.clazz(className).isPresent());
+ assertThat(inspector.clazz(className), isPresent());
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -515,8 +513,8 @@
AndroidApp processedApplication = processApplication(originalApplication);
CodeInspector inspector = new CodeInspector(processedApplication);
- assertTrue(inspector.clazz(className).isPresent());
- assertTrue(inspector.clazz(className).clinit().isPresent());
+ assertThat(inspector.clazz(className), isPresent());
+ assertThat(inspector.clazz(className).clinit(), isPresent());
String result = runArt(processedApplication, className);
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
index b0211bc..81d33fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
@@ -39,6 +39,9 @@
private static final String RULES = StringUtils.lines(
"-assumevalues class **.TestClass {",
" static boolean HAS_R8 return true;",
+ "}",
+ "-assumenosideeffects class java.lang.Boolean {",
+ " static boolean parseBoolean(java.lang.String);",
"}"
);
@@ -79,7 +82,6 @@
assertThat(hasR8, not(isPresent()));
MethodSubject clinit = main.clinit();
- // TODO(b/130561746): need to model that Boolean.parseBoolean doesn't have side-effects.
- assertThat(clinit, isPresent());
+ assertThat(clinit, not(isPresent()));
}
}