Merge commit '93182839cf31f865030c47225a346f6a437f789f' into dev-release
diff --git a/.gitignore b/.gitignore
index e5a5af0..c5f7f13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,6 +124,12 @@
third_party/openjdk/openjdk-9.0.4/windows.tar.gz
third_party/openjdk/openjdk-rt-1.8
third_party/openjdk/openjdk-rt-1.8.tar.gz
+third_party/opensource-apps/android-suite-calculator
+third_party/opensource-apps/android-suite-calculator.tar.gz
+third_party/opensource-apps/anexplorer
+third_party/opensource-apps/anexplorer.tar.gz
+third_party/opensource-apps/antennapod
+third_party/opensource-apps/antennapod.tar.gz
third_party/opensource-apps/applymapping
third_party/opensource-apps/applymapping.tar.gz
third_party/opensource_apps
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index cd4e40c..c0222e8 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -364,6 +364,15 @@
}
}
builders {
+ name: "linux-run-on-app-dump"
+ mixins: "linux"
+ mixins: "normal"
+ recipe {
+ properties_j: "test_options:[\"--bot\"]"
+ properties: "test_wrapper:tools/run_on_app_dump.py"
+ }
+ }
+ builders {
name: "linux-run-on-as-app_release"
mixins: "linux"
mixins: "normal"
@@ -373,6 +382,15 @@
}
}
builders {
+ name: "linux-run-on-app-dump_release"
+ mixins: "linux"
+ mixins: "normal"
+ recipe {
+ properties_j: "test_options:[\"--bot\"]"
+ properties: "test_wrapper:tools/run_on_app_dump.py"
+ }
+ }
+ builders {
name: "linux-jctf"
mixins: "linux"
mixins: "jctf"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 29b0731..e76751b 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -86,6 +86,11 @@
short_name: "apps-rec"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-run-on-app-dump"
+ category: "R8"
+ short_name: "apps-dump"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-jctf"
category: "R8"
short_name: "jctf"
@@ -166,6 +171,11 @@
short_name: "apps"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release"
+ category: "R8 release"
+ short_name: "apps-dump"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/r8cf-linux-jctf_release"
category: "R8 release"
short_name: "cf-jctf"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index 040d32b..1e48f29 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -149,11 +149,21 @@
repository: "https://r8.googlesource.com/r8"
}
builders {
+ name: "linux-run-on-app-dump"
+ bucket: "ci"
+ repository: "https://r8.googlesource.com/r8"
+ }
+ builders {
name: "linux-run-on-as-app_release"
bucket: "ci"
repository: "https://r8.googlesource.com/r8"
}
builders {
+ name: "linux-run-on-app-dump_release"
+ bucket: "ci"
+ repository: "https://r8.googlesource.com/r8"
+ }
+ builders {
name: "linux-jctf"
bucket: "ci"
repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index 3c01353..a0b3fa3 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -40,6 +40,7 @@
triggers: "linux-android-10.0.0"
triggers: "linux-run-on-as-app"
triggers: "linux-run-on-as-app-recompilation"
+ triggers: "linux-run-on-app-dump"
triggers: "linux-internal"
triggers: "linux-jctf"
triggers: "r8cf-linux-jctf"
@@ -77,6 +78,7 @@
triggers: "linux-internal_release"
triggers: "linux-jctf_release"
triggers: "linux-run-on-as-app_release"
+ triggers: "linux-run-on-app-dump_release"
triggers: "linux_release"
triggers: "r8cf-linux-jctf_release"
triggers: "windows_release"
@@ -454,6 +456,21 @@
}
}
+
+job {
+ id: "linux-run-on-app-dump"
+ acl_sets: "default"
+ triggering_policy: {
+ kind: GREEDY_BATCHING
+ max_concurrent_invocations: 3
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "luci.r8.ci"
+ builder: "linux-run-on-app-dump"
+ }
+}
+
job {
id: "linux-run-on-as-app_release"
acl_sets: "default"
@@ -468,6 +485,19 @@
}
}
+job {
+ id: "linux-run-on-app-dump_release"
+ acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ max_concurrent_invocations: 3
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "luci.r8.ci"
+ builder: "linux-run-on-app-dump_release"
+ }
+}
job {
id: "linux-jctf"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 408e3a0..a38b29e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -60,6 +60,7 @@
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataUtils;
@@ -318,8 +319,10 @@
InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
BackportedMethodRewriter.registerAssumedLibraryTypes(options);
if (options.enableEnumUnboxing) {
- EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+ EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
}
+ CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
+ appView.dexItemFactory());
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 4661e68..7af534e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -66,7 +67,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerCheckCast(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 51f34fc..9d4ffa0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -97,8 +98,9 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
- registry.registerConstClass(type);
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+ registry.registerConstClass(type, iterator);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 259a711..37d0ff4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
public class CfConstMethodHandle extends CfInstruction {
@@ -68,7 +69,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerMethodHandle(handle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 70417f6..b6a560f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -66,7 +67,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerProto(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index e9f11a8..b42194f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
public class CfDexItemBasedConstString extends CfInstruction {
@@ -89,7 +90,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
if (nameComputationInfo.needsToRegisterReference()) {
assert item.isDexType();
registry.registerTypeReference(item.asDexType());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 7c28eff..9b463fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
import java.util.Comparator;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -94,7 +95,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
switch (opcode) {
case Opcodes.GETFIELD:
registry.registerInstanceFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index e9c06e0..2c81fce 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
public class CfInitClass extends CfInstruction {
@@ -77,7 +78,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerInitClass(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 1300458..899ef31 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -75,7 +76,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerInstanceOf(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b8874e8..3c36d33 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.ClasspathMethod;
@@ -21,9 +22,10 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
-public abstract class CfInstruction {
+public abstract class CfInstruction implements CfOrDexInstruction {
public abstract void write(
AppView<?> appView,
@@ -72,15 +74,18 @@
return printer.toString();
}
- public void registerUse(UseRegistry registry, ProgramMethod context) {
- internalRegisterUse(registry, context);
+ public void registerUse(
+ UseRegistry registry, ProgramMethod context, ListIterator<CfInstruction> iterator) {
+ internalRegisterUse(registry, context, iterator);
}
- public void registerUseForDesugaring(UseRegistry registry, ClasspathMethod context) {
- internalRegisterUse(registry, context);
+ public void registerUseForDesugaring(
+ UseRegistry registry, ClasspathMethod context, ListIterator<CfInstruction> iterator) {
+ internalRegisterUse(registry, context, iterator);
}
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
// Intentionally empty.
}
@@ -88,6 +93,16 @@
return null;
}
+ @Override
+ public CfInstruction asCfInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isCfInstruction() {
+ return true;
+ }
+
public CfConstString asConstString() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 4765be5..4f5159b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
import java.util.Arrays;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -111,7 +112,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
Type invokeType = getInvokeType(context);
switch (invokeType) {
case DIRECT:
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index add492c..92384c8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.naming.NamingLens;
import java.util.ArrayList;
import java.util.List;
+import java.util.ListIterator;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -112,7 +113,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerCallSite(callSite);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 5f405d3..d52e429 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Comparator;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -75,7 +76,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 60da1b2..a4be181 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -66,7 +67,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerNewInstance(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 30671bd..794227e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ListIterator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -112,7 +113,8 @@
}
@Override
- void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
if (!type.isPrimitiveArrayType()) {
registry.registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
new file mode 100644
index 0000000..e85c75c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.code;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+
+public interface CfOrDexInstruction {
+
+ CfInstruction asCfInstruction();
+
+ boolean isCfInstruction();
+}
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index 3c6d03e..5562a69 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -72,7 +72,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerConstClass(getType());
+ registry.registerConstClass(getType(), null);
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 4ff81dc..52aa90d 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.graph.DexCallSite;
@@ -21,7 +22,7 @@
import java.nio.ShortBuffer;
import java.util.function.BiPredicate;
-public abstract class Instruction implements Comparable<Instruction> {
+public abstract class Instruction implements CfOrDexInstruction, Comparable<Instruction> {
public static final Instruction[] EMPTY_ARRAY = {};
public final static int[] NO_TARGETS = null;
@@ -134,6 +135,16 @@
this.offset = offset;
}
+ @Override
+ public CfInstruction asCfInstruction() {
+ return null;
+ }
+
+ @Override
+ public boolean isCfInstruction() {
+ return false;
+ }
+
public CheckCast asCheckCast() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 6ba885d..519bd27 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
@@ -76,8 +77,8 @@
return result;
}
- public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
- return getFeatureSplit(clazz.getType());
+ public FeatureSplit getFeatureSplit(ProgramDefinition clazz) {
+ return getFeatureSplit(clazz.getContextType());
}
public FeatureSplit getFeatureSplit(DexType type) {
@@ -92,7 +93,7 @@
return getFeatureSplit(clazz).isBase();
}
- public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
+ public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, ProgramDefinition context) {
FeatureSplit split = getFeatureSplit(clazz);
return split.isBase() || split == getFeatureSplit(context);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index ed19598..f872a56 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -27,8 +27,7 @@
return OptionalBool.FALSE;
}
if (clazz.isProgramClass()
- && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
- clazz.asProgramClass(), context.getContextClass())) {
+ && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
return OptionalBool.UNKNOWN;
}
return OptionalBool.TRUE;
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index bd6c452..7b1fe7c 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -178,7 +178,7 @@
IRConverter converter = new IRConverter(appInfo, timing, printer);
OneTimeMethodProcessor methodProcessor =
OneTimeMethodProcessor.create(method, methodProcessingIdFactory);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(ignore, methodProcesingId) ->
converter.processMethod(
method,
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index dbefe85..d6ae59f 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -56,6 +56,7 @@
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiPredicate;
@@ -511,16 +512,20 @@
@Override
public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
- for (CfInstruction instruction : instructions) {
- instruction.registerUse(registry, method);
+ ListIterator<CfInstruction> iterator = instructions.listIterator();
+ while (iterator.hasNext()) {
+ CfInstruction instruction = iterator.next();
+ instruction.registerUse(registry, method, iterator);
}
tryCatchRanges.forEach(tryCatch -> tryCatch.internalRegisterUse(registry, method));
}
@Override
public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
- for (CfInstruction instruction : instructions) {
- instruction.registerUseForDesugaring(registry, method);
+ ListIterator<CfInstruction> iterator = instructions.listIterator();
+ while (iterator.hasNext()) {
+ CfInstruction instruction = iterator.next();
+ instruction.registerUseForDesugaring(registry, method, iterator);
}
tryCatchRanges.forEach(tryCatch -> tryCatch.guards.forEach(registry::registerTypeReference));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e827a25..c49b888 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.TraversalContinuation;
@@ -659,6 +660,16 @@
appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
+ public boolean classInitializationMayHaveSideEffectsInContext(
+ AppView<AppInfoWithLiveness> appView, ProgramDefinition context) {
+ return classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of the current context are guaranteed to be initialized
+ // already.
+ type -> appView.appInfo().isSubtype(context.getContextType(), type),
+ Sets.newIdentityHashSet());
+ }
+
public boolean classInitializationMayHaveSideEffects(
AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
if (!seen.add(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 13c4ac4..98128c4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.LRUCacheTable;
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
@@ -343,7 +344,9 @@
createStaticallyKnownType(referenceFieldUpdaterDescriptor);
public final DexType classType = createStaticallyKnownType(classDescriptor);
+ public final DexType packageType = createStaticallyKnownType(Package.class);
public final DexType classLoaderType = createStaticallyKnownType(classLoaderDescriptor);
+ public final DexType constructorType = createStaticallyKnownType(constructorDescriptor);
public final DexType fieldType = createStaticallyKnownType(fieldDescriptor);
public final DexType methodType = createStaticallyKnownType(methodDescriptor);
public final DexType autoCloseableType = createStaticallyKnownType(autoCloseableDescriptor);
@@ -1160,15 +1163,20 @@
public final DexMethod desiredAssertionStatus;
public final DexMethod forName;
public final DexMethod forName3;
+ public final DexMethod getClassLoader =
+ createMethod(classType, createProto(classLoaderType), "getClassLoader");
public final DexMethod getName;
public final DexMethod getCanonicalName;
public final DexMethod getSimpleName;
public final DexMethod getTypeName;
+ public final DexMethod getConstructor;
public final DexMethod getDeclaredConstructor;
public final DexMethod getField;
public final DexMethod getDeclaredField;
public final DexMethod getMethod;
public final DexMethod getDeclaredMethod;
+ public final DexMethod getPackage =
+ createMethod(classType, createProto(packageType), "getPackage");
public final DexMethod newInstance;
private final Set<DexMethod> getMembers;
public final Set<DexMethod> getNames;
@@ -1195,6 +1203,8 @@
classDescriptor, getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
getTypeName = createMethod(
classDescriptor, getTypeNameName, stringDescriptor, DexString.EMPTY_ARRAY);
+ getConstructor =
+ createMethod(classType, createProto(constructorType, classArrayType), "getConstructor");
getDeclaredConstructor =
createMethod(
classDescriptor,
@@ -1926,6 +1936,11 @@
return createStaticallyKnownType(createString(descriptor));
}
+ private DexType createStaticallyKnownType(Class<?> clazz) {
+ return createStaticallyKnownType(
+ createString(DescriptorUtils.javaTypeToDescriptor(clazz.getName())));
+ }
+
private DexType createStaticallyKnownType(DexString descriptor) {
DexType type = internalCreateType(descriptor);
// Conservatively add all statically known types to "compiler synthesized types set".
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 8c9c20e..c29ebde 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -71,6 +71,11 @@
return this;
}
+ public static MethodAccessFlags createPublicStaticSynthetic() {
+ return fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+ }
+
public static MethodAccessFlags fromSharedAccessFlags(int access, boolean isConstructor) {
assert (access & SHARED_FLAGS) == access;
assert CF_FLAGS == SHARED_FLAGS;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 82a133c..e96d26c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.origin.Origin;
+
public interface ProgramDefinition {
DexProgramClass getContextClass();
@@ -14,6 +16,8 @@
DexDefinition getDefinition();
+ Origin getOrigin();
+
default boolean isProgramClass() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 4a3327a..8f64090 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.code.CfOrDexInstruction;
+import java.util.ListIterator;
public abstract class UseRegistry {
@@ -67,7 +69,8 @@
public abstract void registerInstanceOf(DexType type);
- public void registerConstClass(DexType type) {
+ public void registerConstClass(
+ DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 667c1ff..04f9a85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -156,7 +156,7 @@
SortedProgramMethodSet wave =
SortedProgramMethodSet.create(this::forEachFindLiteExtensionByNumberMethod);
OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(method, methodProcessingId) ->
converter.processMethod(
method,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 6da2550..45f5083 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -80,7 +80,7 @@
timing.begin("[Proto] Post optimize dynamic methods");
SortedProgramMethodSet wave = SortedProgramMethodSet.create(this::forEachDynamicMethod);
OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(method, methodProcessingId) ->
converter.processMethod(
method,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 2ef11c9..7dca039 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis.proto;
+import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
@@ -12,6 +13,7 @@
import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerUseRegistryFactory;
+import java.util.ListIterator;
public class ProtoEnqueuerUseRegistry extends DefaultEnqueuerUseRegistry {
@@ -30,19 +32,20 @@
}
/**
- * Unlike {@link DefaultEnqueuerUseRegistry#registerConstClass(DexType)}, this method does not
- * trace any const-class instructions in every implementation of dynamicMethod().
+ * Unlike {@link DefaultEnqueuerUseRegistry#registerConstClass(DexType, ListIterator)}, this
+ * method does not trace any const-class instructions in every implementation of dynamicMethod().
*
* <p>The const-class instructions that remain after the proto schema has been optimized will be
* traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
*/
@Override
- public void registerConstClass(DexType type) {
+ public void registerConstClass(
+ DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
if (references.isDynamicMethod(getContextMethod())) {
enqueuer.addDeadProtoTypeCandidate(type);
return;
}
- super.registerConstClass(type);
+ super.registerConstClass(type, iterator);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index c4823bc..c9377f7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -407,12 +407,14 @@
// Iterate in reverse order to ensure that POP instructions are inserted in correct order.
for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
Value value = instruction.inValues().get(i);
- if (value instanceof StackValue) {
- if (value.definition.isLoad() && value.definition.getBlock() == this) {
+ if (value.isValueOnStack()) {
+ if (!value.isPhi()
+ && (value.definition.isLoad() && value.definition.getBlock() == this)) {
assert hasLinearFlow(this, value.definition.getBlock());
value.definition.getBlock().removeInstruction(value.definition);
} else {
- Pop pop = new Pop((StackValue) value);
+ assert !(value instanceof StackValues);
+ Pop pop = new Pop(value);
pop.setBlock(this);
pop.setPosition(instruction.getPosition());
getInstructions().addLast(pop);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index e3fa34e..a3f8421 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -247,6 +249,19 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ if (current == null) {
+ throw new IllegalStateException();
+ }
+
+ TypeElement typeElement = TypeElement.classClassType(appView, definitelyNotNull());
+ Value value = code.createValue(typeElement, localInfo);
+ ConstClass constClass = new ConstClass(value, type);
+ replaceCurrentInstruction(constClass);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
if (current == null) {
throw new IllegalStateException();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 90e6097..8e7236c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -55,7 +55,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class IRCode {
+public class IRCode implements ValueFactory {
private static final int MAX_MARKING_COLOR = 0x40000000;
@@ -1097,12 +1097,9 @@
return thisValue;
}
- public Value createValue(TypeElement typeLattice, DebugLocalInfo local) {
- return new Value(valueNumberGenerator.next(), typeLattice, local);
- }
-
- public Value createValue(TypeElement typeLattice) {
- return createValue(typeLattice, null);
+ @Override
+ public Value createValue(TypeElement type, DebugLocalInfo local) {
+ return new Value(valueNumberGenerator.next(), type, local);
}
public ConstNumber createNumberConstant(long value, TypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index bec911e..cb33b18 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -41,6 +42,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 61a1882..c878d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -36,6 +36,10 @@
this.clazz = clazz;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
public DexType getClassValue() {
return clazz;
}
@@ -175,4 +179,26 @@
public String toString() {
return super.toString() + "; " + clazz.toSourceString();
}
+
+ public static class Builder extends BuilderBase<Builder, InitClass> {
+
+ private DexType type;
+
+ private Builder() {}
+
+ public Builder setType(DexType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public InitClass build() {
+ return amend(new InitClass(outValue, type));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 62c94b8..7fc0d77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1543,4 +1543,39 @@
};
}
}
+
+ public abstract static class BuilderBase<B extends BuilderBase<B, I>, I extends Instruction> {
+
+ protected Value outValue;
+ protected Position position;
+
+ public abstract I build();
+
+ public abstract B self();
+
+ final I amend(I instruction) {
+ if (position != null) {
+ instruction.setPosition(position);
+ }
+ return instruction;
+ }
+
+ public B setOutValue(Value outValue) {
+ this.outValue = outValue;
+ return self();
+ }
+
+ public B setFreshOutValue(ValueFactory factory, TypeElement type) {
+ return setOutValue(factory.createValue(type));
+ }
+
+ public B setPosition(Position position) {
+ this.position = position;
+ return self();
+ }
+
+ public B setPosition(Instruction other) {
+ return setPosition(other.getPosition());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 2790e66..792018b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,12 @@
public interface InstructionListIterator
extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
+ default void addBefore(Instruction instruction) {
+ previous();
+ add(instruction);
+ next();
+ }
+
/** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
default void replaceCurrentInstruction(Instruction newInstruction) {
replaceCurrentInstruction(newInstruction, null);
@@ -84,6 +91,9 @@
Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
+ void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
+
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
void replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 0e40a2a..31fd8f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -156,6 +156,10 @@
return arguments().get(index);
}
+ public Value getFirstArgument() {
+ return getArgument(0);
+ }
+
public int requiredArgumentRegisters() {
int registers = 0;
for (Value inValue : inValues) {
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 bf3a599..9bae360 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
@@ -27,7 +27,9 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import java.util.Collections;
import java.util.List;
public class InvokeStatic extends InvokeMethod {
@@ -44,6 +46,10 @@
this.isInterface = isInterface;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public boolean getInterfaceBit() {
return isInterface;
@@ -234,4 +240,43 @@
type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
Sets.newIdentityHashSet());
}
+
+ public static class Builder extends BuilderBase<Builder, InvokeStatic> {
+
+ private DexMethod method;
+ private List<Value> arguments = Collections.emptyList();
+
+ public Builder setArguments(List<Value> arguments) {
+ assert arguments != null;
+ this.arguments = arguments;
+ return this;
+ }
+
+ public Builder setSingleArgument(Value argument) {
+ return setArguments(ImmutableList.of(argument));
+ }
+
+ public Builder setMethod(DexMethod method) {
+ this.method = method;
+ return this;
+ }
+
+ public Builder setMethod(DexClassAndMethod method) {
+ return setMethod(method.getReference());
+ }
+
+ @Override
+ public InvokeStatic build() {
+ assert arguments != null;
+ assert method != null;
+ assert method.getArity() == arguments.size();
+ assert outValue == null || !method.getReturnType().isVoidType();
+ return amend(new InvokeStatic(method, outValue, arguments));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index d851eb9..0dd2895 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -54,6 +55,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 9ec7256..a2f872e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -16,8 +16,9 @@
public class Pop extends Instruction {
- public Pop(StackValue src) {
+ public Pop(Value src) {
super(null, src);
+ assert src.isValueOnStack();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
new file mode 100644
index 0000000..ef1b16c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, 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.ir.code;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+
+public interface ValueFactory {
+
+ default Value createValue(TypeElement type) {
+ return createValue(type, null);
+ }
+
+ Value createValue(TypeElement type, DebugLocalInfo localInfo);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 6595192..7f29860 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -107,7 +107,7 @@
}
void push(Value value) {
- assert value instanceof StackValue;
+ assert value.isValueOnStack();
height += value.requiredRegisters();
maxHeight = Math.max(maxHeight, height);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 6e47281..9b549d5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -57,7 +57,6 @@
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -590,7 +589,7 @@
private static boolean isNonMaterializingConstNumber(
com.android.tools.r8.ir.code.Instruction instruction) {
return instruction.isConstNumber()
- && !(instruction.outValue() instanceof StackValue)
+ && !(instruction.outValue().isValueOnStack())
&& !(instruction.outValue().needsRegister());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 960dea0..b2c5888 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -111,7 +111,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -124,7 +123,7 @@
private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
public final AppView<?> appView;
- public final Set<DexType> mainDexClasses;
+ public final MainDexTracingResult mainDexClasses;
private final Timing timing;
private final Outliner outliner;
@@ -196,7 +195,7 @@
this.appView = appView;
this.options = appView.options();
this.printer = printer;
- this.mainDexClasses = mainDexClasses.getClasses();
+ this.mainDexClasses = mainDexClasses;
this.codeRewriter = new CodeRewriter(appView, this);
this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
this.classInitializerDefaultsOptimization =
@@ -728,7 +727,7 @@
postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
if (postMethodProcessor != null) {
assert !options.debug;
- postMethodProcessor.forEachWave(feedback, executorService);
+ postMethodProcessor.forEachWaveWithExtension(feedback, executorService);
feedback.updateVisibleOptimizationInfo();
assert graphLensForIR == appView.graphLens();
}
@@ -1033,7 +1032,7 @@
// Process the generated method, but don't apply any outlining.
OneTimeMethodProcessor methodProcessor =
OneTimeMethodProcessor.create(synthesizedMethod, appView);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(method, methodProcessingId) ->
processMethod(
method, delayedOptimizationFeedback, methodProcessor, methodProcessingId));
@@ -1044,7 +1043,7 @@
SortedProgramMethodSet wave, ExecutorService executorService) throws ExecutionException {
if (!wave.isEmpty()) {
OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(method, methodProcessingId) ->
processMethod(
method, delayedOptimizationFeedback, methodProcessor, methodProcessingId),
@@ -1305,7 +1304,8 @@
if (appView.appInfo().hasLiveness()) {
// Reflection optimization 1. getClass() / forName() -> const-class
timing.begin("Rewrite to const class");
- ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
+ ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(
+ appView.withLiveness(), code, mainDexClasses);
timing.end();
}
@@ -1342,7 +1342,8 @@
assert code.verifyTypes(appView);
timing.begin("Remove trivial type checks/casts");
- codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+ code, context, methodProcessor, methodProcessingId);
timing.end();
if (enumValueOptimizer != null) {
@@ -1388,7 +1389,8 @@
timing.begin("Simplify control flow");
if (codeRewriter.simplifyControlFlow(code)) {
timing.begin("Remove trivial type checks/casts");
- codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+ code, context, methodProcessor, methodProcessingId);
timing.end();
}
timing.end();
@@ -1466,6 +1468,7 @@
code,
feedback,
methodProcessor,
+ methodProcessingId,
inliner,
Suppliers.memoize(
() ->
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 8e1ee32..e1c9063 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -273,7 +273,7 @@
.setLocalInfo(invoke.outValue().getLocalInfo());
}
invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
- if (graphLens.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
+ if (invoke.getOutType() != constantReturnMaterializingInstruction.getOutType()) {
affectedPhis.addAll(
constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
index 8e7cfea..143d6ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
@@ -25,10 +25,20 @@
return id;
}
+ public String getFullyQualifiedIdAndIncrement() {
+ String id = getFullyQualifiedId();
+ secondaryId++;
+ return id;
+ }
+
public String getId() {
if (secondaryId == 1) {
return Integer.toString(primaryId);
}
+ return getFullyQualifiedId();
+ }
+
+ public String getFullyQualifiedId() {
return primaryId + "$" + secondaryId;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index b7ee7e1..86ff63f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -4,30 +4,49 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-public interface MethodProcessor {
+public abstract class MethodProcessor {
- enum Phase {
+ public enum Phase {
ONE_TIME,
PRIMARY,
POST
}
- Phase getPhase();
+ protected SortedProgramMethodSet wave;
+ protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
- boolean shouldApplyCodeRewritings(ProgramMethod method);
+ public abstract Phase getPhase();
- default boolean isPrimary() {
+ public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
+
+ public boolean isPrimary() {
return getPhase() == Phase.PRIMARY;
}
- default boolean isPost() {
+ public boolean isPost() {
return getPhase() == Phase.POST;
}
- default CallSiteInformation getCallSiteInformation() {
+ public CallSiteInformation getCallSiteInformation() {
return CallSiteInformation.empty();
}
- boolean isProcessedConcurrently(ProgramMethod method);
+ public boolean isProcessedConcurrently(ProgramMethod method) {
+ return wave != null && wave.contains(method);
+ }
+
+ public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+ waveExtension.add(method);
+ }
+
+ protected final void prepareForWaveExtensionProcessing() {
+ if (waveExtension.isEmpty()) {
+ wave = SortedProgramMethodSet.empty();
+ } else {
+ wave = waveExtension;
+ waveExtension = SortedProgramMethodSet.createConcurrent();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 00bad3b..bb3f07c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -16,10 +16,9 @@
* A {@link MethodProcessor} that doesn't persist; rather just processes the given methods one-time,
* along with a default abstraction of concurrent processing.
*/
-public class OneTimeMethodProcessor implements MethodProcessor {
+public class OneTimeMethodProcessor extends MethodProcessor {
private final MethodProcessingId.Factory methodProcessingIdFactory;
- private final SortedProgramMethodSet wave;
private OneTimeMethodProcessor(
MethodProcessingId.Factory methodProcessingIdFactory, SortedProgramMethodSet wave) {
@@ -58,28 +57,29 @@
return Phase.ONE_TIME;
}
- @Override
- public boolean isProcessedConcurrently(ProgramMethod method) {
- return wave != null && wave.contains(method);
- }
-
- public <E extends Exception> void forEachWave(
+ public <E extends Exception> void forEachWaveWithExtension(
ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer) throws E {
- ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
- int i = 0;
- for (ProgramMethod method : wave) {
- consumer.accept(method, methodProcessingIds.get(method, i++));
+ while (!wave.isEmpty()) {
+ ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
+ int i = 0;
+ for (ProgramMethod method : wave) {
+ consumer.accept(method, methodProcessingIds.get(method, i++));
+ }
+ prepareForWaveExtensionProcessing();
}
}
- public <E extends Exception> void forEachWave(
+ public <E extends Exception> void forEachWaveWithExtension(
ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer,
ExecutorService executorService)
throws ExecutionException {
- ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
- ThreadUtils.processItems(
- wave,
- (method, index) -> consumer.accept(method, methodProcessingIds.get(method, index)),
- executorService);
+ while (!wave.isEmpty()) {
+ ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
+ ThreadUtils.processItems(
+ wave,
+ (method, index) -> consumer.accept(method, methodProcessingIds.get(method, index)),
+ executorService);
+ prepareForWaveExtensionProcessing();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 5b1485d..48aaf3a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IROrdering;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
@@ -32,21 +31,23 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-public class PostMethodProcessor implements MethodProcessor {
+public class PostMethodProcessor extends MethodProcessor {
private final AppView<AppInfoWithLiveness> appView;
+ private final Collection<CodeOptimization> defaultCodeOptimizations;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
private final Deque<SortedProgramMethodSet> waves;
- private SortedProgramMethodSet wave;
private final ProgramMethodSet processed = ProgramMethodSet.create();
private PostMethodProcessor(
AppView<AppInfoWithLiveness> appView,
+ Collection<CodeOptimization> defaultCodeOptimizations,
Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap,
CallGraph callGraph) {
this.appView = appView;
+ this.defaultCodeOptimizations = defaultCodeOptimizations;
this.methodsMap = methodsMap;
- this.waves = createWaves(appView, callGraph);
+ this.waves = createWaves(callGraph);
}
@Override
@@ -145,14 +146,13 @@
new PartialCallGraphBuilder(
appView, methodsToReprocess.build(appView, appView.graphLens()))
.build(executorService, timing);
- return new PostMethodProcessor(appView, optimizationsMap, callGraph);
+ return new PostMethodProcessor(
+ appView, defaultCodeOptimizations, optimizationsMap, callGraph);
}
}
- private Deque<SortedProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
- IROrdering shuffle = appView.options().testing.irOrdering;
+ private Deque<SortedProgramMethodSet> createWaves(CallGraph callGraph) {
Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
-
int waveCount = 1;
while (!callGraph.isEmpty()) {
SortedProgramMethodSet wave = callGraph.extractRoots();
@@ -161,32 +161,37 @@
Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
}
}
-
return waves;
}
@Override
- public boolean isProcessedConcurrently(ProgramMethod method) {
- return wave != null && wave.contains(method);
+ public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+ super.scheduleMethodForProcessingAfterCurrentWave(method);
+ methodsMap.put(method.getDefinition(), defaultCodeOptimizations);
}
- void forEachWave(OptimizationFeedback feedback, ExecutorService executorService)
+ void forEachWaveWithExtension(OptimizationFeedback feedback, ExecutorService executorService)
throws ExecutionException {
while (!waves.isEmpty()) {
wave = waves.removeFirst();
- assert wave.size() > 0;
- ReservedMethodProcessingIds methodProcessingIds =
- appView.methodProcessingIdFactory().reserveIds(wave);
- ThreadUtils.processItems(
- wave,
- (method, index) -> {
- Collection<CodeOptimization> codeOptimizations = methodsMap.get(method.getDefinition());
- assert codeOptimizations != null && !codeOptimizations.isEmpty();
- forEachMethod(
- method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
- },
- executorService);
- processed.addAll(wave);
+ assert !wave.isEmpty();
+ assert waveExtension.isEmpty();
+ do {
+ ReservedMethodProcessingIds methodProcessingIds =
+ appView.methodProcessingIdFactory().reserveIds(wave);
+ ThreadUtils.processItems(
+ wave,
+ (method, index) -> {
+ Collection<CodeOptimization> codeOptimizations =
+ methodsMap.get(method.getDefinition());
+ assert codeOptimizations != null && !codeOptimizations.isEmpty();
+ forEachMethod(
+ method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
+ },
+ executorService);
+ processed.addAll(wave);
+ prepareForWaveExtensionProcessing();
+ } while (!wave.isEmpty());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 9dfe8e3..dd4c0a0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -29,7 +29,7 @@
* A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner,
* i.e., from leaves to roots.
*/
-class PrimaryMethodProcessor implements MethodProcessor {
+class PrimaryMethodProcessor extends MethodProcessor {
interface WaveStartAction {
@@ -40,7 +40,6 @@
private final MethodProcessingId.Factory methodProcessingIdFactory;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
private final Deque<SortedProgramMethodSet> waves;
- private SortedProgramMethodSet wave;
private PrimaryMethodProcessor(
AppView<AppInfoWithLiveness> appView,
@@ -105,11 +104,6 @@
return waves;
}
- @Override
- public boolean isProcessedConcurrently(ProgramMethod method) {
- return wave != null && wave.contains(method);
- }
-
/**
* Applies the given method to all leaf nodes of the graph.
*
@@ -127,20 +121,25 @@
timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
while (!waves.isEmpty()) {
wave = waves.removeFirst();
- assert wave.size() > 0;
- waveStartAction.notifyWaveStart(wave);
- ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
- Collection<Timing> timings =
- ThreadUtils.processItemsWithResults(
- wave,
- (method, index) -> {
- Timing time = consumer.apply(method, methodProcessingIds.get(method, index));
- time.end();
- return time;
- },
- executorService);
- merger.add(timings);
- waveDone.accept(wave);
+ assert !wave.isEmpty();
+ assert waveExtension.isEmpty();
+ do {
+ waveStartAction.notifyWaveStart(wave);
+ ReservedMethodProcessingIds methodProcessingIds =
+ methodProcessingIdFactory.reserveIds(wave);
+ Collection<Timing> timings =
+ ThreadUtils.processItemsWithResults(
+ wave,
+ (method, index) -> {
+ Timing time = consumer.apply(method, methodProcessingIds.get(method, index));
+ time.end();
+ return time;
+ },
+ executorService);
+ merger.add(timings);
+ waveDone.accept(wave);
+ prepareForWaveExtensionProcessing();
+ } while (!wave.isEmpty());
}
merger.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 61b13cb..d765d76 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -82,6 +82,9 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -1302,7 +1305,11 @@
REMOVED_CAST_DO_NARROW
}
- public void removeTrivialCheckCastAndInstanceOfInstructions(IRCode code) {
+ public void removeTrivialCheckCastAndInstanceOfInstructions(
+ IRCode code,
+ ProgramMethod context,
+ MethodProcessor methodProcessor,
+ MethodProcessingId methodProcessingId) {
if (!appView.enableWholeProgramOptimizations()) {
return;
}
@@ -1346,7 +1353,14 @@
boolean hasPhiUsers = current.outValue().hasPhiUsers();
RemoveCheckCastInstructionIfTrivialResult removeResult =
removeCheckCastInstructionIfTrivial(
- appViewWithLiveness, current.asCheckCast(), it, code, affectedValues);
+ appViewWithLiveness,
+ current.asCheckCast(),
+ it,
+ code,
+ context,
+ affectedValues,
+ methodProcessor,
+ methodProcessingId);
if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
needToRemoveTrivialPhis |= hasPhiUsers;
@@ -1382,7 +1396,10 @@
CheckCast checkCast,
InstructionListIterator it,
IRCode code,
- Set<Value> affectedValues) {
+ ProgramMethod context,
+ Set<Value> affectedValues,
+ MethodProcessor methodProcessor,
+ MethodProcessingId methodProcessingId) {
Value inValue = checkCast.object();
Value outValue = checkCast.outValue();
DexType castType = checkCast.getType();
@@ -1438,6 +1455,39 @@
return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
}
+ // If values of cast type are guaranteed to be null, then the out-value must be null if the cast
+ // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced
+ // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast
+ // type.
+ if (castType.isClassType()
+ && castType.isAlwaysNull(appViewWithLiveness)
+ && !outValue.hasDebugUsers()) {
+ // Replace all usages of the out-value by null.
+ it.previous();
+ Value nullValue = it.insertConstNullInstruction(code, options);
+ it.next();
+ checkCast.outValue().replaceUsers(nullValue);
+ affectedValues.addAll(nullValue.affectedValues());
+
+ // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
+ UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
+ UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
+ appView, context, methodProcessingId);
+ // TODO(b/172194277): Allow synthetics when generating CF.
+ if (throwClassCastExceptionIfNotNullMethod != null) {
+ throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
+ .setSingleArgument(checkCast.object())
+ .setPosition(checkCast)
+ .build();
+ it.replaceCurrentInstruction(replacement);
+ assert replacement.lookupSingleTarget(appView, context) != null;
+ return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+ }
+ }
+
// Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
// A < B < C
// c = ... // Even though we know c is of type A,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 4e89840..cc5e7ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
@@ -13,36 +14,35 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexTracingResult;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.Sets;
import java.util.Set;
+import java.util.function.BiConsumer;
public class ReflectionOptimizer {
// Rewrite getClass() to const-class if the type of the given instance is effectively final.
// Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
public static void rewriteGetClassOrForNameToConstClass(
- AppView<AppInfoWithLiveness> appView, IRCode code) {
+ AppView<AppInfoWithLiveness> appView, IRCode code, MainDexTracingResult mainDexClasses) {
if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
return;
}
Set<Value> affectedValues = Sets.newIdentityHashSet();
ProgramMethod context = code.context();
- ClassInitializationAnalysis classInitializationAnalysis =
- new ClassInitializationAnalysis(appView, code);
for (BasicBlock block : code.blocks) {
// Conservatively bail out if the containing block has catch handlers.
// TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
@@ -51,32 +51,28 @@
}
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
- Instruction current = it.next();
- if (!current.hasOutValue() || !current.outValue().isUsed()) {
+ InvokeMethod invoke = it.nextUntil(x -> x.isInvokeStatic() || x.isInvokeVirtual());
+ if (invoke == null) {
continue;
}
- DexType type = null;
- if (current.isInvokeVirtual()) {
- type = getTypeForGetClass(appView, context, current.asInvokeVirtual());
- } else if (current.isInvokeStatic()) {
- type = getTypeForClassForName(
- appView, classInitializationAnalysis, context, current.asInvokeStatic());
- }
- if (type != null) {
- affectedValues.addAll(current.outValue().affectedValues());
- TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
- Value value = code.createValue(typeLattice, current.getLocalInfo());
- ConstClass constClass = new ConstClass(value, type);
- it.replaceCurrentInstruction(constClass);
- if (appView.options().isGeneratingClassFiles()) {
- code.method()
- .upgradeClassFileVersion(
- appView.options().requiredCfVersionForConstClassInstructions());
- }
+
+ if (invoke.isInvokeStatic()) {
+ applyTypeForClassForNameTo(
+ appView,
+ context,
+ invoke.asInvokeStatic(),
+ rewriteSingleGetClassOrForNameToConstClass(
+ appView, code, it, invoke, affectedValues, mainDexClasses));
+ } else {
+ applyTypeForGetClassTo(
+ appView,
+ context,
+ invoke.asInvokeVirtual(),
+ rewriteSingleGetClassOrForNameToConstClass(
+ appView, code, it, invoke, affectedValues, mainDexClasses));
}
}
}
- classInitializationAnalysis.finish();
// Newly introduced const-class is not null, and thus propagate that information.
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
@@ -84,24 +80,79 @@
assert code.isConsistentSSA();
}
- private static DexType getTypeForGetClass(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context, InvokeVirtual invoke) {
+ private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass(
+ AppView<AppInfoWithLiveness> appView,
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ Set<Value> affectedValues,
+ MainDexTracingResult mainDexClasses) {
+ return (type, baseClass) -> {
+ if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
+ // Bail-out if the optimization could increase the size of the main dex.
+ if (baseClass.isProgramClass()
+ && !mainDexClasses.canReferenceItemFromContextWithoutIncreasingMainDexSize(
+ baseClass.asProgramClass(), code.context())) {
+ return;
+ }
+
+ // We need to initialize the type if it may have observable side effects.
+ if (type.isClassType()
+ && baseClass.classInitializationMayHaveSideEffectsInContext(appView, code.context())) {
+ if (!baseClass.isProgramClass() || !appView.canUseInitClass()) {
+ // No way to trigger the class initialization of the given class without
+ // Class.forName(), so skip.
+ return;
+ }
+
+ instructionIterator.addBefore(
+ InitClass.builder()
+ .setFreshOutValue(code, TypeElement.getInt())
+ .setType(type)
+ .setPosition(invoke)
+ .build());
+ }
+ }
+
+ // If there are no users of the const-class then simply remove the instruction.
+ if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ return;
+ }
+
+ // Otherwise insert a const-class instruction.
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ instructionIterator.replaceCurrentInstructionWithConstClass(
+ appView, code, type, invoke.getLocalInfo());
+ if (appView.options().isGeneratingClassFiles()) {
+ code.method()
+ .upgradeClassFileVersion(
+ appView.options().requiredCfVersionForConstClassInstructions());
+ }
+ };
+ }
+
+ private static void applyTypeForGetClassTo(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramMethod context,
+ InvokeVirtual invoke,
+ BiConsumer<DexType, ? super DexClass> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod invokedMethod = invoke.getInvokedMethod();
// Class<?> Object#getClass() is final and cannot be overridden.
if (invokedMethod != dexItemFactory.objectMembers.getClass) {
- return null;
+ return;
}
Value in = invoke.getReceiver();
if (in.hasLocalInfo()) {
- return null;
+ return;
}
TypeElement inType = in.getType();
// Check the receiver is either class type or array type. Also make sure it is not
// nullable.
if (!(inType.isClassType() || inType.isArrayType())
|| inType.isNullable()) {
- return null;
+ return;
}
DexType type =
inType.isClassType()
@@ -110,49 +161,49 @@
DexType baseType = type.toBaseType(dexItemFactory);
// Make sure base type is a class type.
if (!baseType.isClassType()) {
- return null;
+ return;
}
// Only consider program class, e.g., platform can introduce subtypes in different
// versions.
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(baseType));
if (clazz == null) {
- return null;
+ return;
}
// Only consider effectively final class. Exception: new Base().getClass().
if (!clazz.isEffectivelyFinal(appView)
&& (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
- return null;
+ return;
}
// Make sure the target (base) type is visible.
ConstraintWithTarget constraints =
ConstraintWithTarget.classIsVisible(context, baseType, appView);
if (constraints == ConstraintWithTarget.NEVER) {
- return null;
+ return;
}
- return type;
+
+ consumer.accept(type, clazz);
}
- private static DexType getTypeForClassForName(
+ private static void applyTypeForClassForNameTo(
AppView<AppInfoWithLiveness> appView,
- ClassInitializationAnalysis classInitializationAnalysis,
ProgramMethod context,
- InvokeStatic invoke) {
+ InvokeStatic invoke,
+ BiConsumer<DexType, ? super DexClass> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod invokedMethod = invoke.getInvokedMethod();
// Class<?> Class#forName(String) is final and cannot be overridden.
if (invokedMethod != dexItemFactory.classMethods.forName) {
- return null;
+ return;
}
- assert invoke.inValues().size() == 1;
- Value in = invoke.inValues().get(0).getAliasedValue();
+ assert invoke.arguments().size() == 1;
+ Value in = invoke.getArgument(0).getAliasedValue();
// Only consider const-string input without locals.
if (in.hasLocalInfo() || in.isPhi()) {
- return null;
+ return;
}
// Also, check if the result of forName() is updatable via locals.
- Value out = invoke.outValue();
- if (out != null && out.hasLocalInfo()) {
- return null;
+ if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) {
+ return;
}
DexType type = null;
if (in.definition.isDexItemBasedConstString()) {
@@ -172,46 +223,39 @@
}
if (descriptor == null
|| descriptor.indexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR) > 0) {
- return null;
+ return;
}
type = dexItemFactory.createType(descriptor);
// Check if the given name refers to a reference type.
if (!type.isReferenceType()) {
- return null;
+ return;
}
} else {
// Bail out for non-deterministic input to Class<?>#forName(name).
- return null;
+ return;
}
if (type == null) {
- return null;
+ return;
}
// Make sure the (base) type is resolvable.
DexType baseType = type.toBaseType(dexItemFactory);
- DexClass baseClazz = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
- if (baseClazz == null || !baseClazz.isResolvable(appView)) {
- return null;
+ DexClass baseClass = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
+ if (baseClass == null || !baseClass.isResolvable(appView)) {
+ return;
}
- // Don't allow the instantiated class to be in a feature, if it is, we can get a
- // NoClassDefFoundError from dalvik/art.
- if (baseClazz.isProgramClass()
- && appView.appInfo().getClassToFeatureSplitMap().isInFeature(baseClazz.asProgramClass())) {
- return null;
- }
// Make sure the (base) type is visible.
- ConstraintWithTarget constraints =
- ConstraintWithTarget.classIsVisible(context, baseType, appView);
- if (constraints == ConstraintWithTarget.NEVER) {
- return null;
+ ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+ if (AccessControl.isClassAccessible(baseClass, context, classToFeatureSplitMap)
+ .isPossiblyFalse()) {
+ return;
}
- // Make sure the type is already initialized.
- // Note that, if the given name refers to an array type, the corresponding Class<?> won't
- // be initialized. So, it's okay to rewrite the instruction.
- if (type.isClassType()
- && !classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(type, invoke)) {
- return null;
- }
- return type;
+
+ // If the type is guaranteed to be visible, it must be in the same feature as the current method
+ // or in the base.
+ assert !baseClass.isProgramClass()
+ || classToFeatureSplitMap.isInBaseOrSameFeatureAs(baseClass.asProgramClass(), context);
+
+ consumer.accept(type, baseClass);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..247e2e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class UtilityMethodsForCodeOptimizations {
+
+ public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
+ AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+ InternalOptions options = appView.options();
+ if (options.isGeneratingClassFiles()) {
+ // TODO(b/172194277): Allow synthetics when generating CF.
+ return null;
+ }
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ ProgramMethod syntheticMethod =
+ syntheticItems.createMethod(
+ context,
+ dexItemFactory,
+ builder ->
+ builder
+ .setProto(proto)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options)),
+ methodProcessingId);
+ return new UtilityMethodForCodeOptimizations(syntheticMethod);
+ }
+
+ private static CfCode getThrowClassCastExceptionIfNotNullCodeTemplate(
+ DexMethod method, InternalOptions options) {
+ return CfUtilityMethodsForCodeOptimizations
+ .CfUtilityMethodsForCodeOptimizationsTemplates_throwClassCastExceptionIfNotNull(
+ options, method);
+ }
+
+ public static class UtilityMethodForCodeOptimizations {
+
+ private final ProgramMethod method;
+ private boolean optimized;
+
+ private UtilityMethodForCodeOptimizations(ProgramMethod method) {
+ this.method = method;
+ }
+
+ public ProgramMethod getMethod() {
+ assert optimized;
+ return method;
+ }
+
+ public void optimize(MethodProcessor methodProcessor) {
+ methodProcessor.scheduleMethodForProcessingAfterCurrentWave(method);
+ optimized = true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 6f945bd..dab89c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionOrPhi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
@@ -132,6 +133,7 @@
IRCode code,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
+ MethodProcessingId methodProcessingId,
Inliner inliner,
Supplier<InliningOracle> defaultOracle) {
@@ -250,7 +252,8 @@
// If a method was inlined we may be able to remove check-cast instructions because we may
// have more information about the types of the arguments at the call site. This is
// particularly important for bridge methods.
- codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+ code, method, methodProcessor, methodProcessingId);
// If a method was inlined we may be able to prune additional branches.
codeRewriter.simplifyControlFlow(code);
// If a method was inlined we may see more trivial computation/conversion of String.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 334e4fa..b46805b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -158,7 +158,7 @@
}
switch (instruction.opcode()) {
case Opcodes.CONST_CLASS:
- analyzeConstClass(instruction.asConstClass(), eligibleEnums);
+ analyzeConstClass(instruction.asConstClass(), eligibleEnums, code.context());
break;
case Opcodes.CHECK_CAST:
analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
@@ -244,7 +244,8 @@
TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
}
- private void analyzeConstClass(ConstClass constClass, Set<DexType> eligibleEnums) {
+ private void analyzeConstClass(
+ ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) {
// We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
// We however allow unboxing if the ConstClass is used only:
// - as an argument to Enum#valueOf, to allow unboxing of:
@@ -269,15 +270,18 @@
&& isUnboxableNameMethod(user.asInvokeVirtual().getInvokedMethod())) {
continue;
}
- if (!(user.isInvokeStatic()
- && user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
- markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
- return;
+ if (user.isInvokeStatic()) {
+ DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
+ if (singleTarget != null && singleTarget.getReference() == factory.enumMembers.valueOf) {
+ // The name data is required for the correct mapping from the enum name to the ordinal in
+ // the valueOf utility method.
+ addRequiredNameData(enumType);
+ continue;
+ }
}
+ markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
+ return;
}
- // The name data is required for the correct mapping from the enum name to the ordinal in the
- // valueOf utility method.
- addRequiredNameData(enumType);
eligibleEnums.add(enumType);
}
@@ -372,6 +376,7 @@
NestedGraphLens enumUnboxingLens =
new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
.fixupTypeReferences();
+ enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
GraphLens previousLens = appView.graphLens();
appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 33cc6c4..27c125a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -26,6 +27,7 @@
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
@@ -77,6 +79,7 @@
private final EnumValueInfoMapCollection enumsToUnbox;
private final EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData;
private final UnboxedEnumMemberRelocator relocator;
+ private NestedGraphLens enumUnboxingLens;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
private final Map<DexField, DexEncodedField> utilityFields = new ConcurrentHashMap<>();
@@ -140,6 +143,10 @@
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
+ public void setEnumUnboxingLens(NestedGraphLens enumUnboxingLens) {
+ this.enumUnboxingLens = enumUnboxingLens;
+ }
+
public EnumValueInfoMapCollection getEnumsToUnbox() {
return enumsToUnbox;
}
@@ -151,6 +158,7 @@
return Sets.newIdentityHashSet();
}
assert code.isConsistentSSABeforeTypesAreCorrect();
+ ProgramMethod context = code.context();
Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
@@ -162,18 +170,23 @@
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
// Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
- // counterpart.
+ // counterpart. The rewriting (== or match) is based on the following:
+ // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
+ // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
+ // - getClass is final and implemented only in java.lang.Object,
+ // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
+ // also in the unboxed enum class.
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
if (enumType != null) {
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (invokedMethod == factory.enumMembers.ordinalMethod
- || invokedMethod == factory.enumMembers.hashCode) {
+ || invokedMethod.match(factory.enumMembers.hashCode)) {
replaceEnumInvoke(
iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
continue;
- } else if (invokedMethod == factory.enumMembers.equals) {
+ } else if (invokedMethod.match(factory.enumMembers.equals)) {
replaceEnumInvoke(
iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
continue;
@@ -181,14 +194,18 @@
replaceEnumInvoke(
iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
continue;
- } else if (invokedMethod == factory.enumMembers.nameMethod
- || invokedMethod == factory.enumMembers.toString) {
- DexMethod toStringMethod =
- computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
- iterator.replaceCurrentInstruction(
- new InvokeStatic(
- toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+ } else if (invokedMethod == factory.enumMembers.nameMethod) {
+ rewriteNameMethod(iterator, invokeMethod, enumType);
continue;
+ } else if (invokedMethod.match(factory.enumMembers.toString)) {
+ DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
+ // If the lookupMethod is different, then a toString method was on the enumType
+ // class, which was moved, and the lens code rewriter will rewrite the invoke to
+ // that method.
+ if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
+ rewriteNameMethod(iterator, invokeMethod, enumType);
+ continue;
+ }
} else if (invokedMethod == factory.objectMembers.getClass) {
assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
replaceEnumInvoke(
@@ -197,11 +214,15 @@
}
} else if (instruction.isInvokeStatic()) {
InvokeStatic invokeStatic = instruction.asInvokeStatic();
- DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+ DexClassAndMethod singleTarget = invokeStatic.lookupSingleTarget(appView, context);
+ if (singleTarget == null) {
+ continue;
+ }
+ DexMethod invokedMethod = singleTarget.getReference();
if (invokedMethod == factory.enumMembers.valueOf
- && invokeStatic.inValues().get(0).isConstClass()) {
+ && invokeStatic.getArgument(0).isConstClass()) {
DexType enumType =
- invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
+ invokeStatic.getArgument(0).getConstInstruction().asConstClass().getValue();
if (enumsToUnbox.containsEnum(enumType)) {
DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
Value outValue = invokeStatic.outValue();
@@ -322,6 +343,9 @@
ArrayAccess arrayAccess = instruction.asArrayAccess();
DexType enumType = getEnumTypeOrNull(arrayAccess);
if (enumType != null) {
+ if (arrayAccess.hasOutValue()) {
+ affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
+ }
instruction = arrayAccess.withMemberType(MemberType.INT);
iterator.replaceCurrentInstruction(instruction);
convertedEnums.put(instruction, enumType);
@@ -334,6 +358,14 @@
return affectedPhis;
}
+ private void rewriteNameMethod(
+ InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
+ DexMethod toStringMethod =
+ computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
+ iterator.replaceCurrentInstruction(
+ new InvokeStatic(toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+ }
+
private Value fixNullsInBlockPhis(IRCode code, BasicBlock block, Value zeroConstValue) {
for (Phi phi : block.getPhis()) {
if (getEnumTypeOrNull(phi.getType()) != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 280dc9b..1dd8e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -113,7 +113,8 @@
encodedMethod.accessFlags.promoteToStatic();
encodedMethod.clearAnnotations();
encodedMethod.clearParameterAnnotations();
- return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ return encodedMethod.toTypeSubstitutedMethod(
+ newMethod, builder -> builder.setCompilationState(encodedMethod.getCompilationState()));
}
private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index cb6a6fa..be88541 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -99,11 +99,9 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.WorkList;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -841,15 +839,14 @@
if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
return InstructionEffect.CONDITIONAL_EFFECT;
}
- if (isKotlinNullCheck(instr, value, appView)) {
- DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
- // Kotlin specific way of throwing NPE: throwParameterIsNullException.
- // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
- // the value.
- if (invokedMethod.name
- == appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException.name) {
- // We found a NPE (or similar exception) throwing code.
- // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
+ if (instr.isInvokeStatic()) {
+ InvokeStatic invoke = instr.asInvokeStatic();
+ if (isKotlinCheckParameterIsNotNull(appView, invoke, value)) {
+ return InstructionEffect.DESIRED_EFFECT;
+ }
+ if (isKotlinThrowParameterIsNullException(appView, invoke)) {
+ // Kotlin specific way of throwing NPE. Combined with the above CONDITIONAL_EFFECT,
+ // the code acts as a null check for the given value.
for (BasicBlock predecessor : currentBlock.getPredecessors()) {
if (isNullCheck(predecessor.exit(), value)) {
return InstructionEffect.DESIRED_EFFECT;
@@ -858,11 +855,6 @@
// Hitting here means that this call might be used for other parameters. If we don't
// bail out, it will be regarded as side effects for the current value.
return InstructionEffect.NO_EFFECT;
- } else {
- // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
- assert invokedMethod.name
- == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
- return InstructionEffect.DESIRED_EFFECT;
}
}
if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
@@ -912,32 +904,40 @@
}
// Note that this method may have false positives, since the application could in principle
- // declare a method called checkParameterIsNotNull(parameter, message) or
- // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
- private static boolean isKotlinNullCheck(Instruction instr, Value value, AppView<?> appView) {
+ // declare a method called checkParameterIsNotNull(parameter, message) in a package that starts
+ // with "kotlin".
+ private static boolean isKotlinCheckParameterIsNotNull(
+ AppView<?> appView, InvokeStatic invoke, Value value) {
if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
return false;
}
- if (!instr.isInvokeStatic()) {
+ // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
+ // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
+ DexMethod checkParameterIsNotNullMethod =
+ appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull;
+ DexMethod originalInvokedMethod =
+ appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
+ return originalInvokedMethod.match(checkParameterIsNotNullMethod)
+ && invoke.getFirstArgument() == value
+ && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
+ }
+
+ // Note that this method may have false positives, since the application could in principle
+ // declare a method called throwParameterIsNullException(parameterName) in a package that starts
+ // with "kotlin".
+ private static boolean isKotlinThrowParameterIsNullException(
+ AppView<?> appView, InvokeStatic invoke) {
+ if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
return false;
}
- // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
+ // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
// e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
- MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
- Wrapper<DexMethod> checkParameterIsNotNull =
- wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
- Wrapper<DexMethod> throwParamIsNullException =
- wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
- DexMethod invokedMethod =
- appView.graphLens().getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
- Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
- if (methodWrap.equals(throwParamIsNullException)
- || (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
- if (invokedMethod.holder.getPackageDescriptor().startsWith(Kotlin.NAME)) {
- return true;
- }
- }
- return false;
+ DexMethod throwParameterIsNullExceptionMethod =
+ appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException;
+ DexMethod originalInvokedMethod =
+ appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
+ return originalInvokedMethod.match(throwParameterIsNullExceptionMethod)
+ && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
}
private static boolean isNullCheck(Instruction instr, Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 1402421..f776d44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -364,7 +364,7 @@
OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
OneTimeMethodProcessor methodProcessor =
OneTimeMethodProcessor.create(methodsToReprocess, appView);
- methodProcessor.forEachWave(
+ methodProcessor.forEachWaveWithExtension(
(method, methodProcessingId) ->
forEachMethod(
method,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..9484007
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateCfUtilityMethodsForCodeOptimizations.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.optimize.templates;
+
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+public final class CfUtilityMethodsForCodeOptimizations {
+
+ public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Ljava/lang/ClassCastException;");
+ }
+
+ public static CfCode
+ CfUtilityMethodsForCodeOptimizationsTemplates_throwClassCastExceptionIfNotNull(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfIf(If.Type.EQ, ValueType.OBJECT, label2),
+ label1,
+ new CfNew(options.itemFactory.createType("Ljava/lang/ClassCastException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/ClassCastException;"),
+ options.itemFactory.createProto(options.itemFactory.voidType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label2,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+ new ArrayDeque<>(Arrays.asList())),
+ new CfReturnVoid(),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index d96b5d4..0179328 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -48,9 +48,11 @@
import com.android.tools.r8.utils.PredicateUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Sets;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
+import java.util.SortedSet;
import java.util.function.Predicate;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
@@ -67,6 +69,7 @@
import org.objectweb.asm.util.TraceMethodVisitor;
public class CfApplicationWriter {
+
private static final boolean RUN_VERIFIER = false;
private static final boolean PRINT_CF = false;
@@ -222,7 +225,17 @@
for (DexEncodedField field : clazz.instanceFields()) {
writeField(field, writer);
}
- clazz.forEachProgramMethod(method -> writeMethod(method, version, rewriter, writer, defaults));
+ if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
+ SortedSet<ProgramMethod> programMethodSortedSet =
+ Sets.newTreeSet(
+ (a, b) -> a.getDefinition().method.slowCompareTo(b.getDefinition().method));
+ clazz.forEachProgramMethod(programMethodSortedSet::add);
+ programMethodSortedSet.forEach(
+ method -> writeMethod(method, version, rewriter, writer, defaults));
+ } else {
+ clazz.forEachProgramMethod(
+ method -> writeMethod(method, version, rewriter, writer, defaults));
+ }
writer.visitEnd();
byte[] result = writer.toByteArray();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 2dc287a..2168235 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -207,8 +208,9 @@
List<Value> ins = invoke.arguments();
Value[] changes = new Value[ins.size()];
if (isReflectionMethod(appView.dexItemFactory(), invokedMethod) || isClassNameComparison) {
- DexReference itemBasedString = identifyIdentifier(invoke, appView);
- if (itemBasedString == null) {
+ IdentifierNameStringLookupResult<?> identifierLookupResult =
+ identifyIdentifier(invoke, appView, code.context());
+ if (identifierLookupResult == null) {
warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, null);
return iterator;
}
@@ -225,7 +227,10 @@
Value newIn = code.createValue(in.getType(), in.getLocalInfo());
DexItemBasedConstString decoupled =
new DexItemBasedConstString(
- newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
+ newIn,
+ identifierLookupResult.getReference(),
+ ClassNameComputationInfo.none(),
+ throwingInfo);
changes[identifierPosition] = newIn;
if (in.numberOfAllUsers() == 1) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index ba73522..01157b9 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -10,12 +10,15 @@
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
@@ -28,6 +31,7 @@
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -196,26 +200,28 @@
* @return {@link DexReference} corresponding to the first constant string argument that matches a
* class or member name, or {@code null} if no such constant was found.
*/
- public static DexReference identifyIdentifier(
- InvokeMethod invoke, DexDefinitionSupplier definitions) {
+ public static IdentifierNameStringLookupResult<?> identifyIdentifier(
+ InvokeMethod invoke, DexDefinitionSupplier definitions, ProgramMethod context) {
+ DexItemFactory dexItemFactory = definitions.dexItemFactory();
List<Value> ins = invoke.arguments();
// The only static calls: Class#forName,
// which receive either (String) or (String, boolean, ClassLoader) as ins.
if (invoke.isInvokeStatic()) {
InvokeStatic invokeStatic = invoke.asInvokeStatic();
- if (definitions.dexItemFactory().classMethods
- .isReflectiveClassLookup(invokeStatic.getInvokedMethod())) {
- return ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions);
+ if (dexItemFactory.classMethods.isReflectiveClassLookup(invokeStatic.getInvokedMethod())) {
+ return IdentifierNameStringLookupResult.fromClassForName(
+ ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions));
}
}
if (invoke.isInvokeVirtual()) {
InvokeVirtual invokeVirtual = invoke.asInvokeVirtual();
- if (isClassNameComparison(invokeVirtual, definitions.dexItemFactory())) {
+ if (isClassNameComparison(invokeVirtual, dexItemFactory)) {
int argumentIndex = getPositionOfFirstConstString(invokeVirtual);
if (argumentIndex >= 0) {
- return inferTypeFromConstStringValue(
- definitions, invokeVirtual.inValues().get(argumentIndex));
+ return IdentifierNameStringLookupResult.fromClassNameComparison(
+ inferTypeFromConstStringValue(
+ definitions, invokeVirtual.inValues().get(argumentIndex)));
}
}
}
@@ -226,8 +232,7 @@
}
boolean isReferenceFieldUpdater =
- invoke.getReturnType().descriptor
- == definitions.dexItemFactory().referenceFieldUpdaterDescriptor;
+ invoke.getReturnType().descriptor == dexItemFactory.referenceFieldUpdaterDescriptor;
int positionOfIdentifier = isReferenceFieldUpdater ? 2 : 1;
Value in = ins.get(positionOfIdentifier);
if (in.isConstString()) {
@@ -241,7 +246,7 @@
// declared in the library. Hence there is no need to handle this case.
return null;
}
- DexClass holder = definitions.definitionFor(holderType);
+ DexClass holder = definitions.definitionFor(holderType, context);
if (holder == null) {
return null;
}
@@ -253,22 +258,29 @@
return null;
}
DexType fieldType = fieldTypeValue.getConstInstruction().asConstClass().getValue();
- return inferFieldInHolder(holder, dexString.toString(), fieldType);
+ return IdentifierNameStringLookupResult.fromUncategorized(
+ inferFieldInHolder(holder, dexString.toString(), fieldType));
}
if (numOfParams == 2) {
- return inferFieldInHolder(holder, dexString.toString(), null);
+ return IdentifierNameStringLookupResult.fromUncategorized(
+ inferFieldInHolder(holder, dexString.toString(), null));
}
assert numOfParams == 3;
- DexTypeList arguments =
- retrieveDexTypeListFromClassList(invoke, ins.get(2), definitions.dexItemFactory());
+ DexTypeList arguments = retrieveDexTypeListFromClassList(invoke, ins.get(2), dexItemFactory);
if (arguments == null) {
return null;
}
- return inferMethodInHolder(holder, dexString.toString(), arguments);
+ return IdentifierNameStringLookupResult.fromUncategorized(
+ inferMethodInHolder(holder, dexString.toString(), arguments));
}
if (in.isDexItemBasedConstString()) {
DexItemBasedConstString constString = in.getConstInstruction().asDexItemBasedConstString();
- return constString.getItem();
+ if (constString.getItem().isDexType()) {
+ return IdentifierNameStringLookupResult.fromDexTypeBasedConstString(
+ constString.getItem().asDexType());
+ }
+ return IdentifierNameStringLookupResult.fromDexMemberBasedConstString(
+ constString.getItem().asDexMember());
}
return null;
}
@@ -288,7 +300,7 @@
AppView<AppInfoWithLiveness> appView, DexString dexString) {
// "fully.qualified.ClassName.fieldOrMethodName"
// "fully.qualified.ClassName#fieldOrMethodName"
- DexReference itemBasedString = inferMemberFromNameString(appView, dexString);
+ DexMember<?, ?> itemBasedString = inferMemberFromNameString(appView, dexString);
if (itemBasedString == null) {
// "fully.qualified.ClassName"
return inferTypeFromNameString(appView, dexString);
@@ -322,7 +334,7 @@
return null;
}
- private static DexReference inferMemberFromNameString(
+ private static DexMember<?, ?> inferMemberFromNameString(
AppView<AppInfoWithLiveness> appView, DexString dexString) {
String identifier = dexString.toString();
String typeIdentifier = null;
@@ -357,14 +369,14 @@
if (holder == null) {
return null;
}
- DexReference itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
+ DexMember<?, ?> itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
if (itemBasedString == null) {
itemBasedString = inferMethodNameInHolder(holder, memberIdentifier);
}
return itemBasedString;
}
- private static DexReference inferFieldInHolder(DexClass holder, String name, DexType fieldType) {
+ private static DexField inferFieldInHolder(DexClass holder, String name, DexType fieldType) {
for (DexEncodedField encodedField : holder.fields()) {
if (encodedField.field.name.toString().equals(name)
&& (fieldType == null || encodedField.field.type == fieldType)) {
@@ -374,7 +386,7 @@
return null;
}
- private static DexReference inferMethodNameInHolder(DexClass holder, String name) {
+ private static DexMethod inferMethodNameInHolder(DexClass holder, String name) {
for (DexEncodedMethod encodedMethod : holder.methods()) {
if (encodedMethod.method.name.toString().equals(name)) {
return encodedMethod.method;
@@ -383,7 +395,7 @@
return null;
}
- private static DexReference inferMethodInHolder(
+ private static DexMethod inferMethodInHolder(
DexClass holder, String name, DexTypeList arguments) {
assert arguments != null;
for (DexEncodedMethod encodedMethod : holder.methods()) {
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..2498026
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class ClassForNameIdentifierNameStringLookupResult
+ extends IdentifierNameStringTypeLookupResult {
+
+ ClassForNameIdentifierNameStringLookupResult(DexType type) {
+ super(type);
+ }
+
+ @Override
+ public boolean isTypeInitializedFromUse() {
+ return true;
+ }
+
+ @Override
+ public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ return options.isForceProguardCompatibilityEnabled();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..0305995
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class ClassNameComparisonIdentifierNameStringLookupResult
+ extends IdentifierNameStringTypeLookupResult {
+
+ ClassNameComparisonIdentifierNameStringLookupResult(DexType type) {
+ super(type);
+ }
+
+ @Override
+ public boolean isTypeInitializedFromUse() {
+ return false;
+ }
+
+ @Override
+ public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..5072d35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public class DexMemberBasedConstStringIdentifierNameStringLookupResult
+ extends IdentifierNameStringMemberLookupResult {
+
+ DexMemberBasedConstStringIdentifierNameStringLookupResult(DexMember<?, ?> member) {
+ super(member);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..f2c1ec1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class DexTypeBasedConstStringIdentifierNameStringLookupResult
+ extends IdentifierNameStringTypeLookupResult {
+
+ DexTypeBasedConstStringIdentifierNameStringLookupResult(DexType type) {
+ super(type);
+ }
+
+ @Override
+ public boolean isTypeInitializedFromUse() {
+ return false;
+ }
+
+ @Override
+ public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..2c11771
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import static com.android.tools.r8.utils.FunctionUtils.applyOrElse;
+
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+
+public abstract class IdentifierNameStringLookupResult<R extends DexReference> {
+
+ private final R reference;
+
+ IdentifierNameStringLookupResult(R reference) {
+ assert reference != null;
+ this.reference = reference;
+ }
+
+ public static ClassForNameIdentifierNameStringLookupResult fromClassForName(DexType type) {
+ return applyOrElse(type, ClassForNameIdentifierNameStringLookupResult::new, null);
+ }
+
+ public static ClassNameComparisonIdentifierNameStringLookupResult fromClassNameComparison(
+ DexType type) {
+ return applyOrElse(type, ClassNameComparisonIdentifierNameStringLookupResult::new, null);
+ }
+
+ public static DexTypeBasedConstStringIdentifierNameStringLookupResult fromDexTypeBasedConstString(
+ DexType type) {
+ return applyOrElse(type, DexTypeBasedConstStringIdentifierNameStringLookupResult::new, null);
+ }
+
+ public static DexMemberBasedConstStringIdentifierNameStringLookupResult
+ fromDexMemberBasedConstString(DexMember<?, ?> member) {
+ return applyOrElse(
+ member, DexMemberBasedConstStringIdentifierNameStringLookupResult::new, null);
+ }
+
+ public static UncategorizedMemberIdentifierNameStringLookupResult fromUncategorized(
+ DexMember<?, ?> member) {
+ return applyOrElse(member, UncategorizedMemberIdentifierNameStringLookupResult::new, null);
+ }
+
+ public boolean isTypeResult() {
+ return false;
+ }
+
+ public IdentifierNameStringTypeLookupResult asTypeResult() {
+ return null;
+ }
+
+ public R getReference() {
+ return reference;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java
new file mode 100644
index 0000000..61d7925
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public abstract class IdentifierNameStringMemberLookupResult
+ extends IdentifierNameStringLookupResult<DexMember<?, ?>> {
+
+ IdentifierNameStringMemberLookupResult(DexMember<?, ?> member) {
+ super(member);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
new file mode 100644
index 0000000..6707406
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public abstract class IdentifierNameStringTypeLookupResult
+ extends IdentifierNameStringLookupResult<DexType> {
+
+ IdentifierNameStringTypeLookupResult(DexType type) {
+ super(type);
+ }
+
+ public abstract boolean isTypeInitializedFromUse();
+
+ public abstract boolean isTypeInstantiatedFromUse(InternalOptions options);
+
+ @Override
+ public boolean isTypeResult() {
+ return true;
+ }
+
+ @Override
+ public IdentifierNameStringTypeLookupResult asTypeResult() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..5d29fc1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public class UncategorizedMemberIdentifierNameStringLookupResult
+ extends IdentifierNameStringMemberLookupResult {
+
+ UncategorizedMemberIdentifierNameStringLookupResult(DexMember<?, ?> member) {
+ super(member);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 55c58c6cd..0922f50 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
@@ -21,12 +22,15 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence.Wrapper;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -159,13 +163,28 @@
}
}
- if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(method.getReference())) {
- // TODO(b/150589374): This should check for dispatch targets or just abandon in
- // package-private.
+ if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) {
doPublicize(method);
return false;
}
+ if (accessFlags.isPackagePrivate()) {
+ // If we publicize a package private method we have to ensure there is no overrides of it. We
+ // could potentially publicize a method if it only has package-private overrides, but for know
+ // we just check if it is seen below.
+ // Note that we will not publize private methods if there exists a package-private override,
+ // and there is therefore no need to check the hierarchy above.
+ MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
+ Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
+ if (memberPool.hasSeenStrictlyBelow(methodKey)) {
+ return false;
+ }
+ doPublicize(method);
+ return false;
+ }
+
+ assert accessFlags.isPrivate();
+
if (accessFlags.isStatic()) {
// For private static methods we can just relax the access to public, since
// even though JLS prevents from declaring static method in derived class if
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index a81b5dc..6922939 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -161,12 +161,15 @@
/** All types that *must* never be inlined due to a configuration directive (testing only). */
public final Set<DexType> neverClassInline;
+ private final Set<DexType> noUnusedInterfaceRemoval;
private final Set<DexType> noVerticalClassMerging;
private final Set<DexType> noHorizontalClassMerging;
private final Set<DexType> noStaticClassMerging;
- /** Set of const-class references. */
- public final Set<DexType> constClassReferences;
+ /**
+ * Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
+ */
+ public final Set<DexType> lockCandidates;
/**
* A map from seen init-class references to the minimum required visibility of the corresponding
* static field.
@@ -226,6 +229,7 @@
Set<DexMethod> neverReprocess,
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverClassInline,
+ Set<DexType> noUnusedInterfaceRemoval,
Set<DexType> noVerticalClassMerging,
Set<DexType> noHorizontalClassMerging,
Set<DexType> noStaticClassMerging,
@@ -234,7 +238,7 @@
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
EnumValueInfoMapCollection enumValueInfoMaps,
- Set<DexType> constClassReferences,
+ Set<DexType> lockCandidates,
Map<DexType, Visibility> initClassReferences) {
super(syntheticItems, classToFeatureSplitMap, mainDexClasses);
this.deadProtoTypes = deadProtoTypes;
@@ -265,6 +269,7 @@
this.neverReprocess = neverReprocess;
this.alwaysClassInline = alwaysClassInline;
this.neverClassInline = neverClassInline;
+ this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
this.noVerticalClassMerging = noVerticalClassMerging;
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.noStaticClassMerging = noStaticClassMerging;
@@ -273,7 +278,7 @@
this.prunedTypes = prunedTypes;
this.switchMaps = switchMaps;
this.enumValueInfoMaps = enumValueInfoMaps;
- this.constClassReferences = constClassReferences;
+ this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
}
@@ -307,6 +312,7 @@
Set<DexMethod> neverReprocess,
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverClassInline,
+ Set<DexType> noUnusedInterfaceRemoval,
Set<DexType> noVerticalClassMerging,
Set<DexType> noHorizontalClassMerging,
Set<DexType> noStaticClassMerging,
@@ -315,7 +321,7 @@
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
EnumValueInfoMapCollection enumValueInfoMaps,
- Set<DexType> constClassReferences,
+ Set<DexType> lockCandidates,
Map<DexType, Visibility> initClassReferences) {
super(
appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()),
@@ -349,6 +355,7 @@
this.neverReprocess = neverReprocess;
this.alwaysClassInline = alwaysClassInline;
this.neverClassInline = neverClassInline;
+ this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
this.noVerticalClassMerging = noVerticalClassMerging;
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.noStaticClassMerging = noStaticClassMerging;
@@ -357,7 +364,7 @@
this.prunedTypes = prunedTypes;
this.switchMaps = switchMaps;
this.enumValueInfoMaps = enumValueInfoMaps;
- this.constClassReferences = constClassReferences;
+ this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
}
@@ -396,6 +403,7 @@
previous.neverReprocess,
previous.alwaysClassInline,
previous.neverClassInline,
+ previous.noUnusedInterfaceRemoval,
previous.noVerticalClassMerging,
previous.noHorizontalClassMerging,
previous.noStaticClassMerging,
@@ -404,7 +412,7 @@
previous.prunedTypes,
previous.switchMaps,
previous.enumValueInfoMaps,
- previous.constClassReferences,
+ previous.lockCandidates,
previous.initClassReferences);
}
@@ -447,6 +455,7 @@
previous.neverReprocess,
previous.alwaysClassInline,
previous.neverClassInline,
+ previous.noUnusedInterfaceRemoval,
previous.noVerticalClassMerging,
previous.noHorizontalClassMerging,
previous.noStaticClassMerging,
@@ -457,7 +466,7 @@
: CollectionUtils.mergeSets(previous.prunedTypes, removedClasses),
previous.switchMaps,
previous.enumValueInfoMaps,
- previous.constClassReferences,
+ previous.lockCandidates,
previous.initClassReferences);
assert keepInfo.verifyNoneArePinned(removedClasses, previous);
}
@@ -535,6 +544,7 @@
this.neverReprocess = previous.neverReprocess;
this.alwaysClassInline = previous.alwaysClassInline;
this.neverClassInline = previous.neverClassInline;
+ this.noUnusedInterfaceRemoval = previous.noUnusedInterfaceRemoval;
this.noVerticalClassMerging = previous.noVerticalClassMerging;
this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
this.noStaticClassMerging = previous.noStaticClassMerging;
@@ -543,7 +553,7 @@
this.prunedTypes = previous.prunedTypes;
this.switchMaps = switchMaps;
this.enumValueInfoMaps = enumValueInfoMaps;
- this.constClassReferences = previous.constClassReferences;
+ this.lockCandidates = previous.lockCandidates;
this.initClassReferences = previous.initClassReferences;
previous.markObsolete();
}
@@ -700,7 +710,7 @@
* merge any const-class classes. More info at b/142438687.
*/
public boolean isLockCandidate(DexType type) {
- return constClassReferences.contains(type);
+ return lockCandidates.contains(type);
}
public Set<DexType> getDeadProtoTypes() {
@@ -1033,6 +1043,7 @@
lens.rewriteMethods(neverReprocess),
alwaysClassInline.rewriteItems(lens::lookupType),
lens.rewriteTypes(neverClassInline),
+ lens.rewriteTypes(noUnusedInterfaceRemoval),
lens.rewriteTypes(noVerticalClassMerging),
lens.rewriteTypes(noHorizontalClassMerging),
lens.rewriteTypes(noStaticClassMerging),
@@ -1042,7 +1053,7 @@
prunedTypes,
lens.rewriteFieldKeys(switchMaps),
enumValueInfoMaps.rewrittenWithLens(lens),
- lens.rewriteTypes(constClassReferences),
+ lens.rewriteTypes(lockCandidates),
lens.rewriteTypeKeys(initClassReferences));
}
@@ -1403,6 +1414,11 @@
.shouldBreak();
}
+ /** All unused interface types that *must* never be pruned. */
+ public Set<DexType> getNoUnusedInterfaceRemovalSet() {
+ return noUnusedInterfaceRemoval;
+ }
+
/**
* All types that *must* never be merged vertically due to a configuration directive (testing
* only).
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index b67d90d..18ae137 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
+import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -14,6 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
public class DefaultEnqueuerUseRegistry extends UseRegistry {
@@ -114,8 +116,9 @@
}
@Override
- public void registerConstClass(DexType type) {
- enqueuer.traceConstClass(type, context);
+ public void registerConstClass(
+ DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
+ enqueuer.traceConstClass(type, context, iterator);
}
@Override
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 7a2969e..8e6142e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,6 +10,9 @@
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
@@ -31,6 +34,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
@@ -87,6 +91,8 @@
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringTypeLookupResult;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
@@ -101,6 +107,7 @@
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
@@ -131,6 +138,7 @@
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -314,10 +322,10 @@
private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
/**
- * A set of seen const-class references that both serve as an initial lock-candidate set and will
- * prevent statically merging the classes referenced.
+ * A set of seen const-class references that serve as an initial lock-candidate set and will
+ * prevent class merging.
*/
- private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
+ private final Set<DexType> lockCandidates = Sets.newIdentityHashSet();
/**
* A map from seen init-class references to the minimum required visibility of the corresponding
@@ -535,9 +543,22 @@
}
private DexClass definitionFor(DexType type) {
- DexClass clazz = appView.definitionFor(type);
+ return internalDefinitionFor(type, false);
+ }
+
+ private DexClass definitionForFromReflectiveAccess(DexType type) {
+ return internalDefinitionFor(type, true);
+ }
+
+ private DexClass internalDefinitionFor(DexType type, boolean fromReflectiveAccess) {
+ DexClass clazz =
+ fromReflectiveAccess
+ ? appView.appInfo().definitionForWithoutExistenceAssert(type)
+ : appView.appInfo().definitionFor(type);
if (clazz == null) {
- reportMissingClass(type);
+ if (!fromReflectiveAccess) {
+ reportMissingClass(type);
+ }
return null;
}
if (clazz.isNotProgramClass()) {
@@ -610,9 +631,9 @@
}
private DexProgramClass getProgramClassOrNullFromReflectiveAccess(DexType type) {
- // This is using appInfo.definitionForWithoutExistenceAssert() to avoid that we report
- // reflectively accessed types as missing.
- return asProgramClassOrNull(appInfo().definitionForWithoutExistenceAssert(type));
+ // To avoid that we report reflectively accessed types as missing.
+ DexClass clazz = definitionForFromReflectiveAccess(type);
+ return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
}
private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
@@ -926,17 +947,63 @@
traceConstClassOrCheckCast(type, currentMethod);
}
- void traceConstClass(DexType type, ProgramMethod currentMethod) {
+ void traceConstClass(
+ DexType type,
+ ProgramMethod currentMethod,
+ ListIterator<? extends CfOrDexInstruction> iterator) {
+ handleLockCandidate(type, currentMethod, iterator);
+ traceConstClassOrCheckCast(type, currentMethod);
+ }
+
+ private void handleLockCandidate(
+ DexType type,
+ ProgramMethod currentMethod,
+ ListIterator<? extends CfOrDexInstruction> iterator) {
// We conservatively group T.class and T[].class to ensure that we do not merge T with S if
// potential locks on T[].class and S[].class exists.
DexType baseType = type.toBaseType(appView.dexItemFactory());
if (baseType.isClassType()) {
DexProgramClass baseClass = getProgramClassOrNull(baseType);
- if (baseClass != null) {
- constClassReferences.add(baseType);
+ if (baseClass != null && isConstClassMaybeUsedAsLock(currentMethod, iterator)) {
+ lockCandidates.add(baseType);
}
}
- traceConstClassOrCheckCast(type, currentMethod);
+ }
+
+ /**
+ * Returns true if the const-class value may flow into a monitor instruction.
+ *
+ * <p>Some common usages of const-class values are handled, such as calls to Class.get*Name().
+ */
+ private boolean isConstClassMaybeUsedAsLock(
+ ProgramMethod currentMethod, ListIterator<? extends CfOrDexInstruction> iterator) {
+ if (iterator == null) {
+ return true;
+ }
+ boolean result = true;
+ if (currentMethod.getDefinition().getCode().isCfCode()) {
+ CfInstruction nextInstruction =
+ IteratorUtils.nextUntil(
+ iterator,
+ instruction ->
+ !instruction.asCfInstruction().isLabel()
+ && !instruction.asCfInstruction().isPosition())
+ .asCfInstruction();
+ assert nextInstruction != null;
+ if (nextInstruction.isInvoke()) {
+ CfInvoke invoke = nextInstruction.asInvoke();
+ DexMethod invokedMethod = invoke.getMethod();
+ ClassMethods classMethods = appView.dexItemFactory().classMethods;
+ if (classMethods.isReflectiveNameLookup(invokedMethod)
+ || invokedMethod == classMethods.desiredAssertionStatus
+ || invokedMethod == classMethods.getClassLoader
+ || invokedMethod == classMethods.getPackage) {
+ result = false;
+ }
+ }
+ iterator.previous();
+ }
+ return result;
}
private void traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
@@ -1527,14 +1594,16 @@
// Ignore primitive types.
return;
}
- DexProgramClass holder = getProgramClassOrNull(type);
- if (holder == null) {
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz == null) {
return;
}
- markTypeAsLive(
- holder,
- scopedMethodsForLiveTypes.computeIfAbsent(type, ignore -> new ScopedDexMethodSet()),
- graphReporter.registerClass(holder, reason));
+ markTypeAsLive(clazz, reason);
+ }
+
+ private void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
+ assert clazz != null;
+ markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
}
private void markTypeAsLive(DexType type, Function<DexProgramClass, KeepReasonWitness> reason) {
@@ -1671,7 +1740,9 @@
return;
}
- if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
+ if (!appView.options().enableUnusedInterfaceRemoval
+ || rootSet.noUnusedInterfaceRemoval.contains(type)
+ || mode.isTracingMainDex()) {
markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
} else {
if (liveTypes.contains(clazz)) {
@@ -3129,6 +3200,7 @@
rootSet.neverReprocess,
rootSet.alwaysClassInline,
rootSet.neverClassInline,
+ rootSet.noUnusedInterfaceRemoval,
rootSet.noVerticalClassMerging,
rootSet.noHorizontalClassMerging,
rootSet.noStaticClassMerging,
@@ -3137,7 +3209,7 @@
Collections.emptySet(),
Collections.emptyMap(),
EnumValueInfoMapCollection.empty(),
- constClassReferences,
+ lockCandidates,
initClassReferences);
appInfo.markObsolete();
return appInfoWithLiveness;
@@ -3758,18 +3830,22 @@
if (!isReflectionMethod(dexItemFactory, invokedMethod)) {
return;
}
- DexReference identifierItem = identifyIdentifier(invoke, appView);
- if (identifierItem == null) {
+ IdentifierNameStringLookupResult<?> identifierLookupResult =
+ identifyIdentifier(invoke, appView, method);
+ if (identifierLookupResult == null) {
return;
}
- if (identifierItem.isDexType()) {
- DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(identifierItem.asDexType());
+ DexReference referencedItem = identifierLookupResult.getReference();
+ if (referencedItem.isDexType()) {
+ assert identifierLookupResult.isTypeResult();
+ IdentifierNameStringTypeLookupResult identifierTypeLookupResult =
+ identifierLookupResult.asTypeResult();
+ DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(referencedItem.asDexType());
if (clazz == null) {
return;
}
- if (clazz.isAnnotation() || clazz.isInterface()) {
- markTypeAsLive(clazz.type, KeepReason.reflectiveUseIn(method));
- } else {
+ markTypeAsLive(clazz, KeepReason.reflectiveUseIn(method));
+ if (identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
workList.enqueueMarkInstantiatedAction(
clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
if (clazz.hasDefaultInitializer()) {
@@ -3778,9 +3854,11 @@
markMethodAsTargeted(initializer, reason);
markDirectStaticOrConstructorMethodAsLive(initializer, reason);
}
+ } else if (identifierTypeLookupResult.isTypeInitializedFromUse()) {
+ markDirectAndIndirectClassInitializersAsLive(clazz);
}
- } else if (identifierItem.isDexField()) {
- DexField field = identifierItem.asDexField();
+ } else if (referencedItem.isDexField()) {
+ DexField field = referencedItem.asDexField();
DexProgramClass clazz = getProgramClassOrNull(field.holder);
if (clazz == null) {
return;
@@ -3807,8 +3885,8 @@
markFieldAsKept(programField, KeepReason.reflectiveUseIn(method));
}
} else {
- assert identifierItem.isDexMethod();
- DexMethod targetedMethodReference = identifierItem.asDexMethod();
+ assert referencedItem.isDexMethod();
+ DexMethod targetedMethodReference = referencedItem.asDexMethod();
DexProgramClass clazz = getProgramClassOrNull(targetedMethodReference.holder);
if (clazz == null) {
return;
@@ -3872,8 +3950,9 @@
}
InvokeVirtual constructorDefinition = constructorValue.definition.asInvokeVirtual();
- if (constructorDefinition.getInvokedMethod()
- != appView.dexItemFactory().classMethods.getDeclaredConstructor) {
+ DexMethod invokedMethod = constructorDefinition.getInvokedMethod();
+ if (invokedMethod != appView.dexItemFactory().classMethods.getConstructor
+ && invokedMethod != appView.dexItemFactory().classMethods.getDeclaredConstructor) {
// Give up, we can't tell which constructor is being invoked.
return;
}
@@ -3887,7 +3966,7 @@
return;
}
- DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+ DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
if (clazz == null) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 2bc8459..c368c21 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -176,7 +176,7 @@
@Override
public void run(Enqueuer enqueuer) {
- enqueuer.traceConstClass(type, context);
+ enqueuer.traceConstClass(type, context, null);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index 76b6d8a..6383473 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -6,8 +6,8 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collection;
@@ -76,6 +76,17 @@
this.classes = Sets.union(roots, dependencies);
}
+ public boolean canReferenceItemFromContextWithoutIncreasingMainDexSize(
+ ProgramDefinition item, ProgramDefinition context) {
+ // If the context is not a root, then additional references from inside the context will not
+ // increase the size of the main dex.
+ if (!isRoot(context)) {
+ return true;
+ }
+ // Otherwise, require that the item is a root itself.
+ return isRoot(item);
+ }
+
public boolean isEmpty() {
assert !roots.isEmpty() || dependencies.isEmpty();
return roots.isEmpty();
@@ -93,8 +104,8 @@
return classes;
}
- public boolean contains(DexProgramClass clazz) {
- return contains(clazz.type);
+ public boolean contains(ProgramDefinition clazz) {
+ return contains(clazz.getContextType());
}
public boolean contains(DexType type) {
@@ -111,6 +122,14 @@
});
}
+ public boolean isRoot(ProgramDefinition definition) {
+ return getRoots().contains(definition.getContextType());
+ }
+
+ public boolean isDependency(ProgramDefinition definition) {
+ return getDependencies().contains(definition.getContextType());
+ }
+
public MainDexTracingResult prunedCopy(AppInfoWithLiveness appInfo) {
Builder builder = builder(appInfo);
Predicate<DexType> wasPruned = appInfo::wasPruned;
diff --git a/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java b/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java
new file mode 100644
index 0000000..c14f3e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java
@@ -0,0 +1,83 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class NoUnusedInterfaceRemovalRule extends ProguardConfigurationRule {
+
+ public static final String RULE_NAME = "nounusedinterfaceremoval";
+
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<NoUnusedInterfaceRemovalRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public NoUnusedInterfaceRemovalRule build() {
+ return new NoUnusedInterfaceRemovalRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ private NoUnusedInterfaceRemovalRule(
+ Origin origin,
+ Position position,
+ String source,
+ List<ProguardTypeMatcher> classAnnotations,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return RULE_NAME;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index d846f49..f71d027 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -469,6 +469,11 @@
configurationBuilder.addRule(rule);
return true;
}
+ if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
+ ProguardConfigurationRule rule = parseNoUnusedInterfaceRemovalRule(optionStart);
+ configurationBuilder.addRule(rule);
+ return true;
+ }
if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
ProguardConfigurationRule rule = parseNoVerticalClassMergingRule(optionStart);
configurationBuilder.addRule(rule);
@@ -751,6 +756,17 @@
return keepRuleBuilder.build();
}
+ private NoUnusedInterfaceRemovalRule parseNoUnusedInterfaceRemovalRule(Position start)
+ throws ProguardRuleParserException {
+ NoUnusedInterfaceRemovalRule.Builder keepRuleBuilder =
+ NoUnusedInterfaceRemovalRule.builder().setOrigin(origin).setStart(start);
+ parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
+ return keepRuleBuilder.build();
+ }
+
private NoVerticalClassMergingRule parseNoVerticalClassMergingRule(Position start)
throws ProguardRuleParserException {
NoVerticalClassMergingRule.Builder keepRuleBuilder =
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 0798093..5199435 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -99,6 +99,7 @@
private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
+ private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
private final Set<DexType> noStaticClassMerging = Sets.newIdentityHashSet();
@@ -246,6 +247,7 @@
|| rule instanceof WhyAreYouNotInliningRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
} else if (rule instanceof ClassInlineRule
+ || rule instanceof NoUnusedInterfaceRemovalRule
|| rule instanceof NoVerticalClassMergingRule
|| rule instanceof NoHorizontalClassMergingRule
|| rule instanceof NoStaticClassMergingRule
@@ -353,6 +355,7 @@
neverReprocess,
alwaysClassInline,
neverClassInline,
+ noUnusedInterfaceRemoval,
noVerticalClassMerging,
noHorizontalClassMerging,
noStaticClassMerging,
@@ -1238,6 +1241,9 @@
throw new Unreachable();
}
context.markAsUsed();
+ } else if (context instanceof NoUnusedInterfaceRemovalRule) {
+ noUnusedInterfaceRemoval.add(item.asDexClass().type);
+ context.markAsUsed();
} else if (context instanceof NoVerticalClassMergingRule) {
noVerticalClassMerging.add(item.asDexClass().type);
context.markAsUsed();
@@ -1751,6 +1757,7 @@
public final Set<DexMethod> reprocess;
public final Set<DexMethod> neverReprocess;
public final PredicateSet<DexType> alwaysClassInline;
+ public final Set<DexType> noUnusedInterfaceRemoval;
public final Set<DexType> noVerticalClassMerging;
public final Set<DexType> noHorizontalClassMerging;
public final Set<DexType> noStaticClassMerging;
@@ -1778,6 +1785,7 @@
Set<DexMethod> neverReprocess,
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverClassInline,
+ Set<DexType> noUnusedInterfaceRemoval,
Set<DexType> noVerticalClassMerging,
Set<DexType> noHorizontalClassMerging,
Set<DexType> noStaticClassMerging,
@@ -1812,6 +1820,7 @@
this.reprocess = reprocess;
this.neverReprocess = neverReprocess;
this.alwaysClassInline = alwaysClassInline;
+ this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
this.noVerticalClassMerging = noVerticalClassMerging;
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.noStaticClassMerging = noStaticClassMerging;
@@ -1893,6 +1902,7 @@
}
public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+ pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
pruneDeadReferences(noStaticClassMerging, definitions, enqueuer);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 974fdb1..fe5a13a 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -337,7 +337,7 @@
// We are not allowed to merge synchronized classes with synchronized methods.
return;
}
- if (appView.appInfo().constClassReferences.contains(clazz.type)) {
+ if (appView.appInfo().lockCandidates.contains(clazz.type)) {
// Since the type is const-class referenced (and the static merger does not create a lens
// to map the merged type) the class will likely remain and there is no gain from merging.
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b50d089..9831468 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -371,7 +371,9 @@
}
return false;
}
- if (targetClass.isSerializable(appView) && !appInfo.isSerializable(sourceClass.type)) {
+ if (!sourceClass.isInterface()
+ && targetClass.isSerializable(appView)
+ && !appInfo.isSerializable(sourceClass.type)) {
// https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
// 1.10 The Serializable Interface
// ...
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 3625fbb..303df06 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.MainDexClasses;
import java.util.Comparator;
@@ -37,10 +38,11 @@
private final DexType inputContextType;
private final Origin inputContextOrigin;
- static SynthesizingContext fromNonSyntheticInputClass(DexProgramClass clazz) {
+ static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
// A context that is itself non-synthetic is the single context, thus both the input context
// and synthesizing context coincide.
- return new SynthesizingContext(clazz.type, clazz.type, clazz.origin);
+ return new SynthesizingContext(
+ context.getContextType(), context.getContextType(), context.getOrigin());
}
static SynthesizingContext fromSyntheticInputClass(
@@ -73,7 +75,7 @@
return inputContextOrigin;
}
- DexType createHygienicType(int syntheticId, DexItemFactory factory) {
+ DexType createHygienicType(String syntheticId, DexItemFactory factory) {
// If the context is a synthetic input, then use its annotated context as the hygienic context.
String contextDesc = synthesizingContextType.toDescriptorString();
String prefix = contextDesc.substring(0, contextDesc.length() - 1);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 1d037cc..30d232e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -26,7 +26,7 @@
abstract DexProgramClass getHolder();
- abstract HashCode computeHash();
+ abstract HashCode computeHash(boolean intermediate);
- abstract boolean isEquivalentTo(SyntheticDefinition other);
+ abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 8aaeeb9..1a96106 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -98,13 +97,10 @@
lookupSyntheticMethodDefinitions(application);
Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
- // Don't share synthetics in intermediate mode builds.
- options.intermediate
- ? ListUtils.map(methodDefinitions, Collections::singletonList)
- : computePotentialEquivalences(methodDefinitions);
+ computePotentialEquivalences(methodDefinitions, options.intermediate);
Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
- computeActualEquivalences(potentialEquivalences, options.itemFactory);
+ computeActualEquivalences(potentialEquivalences, options.intermediate, options.itemFactory);
Builder lensBuilder = NestedGraphLens.builder();
List<DexProgramClass> newProgramClasses = new ArrayList<>();
@@ -336,11 +332,11 @@
private static <T extends SyntheticDefinition & Comparable<T>>
Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
- Collection<List<T>> potentialEquivalences, DexItemFactory factory) {
+ Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
potentialEquivalences.forEach(
members -> {
- List<List<T>> groups = groupEquivalent(members);
+ List<List<T>> groups = groupEquivalent(members, intermediate);
for (List<T> group : groups) {
T representative = findDeterministicRepresentative(group);
// The representative is required to be the first element of the group.
@@ -360,6 +356,9 @@
groups.sort(EquivalenceGroup::compareTo);
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
+ // Two equivalence groups in same context type must be distinct otherwise the assignment
+ // of the synthetic name will be non-deterministic between the two.
+ assert i == 0 || checkGroupsAreDistict(groups.get(i - 1), group);
DexType representativeType = createExternalType(context, i, factory);
equivalences.put(representativeType, group);
}
@@ -368,13 +367,13 @@
}
private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
- List<T> potentialEquivalence) {
+ List<T> potentialEquivalence, boolean intermediate) {
List<List<T>> groups = new ArrayList<>();
// Each other member is in a shared group if it is actually equivalent to the first member.
for (T synthetic : potentialEquivalence) {
boolean requireNewGroup = true;
for (List<T> group : groups) {
- if (synthetic.isEquivalentTo(group.get(0))) {
+ if (synthetic.isEquivalentTo(group.get(0), intermediate)) {
requireNewGroup = false;
group.add(synthetic);
break;
@@ -389,6 +388,12 @@
return groups;
}
+ private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistict(
+ EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
+ assert g1.compareTo(g2) != 0;
+ return true;
+ }
+
private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
List<T> members) {
// Pick a deterministic member as representative.
@@ -412,10 +417,10 @@
}
private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
- List<T> definitions) {
+ List<T> definitions, boolean intermediate) {
Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
for (T definition : definitions) {
- HashCode hash = definition.computeHash();
+ HashCode hash = definition.computeHash(intermediate);
equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
}
return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index c6726e1..dcdaafc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -16,7 +16,9 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -32,6 +34,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
public class SyntheticItems implements SyntheticDefinitionsProvider {
@@ -175,17 +178,12 @@
// Internal synthetic id creation helpers.
- private synchronized int getNextSyntheticId() {
+ private synchronized String getNextSyntheticId() {
if (nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION) {
throw new InternalCompilerError(
"Unexpected attempt to synthesize classes after synthetic finalization.");
}
- return nextSyntheticId++;
- }
-
- private static DexType hygienicType(
- DexItemFactory factory, int syntheticId, SynthesizingContext context) {
- return context.createHygienicType(syntheticId, factory);
+ return Integer.toString(nextSyntheticId++);
}
// Predicates and accessors.
@@ -244,15 +242,15 @@
return Collections.unmodifiableCollection(legacyPendingClasses.values());
}
- private SynthesizingContext getSynthesizingContext(DexProgramClass context) {
- SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.type);
+ private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
+ SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.getContextType());
if (pendingItemContext != null) {
return pendingItemContext.getContext();
}
- SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.type);
+ SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.getContextType());
return committedItemContext != null
? committedItemContext.getContext()
- : SynthesizingContext.fromNonSyntheticInputClass(context);
+ : SynthesizingContext.fromNonSyntheticInputContext(context);
}
// Addition and creation of synthetic items.
@@ -267,11 +265,28 @@
/** Create a single synthetic method item. */
public ProgramMethod createMethod(
- DexProgramClass context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+ ProgramDefinition context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+ return createMethod(context, factory, fn, this::getNextSyntheticId);
+ }
+
+ public ProgramMethod createMethod(
+ ProgramDefinition context,
+ DexItemFactory factory,
+ Consumer<SyntheticMethodBuilder> fn,
+ MethodProcessingId methodProcessingId) {
+ return createMethod(context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+ }
+
+ private ProgramMethod createMethod(
+ ProgramDefinition context,
+ DexItemFactory factory,
+ Consumer<SyntheticMethodBuilder> fn,
+ Supplier<String> syntheticIdSupplier) {
+ assert nextSyntheticId != INVALID_ID_AFTER_SYNTHETIC_FINALIZATION;
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
SynthesizingContext outerContext = getSynthesizingContext(context);
- DexType type = outerContext.createHygienicType(getNextSyntheticId(), factory);
+ DexType type = outerContext.createHygienicType(syntheticIdSupplier.get(), factory);
SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
DexProgramClass clazz = classBuilder.addMethod(fn).build();
ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index ec7794d..8cddcfc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -41,17 +41,29 @@
}
@Override
- HashCode computeHash() {
+ HashCode computeHash(boolean intermediate) {
Hasher hasher = Hashing.sha256().newHasher();
+ if (intermediate) {
+ // If in intermediate mode, include the context type as sharing is restricted to within a
+ // single context.
+ hasher.putInt(getContext().getSynthesizingContextType().hashCode());
+ }
method.getDefinition().hashSyntheticContent(hasher);
return hasher.hash();
}
@Override
- boolean isEquivalentTo(SyntheticDefinition other) {
+ boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate) {
if (!(other instanceof SyntheticMethodDefinition)) {
return false;
}
+ if (intermediate
+ && getContext().getSynthesizingContextType()
+ != other.getContext().getSynthesizingContextType()) {
+ // If in intermediate mode, only synthetics within the same context should be considered
+ // equal.
+ return false;
+ }
SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java b/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
index db31dac..8fd9edb 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.tracereferences.Tracer.TracedClassImpl;
import com.android.tools.r8.tracereferences.Tracer.TracedFieldImpl;
import com.android.tools.r8.tracereferences.Tracer.TracedMethodImpl;
+import com.android.tools.r8.tracereferences.Tracer.TracedReferenceBase;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -40,18 +41,27 @@
return Position.UNKNOWN;
}
+ private <T extends TracedReferenceBase<?, ?>> void appendSorted(
+ StringBuilder builder, Set<T> missing) {
+ missing.stream()
+ .map(element -> element.getReference())
+ .map(Object::toString)
+ .sorted()
+ .forEach(item -> builder.append(" ").append(item).append(System.lineSeparator()));
+ }
+
@Override
public String getDiagnosticMessage() {
StringBuilder builder = new StringBuilder("Tracereferences found ");
List<String> components = new ArrayList<>();
if (missingClasses.size() > 0) {
- components.add("" + missingClasses.size() + " classes");
+ components.add("" + missingClasses.size() + " classe(s)");
}
if (missingFields.size() > 0) {
- components.add("" + missingClasses.size() + " fields");
+ components.add("" + missingFields.size() + " field(s)");
}
if (missingMethods.size() > 0) {
- components.add("" + missingClasses.size() + " methods");
+ components.add("" + missingMethods.size() + " method(s)");
}
assert components.size() > 0;
for (int i = 0; i < components.size(); i++) {
@@ -63,27 +73,12 @@
builder.append(" without definition");
builder.append(System.lineSeparator());
builder.append(System.lineSeparator());
- builder.append("Classes without definition:");
- missingClasses.forEach(
- clazz ->
- builder
- .append(" ")
- .append(clazz.getReference().toString())
- .append(System.lineSeparator()));
- builder.append("Fields without definition");
- missingFields.forEach(
- field ->
- builder
- .append(" ")
- .append(field.getReference().toString())
- .append(System.lineSeparator()));
- builder.append("Methods without definition");
- missingMethods.forEach(
- method ->
- builder
- .append(" ")
- .append(method.getReference().toString())
- .append(System.lineSeparator()));
+ builder.append("Classe(s) without definition:" + System.lineSeparator());
+ appendSorted(builder, missingClasses);
+ builder.append("Field(s) without definition:" + System.lineSeparator());
+ appendSorted(builder, missingFields);
+ builder.append("Method(s) without definition:" + System.lineSeparator());
+ appendSorted(builder, missingMethods);
return builder.toString();
}
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 0f9c27e..8c5cce7 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -16,12 +16,12 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Consumer;
@Keep
public class TraceReferences {
@@ -30,38 +30,43 @@
ExceptionUtils.withCompilationHandler(command.getReporter(), () -> runInternal(command));
}
+ private static void forEachDescriptor(ProgramResourceProvider provider, Consumer<String> consumer)
+ throws ResourceException, IOException {
+ for (ProgramResource programResource : provider.getProgramResources()) {
+ if (programResource.getKind() == Kind.DEX) {
+ assert programResource.getClassDescriptors() == null;
+ for (DexProgramClass clazz :
+ new ApplicationReader(
+ AndroidApp.builder()
+ .addDexProgramData(ImmutableList.of(programResource.getBytes()))
+ .build(),
+ new InternalOptions(),
+ Timing.empty())
+ .read()
+ .classes()) {
+ consumer.accept(clazz.getType().toDescriptorString());
+ }
+ } else {
+ assert programResource.getClassDescriptors() != null;
+ programResource.getClassDescriptors().forEach(consumer);
+ }
+ }
+ }
+
private static void runInternal(TraceReferencesCommand command)
throws IOException, ResourceException {
AndroidApp.Builder builder = AndroidApp.builder();
command.getLibrary().forEach(builder::addLibraryResourceProvider);
command.getTarget().forEach(builder::addLibraryResourceProvider);
command.getSource().forEach(builder::addProgramResourceProvider);
- Set<String> tagetDescriptors = new HashSet<>();
+ Set<String> targetDescriptors = new HashSet<>();
command
.getTarget()
- .forEach(provider -> tagetDescriptors.addAll(provider.getClassDescriptors()));
+ .forEach(provider -> targetDescriptors.addAll(provider.getClassDescriptors()));
for (ProgramResourceProvider provider : command.getSource()) {
- for (ProgramResource programResource : provider.getProgramResources()) {
- if (programResource.getKind() == Kind.DEX) {
- command.getReporter().warning(new StringDiagnostic("DEX files not fully supported"));
- assert programResource.getClassDescriptors() == null;
- for (DexProgramClass clazz :
- new ApplicationReader(
- AndroidApp.builder()
- .addDexProgramData(ImmutableList.of(programResource.getBytes()))
- .build(),
- new InternalOptions(),
- Timing.empty())
- .read()
- .classes()) {
- tagetDescriptors.remove(clazz.getType().toDescriptorString());
- }
- } else {
- tagetDescriptors.removeAll(programResource.getClassDescriptors());
- }
- }
+ forEachDescriptor(provider, targetDescriptors::remove);
}
- Tracer tracer = new Tracer(tagetDescriptors, builder.build(), command.getReporter());
+ Tracer tracer = new Tracer(targetDescriptors, builder.build(), command.getReporter());
tracer.run(command.getConsumer());
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index 6923654..ce8f565 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.tracereferences;
import static com.android.tools.r8.utils.FileUtils.isArchive;
+import static com.android.tools.r8.utils.FileUtils.isDexFile;
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ClassFileResourceProvider;
@@ -11,7 +12,10 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.ArchiveResourceProvider;
@@ -27,6 +31,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
@Keep
@@ -173,6 +178,16 @@
}
if (isArchive(file)) {
traceSourceBuilder.add(ArchiveResourceProvider.fromArchive(file, false));
+ } else if (isDexFile(file)) {
+ traceSourceBuilder.add(
+ new ProgramResourceProvider() {
+ ProgramResource dexResource = ProgramResource.fromFile(Kind.DEX, file);
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return Collections.singletonList(dexResource);
+ }
+ });
} else {
error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
index 679c330..5750fe1 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
@@ -87,4 +87,56 @@
* Tracing has finished. There will be no more calls to any of the <code>acceptXXX</code> methods.
*/
default void finished() {}
+
+ static TraceReferencesConsumer emptyConsumer() {
+ return ForwardingConsumer.EMPTY_CONSUMER;
+ }
+
+ /** Forwarding consumer to delegate to an optional existing consumer. */
+ @Keep
+ class ForwardingConsumer implements TraceReferencesConsumer {
+
+ private static final TraceReferencesConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
+
+ private final TraceReferencesConsumer consumer;
+
+ public ForwardingConsumer(TraceReferencesConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void acceptType(TracedClass tracedClass) {
+ if (consumer != null) {
+ consumer.acceptType(tracedClass);
+ }
+ }
+
+ @Override
+ public void acceptField(TracedField tracedField) {
+ if (consumer != null) {
+ consumer.acceptField(tracedField);
+ }
+ }
+
+ @Override
+ public void acceptMethod(TracedMethod tracedMethod) {
+ if (consumer != null) {
+ consumer.acceptMethod(tracedMethod);
+ }
+ }
+
+ @Override
+ public void acceptPackage(PackageReference pkg) {
+ if (consumer != null) {
+ consumer.acceptPackage(pkg);
+ }
+ }
+
+ @Override
+ public void finished() {
+ if (consumer != null) {
+ consumer.finished();
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 110fb8b..72db701 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -369,6 +369,10 @@
@Override
public void registerInvokeVirtual(DexMethod method) {
+ if (method.holder.isArrayType()) {
+ addType(method.holder);
+ return;
+ }
ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
DexEncodedMethod target =
resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
diff --git a/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
index a42fd3a..59cac42 100644
--- a/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.errors.Unreachable;
import java.util.Comparator;
import java.util.List;
@@ -61,4 +62,10 @@
return diff;
};
}
+
+ public static <T> Comparator<T> unreachableComparator() {
+ return (t1, t2) -> {
+ throw new Unreachable();
+ };
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
index b7c7a87..3338dde 100644
--- a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -10,6 +10,10 @@
public class FunctionUtils {
+ public static <S, T> T applyOrElse(S object, Function<S, T> fn, T orElse) {
+ return object != null ? fn.apply(object) : orElse;
+ }
+
public static <S, T, R> Function<S, Function<T, R>> curry(BiFunction<S, T, R> function) {
return arg -> arg2 -> function.apply(arg, arg2);
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3f5ceb6..dd85ad0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -183,8 +183,7 @@
protoShrinking.enableGeneratedMessageLiteShrinking = true;
protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
- // TODO(b/170798502): Reland enum unboxing for proto enums.
- // protoShrinking.enableEnumLiteProtoShrinking = true;
+ protoShrinking.enableEnumLiteProtoShrinking = true;
}
void disableAllOptimizations() {
@@ -602,6 +601,7 @@
private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
private final KotlinOptimizationOptions kotlinOptimizationOptions =
new KotlinOptimizationOptions();
+ private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
public final TestingOptions testing = new TestingOptions();
public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -629,6 +629,10 @@
return kotlinOptimizationOptions;
}
+ public DesugarSpecificOptions desugarSpecificOptions() {
+ return desugarSpecificOptions;
+ }
+
public static boolean shouldEnableKeepRuleSynthesisForRecompilation() {
return System.getProperty("com.android.tools.r8.keepRuleSynthesisForRecompilation") != null;
}
@@ -1164,6 +1168,14 @@
System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
}
+ // Temporary desugar specific options to make progress on b/147485959
+ // All options should be including bugs to either fix the underlying issue or extend the api.
+ public static class DesugarSpecificOptions {
+ // b/172508621
+ public boolean sortMethodsOnCfOutput =
+ System.getProperty("com.android.tools.r8.sortMethodsOnCfWriting") != null;
+ }
+
public static class CallSiteOptimizationOptions {
// Each time we see an invoke with more dispatch targets than the threshold, we stop call site
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index f38c02a..b12de07 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -50,6 +50,16 @@
};
}
+ public static <T> T nextUntil(Iterator<T> iterator, Predicate<T> predicate) {
+ while (iterator.hasNext()) {
+ T element = iterator.next();
+ if (predicate.test(element)) {
+ return element;
+ }
+ }
+ return null;
+ }
+
public static <T> T peekPrevious(ListIterator<T> iterator) {
T previous = iterator.previous();
T next = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index f75d170..f33ed6f 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -177,4 +177,53 @@
}
return name.endsWith(CLASS_EXTENSION);
}
+
+ public static class ZipBuilder {
+ private final Path zipFile;
+ private final ZipOutputStream stream;
+
+ private ZipBuilder(Path zipFile) throws IOException {
+ this.zipFile = zipFile;
+ stream = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)));
+ }
+
+ public static ZipBuilder builder(Path zipFile) throws IOException {
+ return new ZipBuilder(zipFile);
+ }
+
+ public ZipBuilder addFilesRelative(Path basePath, Collection<Path> filesToAdd)
+ throws IOException {
+ for (Path path : filesToAdd) {
+ ZipEntry zipEntry =
+ new ZipEntry(
+ StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ basePath.relativize(path).iterator(), Spliterator.ORDERED),
+ false)
+ .map(Path::toString)
+ .collect(Collectors.joining("/")));
+ stream.putNextEntry(zipEntry);
+ Files.copy(path, stream);
+ stream.closeEntry();
+ }
+ return this;
+ }
+
+ public ZipBuilder addFilesRelative(Path basePath, Path... filesToAdd) throws IOException {
+ return addFilesRelative(basePath, Arrays.asList(filesToAdd));
+ }
+
+ public ZipBuilder addBytes(String path, byte[] bytes) throws IOException {
+ ZipEntry zipEntry = new ZipEntry(path);
+ stream.putNextEntry(zipEntry);
+ stream.write(bytes);
+ stream.closeEntry();
+ return this;
+ }
+
+ public Path build() throws IOException {
+ stream.close();
+ return zipFile;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
index 6f6d1a5..d902126 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -9,17 +9,23 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ComparatorUtils;
import com.android.tools.r8.utils.ForEachable;
import com.android.tools.r8.utils.ForEachableUtils;
import java.util.Comparator;
import java.util.Set;
+import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Supplier;
public class SortedProgramMethodSet extends ProgramMethodSet {
- private SortedProgramMethodSet(Supplier<TreeMap<DexMethod, ProgramMethod>> backingFactory) {
+ private static final SortedProgramMethodSet EMPTY =
+ new SortedProgramMethodSet(() -> new TreeMap<>(ComparatorUtils.unreachableComparator()));
+
+ private SortedProgramMethodSet(Supplier<SortedMap<DexMethod, ProgramMethod>> backingFactory) {
super(backingFactory);
}
@@ -40,6 +46,14 @@
return result;
}
+ public static SortedProgramMethodSet createConcurrent() {
+ return new SortedProgramMethodSet(() -> new ConcurrentSkipListMap<>(DexMethod::slowCompareTo));
+ }
+
+ public static SortedProgramMethodSet empty() {
+ return EMPTY;
+ }
+
@Override
public SortedProgramMethodSet rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens) {
diff --git a/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java b/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java
new file mode 100644
index 0000000..3cc8378
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java
@@ -0,0 +1,10 @@
+// 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NoUnusedInterfaceRemoval {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index de56e20..9167acd 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -60,6 +61,7 @@
private boolean enableConstantArgumentAnnotations = false;
private boolean enableInliningAnnotations = false;
private boolean enableMemberValuePropagationAnnotations = false;
+ private boolean enableNoUnusedInterfaceRemovalAnnotations = false;
private boolean enableNoVerticalClassMergingAnnotations = false;
private boolean enableNoHorizontalClassMergingAnnotations = false;
private boolean enableNoStaticClassMergingAnnotations = false;
@@ -83,6 +85,7 @@
if (enableConstantArgumentAnnotations
|| enableInliningAnnotations
|| enableMemberValuePropagationAnnotations
+ || enableNoUnusedInterfaceRemovalAnnotations
|| enableNoVerticalClassMergingAnnotations
|| enableNoHorizontalClassMergingAnnotations
|| enableNoStaticClassMergingAnnotations
@@ -425,6 +428,15 @@
addInternalKeepRules(sb.toString());
}
+ public T enableNoUnusedInterfaceRemovalAnnotations() {
+ if (!enableNoUnusedInterfaceRemovalAnnotations) {
+ enableNoUnusedInterfaceRemovalAnnotations = true;
+ addInternalMatchInterfaceRule(
+ NoUnusedInterfaceRemovalRule.RULE_NAME, NoUnusedInterfaceRemoval.class);
+ }
+ return self();
+ }
+
public T enableNoVerticalClassMergingAnnotations() {
if (!enableNoVerticalClassMergingAnnotations) {
enableNoVerticalClassMergingAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index ddcb6c3..e37e48e 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -267,6 +267,17 @@
return self();
}
+ public Path writeToDirectory() throws IOException {
+ Path directory = state.getNewTempFolder();
+ writeToDirectory(directory);
+ return directory;
+ }
+
+ public CR writeToDirectory(Path directory) throws IOException {
+ app.writeToDirectory(directory, getOutputMode());
+ return self();
+ }
+
public CodeInspector inspector() throws IOException, ExecutionException {
return new CodeInspector(app);
}
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 20ea852..1ac00bb 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.TestRuntime.NoneRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -43,6 +44,10 @@
return runtime.isCf();
}
+ public boolean isCfRuntime(CfVm vm) {
+ return runtime.isCf() && runtime.asCf().getVm() == vm;
+ }
+
public boolean isNoneRuntime() {
return runtime == NoneRuntime.getInstance();
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 85214af..849e7d7 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -13,7 +13,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
import java.util.function.Function;
import org.hamcrest.Matcher;
@@ -40,7 +39,7 @@
public abstract RR disassemble() throws IOException, ExecutionException;
- public RR apply(Consumer<RR> fn) {
+ public <E extends Throwable> RR apply(ThrowingConsumer<RR, E> fn) throws E {
fn.accept(self());
return self();
}
@@ -66,6 +65,13 @@
return assertSuccessWithOutputLines(Arrays.asList(expected));
}
+ public RR assertSuccessWithOutputLinesIf(boolean condition, String... expected) {
+ if (condition) {
+ return assertSuccessWithOutputLines(Arrays.asList(expected));
+ }
+ return self();
+ }
+
public RR assertSuccessWithOutputLines(List<String> expected) {
return assertSuccessWithOutput(StringUtils.lines(expected));
}
@@ -75,6 +81,13 @@
return assertFailure();
}
+ public RR assertFailureWithErrorThatMatchesIf(boolean condition, Matcher<String> matcher) {
+ if (condition) {
+ return assertFailureWithErrorThatMatches(matcher);
+ }
+ return self();
+ }
+
public RR assertFailureWithOutput(String expected) {
assertStdoutMatches(is(expected));
return assertFailure();
@@ -83,4 +96,12 @@
public RR assertFailureWithErrorThatThrows(Class<? extends Throwable> expectedError) {
return assertFailureWithErrorThatMatches(containsString(expectedError.getName()));
}
+
+ public RR assertFailureWithErrorThatThrowsIf(
+ boolean condition, Class<? extends Throwable> expectedError) {
+ if (condition) {
+ return assertFailureWithErrorThatThrows(expectedError);
+ }
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 11d410f..8624277 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -139,12 +139,15 @@
"Base::foo()",
"Base::foo1()",
"Base::foo2()",
+ "Base::foo3()",
"Sub1::foo1()",
"Itf1::foo1(0) >> Sub1::foo1()",
"Sub1::bar1(0)",
+ "Sub1::foo3()",
"Sub2::foo2()",
"Itf2::foo2(0) >> Sub2::foo2()",
- "Sub2::bar2(0)");
+ "Sub2::bar2(0)",
+ "Sub2::foo3()");
Class<?> mainClass = TestMain.class;
if (parameters.isCfRuntime()) {
// Only run JVM reference on CF runtimes.
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java
new file mode 100644
index 0000000..fa7e479
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 PackagePrivateNonOverridePublicizerTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean allowAccessModification;
+ private final String[] EXPECTED =
+ new String[] {"SubViewModel.clearNotOverriding()", "ViewModel.clear()"};
+
+ @Parameters(name = "{0}, allowAccessModification: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public PackagePrivateNonOverridePublicizerTest(
+ TestParameters parameters, boolean allowAccessModification) {
+ this.parameters = parameters;
+ this.allowAccessModification = allowAccessModification;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .applyIf(allowAccessModification, TestShrinkerBuilder::allowAccessModification)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(ViewModel.class);
+ // ViewModel.clear() is package private. When we publicize the method, we can inline
+ // the clearBridge() into Main and thereby remove the ViewModel class entirely.
+ if (allowAccessModification) {
+ assertThat(clazz, not(isPresent()));
+ } else {
+ assertThat(clazz, isPresent());
+ }
+ });
+ }
+
+ @NeverClassInline
+ @NoVerticalClassMerging
+ public static class SubViewModel extends ViewModel {
+
+ @NeverInline
+ public void clearNotOverriding() {
+ System.out.println("SubViewModel.clearNotOverriding()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ SubViewModel subViewModel = new SubViewModel();
+ subViewModel.clearNotOverriding();
+ subViewModel.clearBridge();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
new file mode 100644
index 0000000..ccd0b21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, 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.accessrelaxation;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+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 PackagePrivateOverridePublicizerTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PackagePrivateOverridePublicizerTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .allowAccessModification()
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ }
+
+ private void assertSuccessOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+ result.assertFailureWithErrorThatMatches(containsString("overrides final"));
+ } else {
+ result.assertSuccessWithOutputLines(EXPECTED);
+ }
+ }
+
+ @NeverClassInline
+ public static class SubViewModel extends ViewModel {
+
+ @NeverInline
+ public void clear() {
+ System.out.println("SubViewModel.clear()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ SubViewModel subViewModel = new SubViewModel();
+ subViewModel.clear();
+ subViewModel.clearBridge();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
index 4f5becc..0c25396 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -38,10 +38,17 @@
return foo2();
}
+ @NeverPropagateValue
+ @NeverInline
+ private String foo3() {
+ return "Base::foo3()";
+ }
+
@NeverInline
public void dump() {
System.out.println(foo());
System.out.println(foo1());
System.out.println(foo2());
+ System.out.println(foo3());
}
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index b45a708..deb2559 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -23,11 +23,17 @@
return bar1(1);
}
+ @NeverInline
+ private String foo3() {
+ return "Sub1::foo3()";
+ }
+
@Override
public void dump() {
System.out.println(foo1());
System.out.println(foo1(0));
System.out.println(bar1(0));
+ System.out.println(foo3());
}
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index fbc2ebc..15cc549 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -32,11 +32,17 @@
return bar2(2);
}
+ @NeverInline
+ private String foo3() {
+ return "Sub2::foo3()";
+ }
+
@Override
public void dump() {
System.out.println(foo2());
System.out.println(foo2(0));
System.out.println(bar2(0));
+ System.out.println(foo3());
try {
bar1(0);
} catch (AssertionError e) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
new file mode 100644
index 0000000..3b957c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import org.junit.Test;
+
+public class PrivateAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+ public PrivateAndInterfaceMethodCollisionTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void test() throws Exception {
+ // TODO(b/167981556): Should always succeed.
+ boolean expectedToSucceed =
+ !enableHorizontalClassMerging
+ || parameters.isCfRuntime(CfVm.JDK11)
+ || parameters.isDexRuntime();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(expectedToSucceed, "A.foo()", "B.bar()", "J.foo()")
+ .assertFailureWithErrorThatThrowsIf(!expectedToSucceed, IllegalAccessError.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ new B().bar();
+ new C().foo();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+
+ @NeverInline
+ default void foo() {
+ System.out.println("J.foo()");
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ private void foo() {
+ System.out.println("A.foo()");
+ }
+ }
+
+ @NoVerticalClassMerging
+ static class B {
+
+ // Only here to make sure that B is not made abstract as a result of tree shaking.
+ @NeverInline
+ public void bar() {
+ System.out.println("B.bar()");
+ }
+ }
+
+ @NeverClassInline
+ static class C extends B implements J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
new file mode 100644
index 0000000..df03d8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class PrivateAndStaticMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+ public PrivateAndStaticMethodCollisionTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ new A().bar();
+ new B().foo();
+ new B().bar();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ private static void foo() {
+ System.out.println("A.foo()");
+ }
+
+ @NeverInline
+ private void bar() {
+ System.out.println("A.bar()");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ @NeverInline
+ private void foo() {
+ System.out.println("B.foo()");
+ }
+
+ @NeverInline
+ private static void bar() {
+ System.out.println("B.bar()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
new file mode 100644
index 0000000..356acd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class StaticAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+ public StaticAndInterfaceMethodCollisionTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo()", "A.baz()", "B.bar()", "J.foo()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A.foo();
+ new A().baz();
+ new B().bar();
+ new C().foo();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+
+ @NeverInline
+ default void foo() {
+ System.out.println("J.foo()");
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ static void foo() {
+ System.out.println("A.foo()");
+ }
+
+ // Only here to make sure that A is not made abstract as a result of tree shaking.
+ @NeverInline
+ public void baz() {
+ System.out.println("A.baz()");
+ }
+ }
+
+ @NoVerticalClassMerging
+ static class B {
+
+ // Only here to make sure that B is not made abstract as a result of tree shaking.
+ @NeverInline
+ public void bar() {
+ System.out.println("B.bar()");
+ }
+ }
+
+ @NeverClassInline
+ static class C extends B implements J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
new file mode 100644
index 0000000..52b506f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class StaticAndVirtualMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+ public StaticAndVirtualMethodCollisionTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void test() throws Exception {
+ // TODO(b/172415620): Handle static/virtual method collisions.
+ assertFailsCompilationIf(
+ enableHorizontalClassMerging,
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging,
+ inspector -> inspector.assertMergedInto(B.class, A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()"),
+ e -> assertThat(e.getCause().getMessage(), containsString("Duplicate method")));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ new A().bar();
+ new B().foo();
+ new B().bar();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("A.foo()");
+ }
+
+ @NeverInline
+ public void bar() {
+ System.out.println("A.bar()");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ @NeverInline
+ public void foo() {
+ System.out.println("B.foo()");
+ }
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B.bar()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java
new file mode 100644
index 0000000..df89c21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, 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.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 VerticalClassMergingWithGetNameTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VerticalClassMergingWithGetNameTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .apply(
+ runResult -> {
+ ClassSubject bClassSubject = runResult.inspector().clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+ runResult.assertSuccessWithOutputLines(
+ bClassSubject.getFinalName(), bClassSubject.getFinalName());
+ });
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new B().print();
+ }
+ }
+
+ static class A {
+
+ @NeverInline
+ public void print() {
+ System.out.println(A.class.getName());
+ }
+ }
+
+ static class B extends A {
+
+ @NeverInline
+ public void print() {
+ super.print();
+ System.out.println(B.class.getName());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index ea450e4..dbe0d64 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -3,26 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.compatproguard;
-import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.R8CompatTestBuilder;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
import java.util.List;
class CompatProguardSmaliTestBase extends SmaliTestBase {
- CodeInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
- throws Exception {
- Path dexOutputDir = temp.newFolder().toPath();
- R8Command.Builder commandBuilder =
- new CompatProguardCommandBuilder(true)
- .setOutput(dexOutputDir, OutputMode.DexIndexed)
- .addProguardConfiguration(proguardConfigurations, Origin.unknown());
- ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
- return new CodeInspector(ToolHelper.runR8(commandBuilder.build()));
+
+ CodeInspector runCompatProguard(SmaliBuilder builder, List<String> keepRules) throws Exception {
+ return runCompatProguard(builder, testBuilder -> testBuilder.addKeepRules(keepRules));
+ }
+
+ CodeInspector runCompatProguard(
+ SmaliBuilder builder, ThrowableConsumer<R8CompatTestBuilder> configuration) throws Exception {
+ return testForR8Compat(Backend.DEX)
+ .addProgramDexFileData(builder.compile())
+ .applyIf(configuration != null, configuration)
+ .compile()
+ .inspector();
}
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 6aa8805..9976c33 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -12,11 +12,10 @@
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
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.util.List;
import org.junit.Test;
public class ForNameTest extends CompatProguardSmaliTestBase {
@@ -35,11 +34,17 @@
"return-void");
builder.addClass(BOO);
- List<String> pgConfigs = ImmutableList.of(
- keepMainProguardConfiguration(CLASS_NAME),
- "-dontshrink",
- "-dontoptimize");
- CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector =
+ runCompatProguard(
+ builder,
+ testBuilder ->
+ testBuilder
+ .addKeepMainRule(CLASS_NAME)
+ // Add main dex rule to disable Class.forName() optimization.
+ .addMainDexRules("-keep class " + CLASS_NAME)
+ .noOptimization()
+ .noTreeShaking()
+ .setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -65,12 +70,18 @@
"return-void");
builder.addClass(BOO);
- List<String> pgConfigs = ImmutableList.of(
- keepMainProguardConfiguration(CLASS_NAME),
- "-dontshrink",
- "-dontoptimize",
- "-dontobfuscate");
- CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector =
+ runCompatProguard(
+ builder,
+ testBuilder ->
+ testBuilder
+ .addKeepMainRule(CLASS_NAME)
+ // Add main dex rule to disable Class.forName() optimization.
+ .addMainDexRules("-keep class " + CLASS_NAME)
+ .noOptimization()
+ .noMinification()
+ .noTreeShaking()
+ .setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
index 9c006cb..5fbca11 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -60,7 +60,8 @@
companionClassSubject
.allMethods()
.forEach(
- method -> assertTrue(method.streamInstructions().anyMatch(this::isInvokeClassForName)));
+ method ->
+ assertTrue(method.streamInstructions().noneMatch(this::isInvokeClassForName)));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 4a2e645..45f8957 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -147,8 +147,9 @@
.writeToZip();
GenerateMainDexListRunResult mainDexListFromDex =
traceMainDex(Collections.emptyList(), Collections.singleton(out));
- // Compiling in intermediate will not share the synthetics so there is one per call site.
- assertEquals(MAIN_DEX_LIST_CLASSES.size() + 6, mainDexListFromDex.getMainDexList().size());
+ // Compiling in intermediate will share the synthetics within the context types so there is one
+ // synthetic class per backport in User2: Character.compare and Integer.compare.
+ assertEquals(MAIN_DEX_LIST_CLASSES.size() + 2, mainDexListFromDex.getMainDexList().size());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index 7a0c8bb..d2851c1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -65,6 +65,8 @@
public static void main(String[] args) {
nonNullTest();
nullTest();
+ arrayGetAndPhiTest();
+ argumentsAndPhiTest();
}
private static void nonNullTest() {
@@ -81,6 +83,21 @@
System.out.println(true);
}
+ private static void arrayGetAndPhiTest() {
+ MyEnum[] values = MyEnum.values();
+ System.out.println(arrayGetAndPhi(values, true));
+ System.out.println(values[1].ordinal());
+ System.out.println(arrayGetAndPhi(values, false));
+ System.out.println(values[0].ordinal());
+ }
+
+ private static void argumentsAndPhiTest() {
+ System.out.println(argumentsAndPhi(MyEnum.A, MyEnum.B, true));
+ System.out.println(MyEnum.B.ordinal());
+ System.out.println(argumentsAndPhi(MyEnum.A, MyEnum.B, false));
+ System.out.println(MyEnum.A.ordinal());
+ }
+
@NeverInline
static MyEnum switchOn(int i) {
MyEnum returnValue;
@@ -112,5 +129,15 @@
}
return returnValue;
}
+
+ @NeverInline
+ static int arrayGetAndPhi(MyEnum[] enums, boolean b) {
+ return (b ? enums[1] : enums[0]).ordinal();
+ }
+
+ @NeverInline
+ static int argumentsAndPhi(MyEnum e0, MyEnum e1, boolean b) {
+ return (b ? e1 : e0).ordinal();
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 357fa0e..a2fe521 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
@@ -209,7 +210,7 @@
}
}
- static class MethodProcessorMock implements MethodProcessor {
+ static class MethodProcessorMock extends MethodProcessor {
@Override
public Phase getPhase() {
@@ -225,6 +226,11 @@
public boolean isProcessedConcurrently(ProgramMethod method) {
return false;
}
+
+ @Override
+ public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+ throw new Unreachable();
+ }
}
static class OptimizationFeedbackMock extends OptimizationFeedbackIgnore {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
new file mode 100644
index 0000000..aa385fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, 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.ir.optimize.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.ArrayList;
+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 CastToUninstantiatedClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // TODO(b/172194277): Add support for synthetics when generating CF.
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public CastToUninstantiatedClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertThat(inspector.clazz(Uninstantiated.class), isAbsent()))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Got null", "Caught ClassCastException");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testNull();
+ testNonNull();
+ }
+
+ @NeverInline
+ private static void testNull() {
+ Uninstantiated u = (Uninstantiated) get(null);
+ System.out.println("Got " + u);
+ }
+
+ @NeverInline
+ private static void testNonNull() {
+ try {
+ Uninstantiated u = (Uninstantiated) get(new Object());
+ System.out.println("Got " + u);
+ } catch (ClassCastException e) {
+ System.out.println("Caught ClassCastException");
+ }
+ }
+
+ @NeverInline
+ private static Object get(Object o) {
+ List<Object> list = new ArrayList<>();
+ list.add(o);
+ return list.get(0);
+ }
+ }
+
+ static class Uninstantiated {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
index f99639f..26b927e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
@@ -161,7 +161,7 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 2, 2);
+ test(result, 1, 3);
// R8 release, minification.
result =
@@ -173,6 +173,6 @@
.setMinApi(parameters.getApiLevel())
// We are not checking output because it can't be matched due to minification. Just run.
.run(parameters.getRuntime(), MAIN);
- test(result, 2, 2);
+ test(result, 1, 3);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
new file mode 100644
index 0000000..d8d7c85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.ir.optimize.templates;
+
+public class CfUtilityMethodsForCodeOptimizationsTemplates {
+
+ public static void throwClassCastExceptionIfNotNull(Object o) {
+ if (o != null) {
+ throw new ClassCastException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..c35135e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, 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.ir.optimize.templates;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+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 GenerateCfUtilityMethodsForCodeOptimizations extends MethodGenerationBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public GenerateCfUtilityMethodsForCodeOptimizations(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Override
+ protected DexType getGeneratedType() {
+ return factory.createType(
+ "Lcom/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations;");
+ }
+
+ @Override
+ protected List<Class<?>> getMethodTemplateClasses() {
+ return ImmutableList.of(CfUtilityMethodsForCodeOptimizationsTemplates.class);
+ }
+
+ @Test
+ public void test() throws Exception {
+ ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+ sorted.sort(Comparator.comparing(Class::getTypeName));
+ assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+ assertEquals(
+ FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+ }
+
+ public static void main(String[] args) throws Exception {
+ new GenerateCfUtilityMethodsForCodeOptimizations(
+ getTestParameters().withNoneRuntime().build().iterator().next())
+ .generateMethodsAndWriteThemToFile();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 3f7bb60..fa7b74e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
@@ -61,6 +62,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index aebb0cf..a144a0e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -56,7 +56,7 @@
long paramNullCheckCount = countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
// One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
assertEquals(2, ifzCount);
- assertEquals(allowAccessModification ? 0 : 5, paramNullCheckCount);
+ assertEquals(0, paramNullCheckCount);
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index ad8c526..8775bd7 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -115,19 +115,19 @@
// Without -adaptclassstrings
private static void test1_rule1(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
test1_rules(inspector, expectedRenamedIdentifierInMain, 0, 0);
}
// With -adaptclassstrings *.*A
private static void test1_rule2(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
}
// With -adaptclassstrings (no filter)
private static void test1_rule3(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 3 : 2;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 1 : 1;
test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
}
diff --git a/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
new file mode 100644
index 0000000..cd9d54c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+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 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 PackagePrivateOverrideSameMethodNameTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
+ private final boolean minification;
+
+ @Parameters(name = "{0}, minification: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public PackagePrivateOverrideSameMethodNameTest(TestParameters parameters, boolean minification) {
+ this.parameters = parameters;
+ this.minification = minification;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeFalse(minification);
+ testForRuntime(parameters)
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .minification(minification)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+ runResult.inspectFailure(this::inspect);
+ } else {
+ runResult.inspect(this::inspect);
+ }
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject viewModel = inspector.clazz(ViewModel.class);
+ assertThat(viewModel, isPresentAndRenamed(minification));
+ ClassSubject subViewModel = inspector.clazz(SubViewModel.class);
+ assertThat(subViewModel, isPresentAndRenamed(minification));
+ MethodSubject viewModelClear = viewModel.uniqueMethodWithName("clear");
+ assertThat(viewModelClear, isPresentAndRenamed(minification));
+ MethodSubject subViewModelClear = subViewModel.uniqueMethodWithName("clear");
+ assertThat(subViewModelClear, isPresentAndRenamed(minification));
+ assertEquals(viewModelClear.getFinalName(), subViewModelClear.getFinalName());
+ if (!minification) {
+ assertEquals("clear", viewModelClear.getFinalName());
+ }
+ }
+
+ private void assertSuccessOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+ result.assertFailureWithErrorThatMatches(containsString("overrides final"));
+ } else {
+ result.assertSuccessWithOutputLines(EXPECTED);
+ }
+ }
+
+ @NeverClassInline
+ public static class SubViewModel extends ViewModel {
+
+ @NeverInline
+ public void clear() {
+ System.out.println("SubViewModel.clear()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ SubViewModel subViewModel = new SubViewModel();
+ subViewModel.clear();
+ subViewModel.clearBridge();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
index 8a36090..e374f2a 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
@@ -4,22 +4,38 @@
package com.android.tools.r8.repackage;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+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 RepackageWithDexItemBasedConstStringTest extends RepackageTestBase {
+ @Parameters(name = "{1}, kind: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+ getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsEndingAtExcluding(AndroidApiLevel.L)
+ .build());
+ }
+
public RepackageWithDexItemBasedConstStringTest(
String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
super(flattenPackageHierarchyOrRepackageClasses, parameters);
@@ -30,6 +46,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
+ .addMainDexClassRules(TestClass.class)
.apply(this::configureRepackaging)
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
index 2c5f612..0e8f6cd 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.resolution.virtualtargets.package_a;
+import com.android.tools.r8.NeverInline;
+
public abstract class ViewModel {
+ @NeverInline
final void clear() {
System.out.println("ViewModel.clear()");
}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
index 0a1914f..5a3efa6 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.shaking.clinit;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
@@ -45,10 +45,15 @@
}
private void inspect(CodeInspector inspector) {
- // Check that A.<clinit>() is removed.
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject.clinit(), not(isPresent()));
+ if (parameters.isCfRuntime()) {
+ // Check that A.<clinit>() is removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isAbsent());
+ } else {
+ // Check that A is removed.
+ assertThat(inspector.clazz(A.class), isAbsent());
+ }
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java b/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
index 76e1403..edebe31 100644
--- a/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
@@ -7,11 +7,15 @@
// Reflectively allocate A such that its instantiation cannot be identified.
public static A getInstance() throws ReflectiveOperationException {
- return (A)
- Class.forName(
- TestClass.class.getPackage().getName() + (System.nanoTime() > 0 ? ".A" : ".B"))
- .getConstructor()
- .newInstance();
+ try {
+ return (A)
+ Class.forName(
+ TestClass.class.getPackage().getName() + (System.nanoTime() > 0 ? ".A" : ".B"))
+ .getConstructor()
+ .newInstance();
+ } catch (ClassCastException e) {
+ return null;
+ }
}
public static void main(String[] args) throws ReflectiveOperationException {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 91cd90c..04e2c9f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -67,7 +67,7 @@
this.forceProguardCompatibility = forceProguardCompatibility;
}
- private void test(Class mainClass, Class mentionedClass) throws Exception {
+ private void test(Class<?> mainClass, Class<?> mentionedClass) throws Exception {
CodeInspector inspector =
testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
.noMinification()
@@ -96,9 +96,9 @@
private void runAnnotationsTest(boolean keepAnnotations) throws Exception {
// Add application classes including the annotation class.
- Class mainClass = TestMain.class;
- Class mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class;
- Class annotationClass = TestAnnotation.class;
+ Class<?> mainClass = TestMain.class;
+ Class<?> mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class;
+ Class<?> annotationClass = TestAnnotation.class;
CodeInspector inspector =
testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
@@ -173,13 +173,14 @@
runDefaultConstructorTest(TestClassWithoutDefaultConstructor.class, false);
}
- public void testCheckCast(Class mainClass, Class instantiatedClass, boolean containsCheckCast)
- throws Exception {
- List<String> proguardConfig = ImmutableList.of(
- "-keep class " + mainClass.getCanonicalName() + " {",
- " public static void main(java.lang.String[]);",
- "}",
- "-dontobfuscate");
+ public void testCheckCast(
+ Class<?> mainClass, Class<?> instantiatedClass, boolean containsCheckCast) throws Exception {
+ List<String> proguardConfig =
+ ImmutableList.of(
+ "-keep class " + mainClass.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-dontobfuscate");
CodeInspector inspector =
testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
@@ -192,7 +193,6 @@
assertTrue(inspector.clazz(mainClass).isPresent());
ClassSubject clazz = inspector.clazz(instantiatedClass);
assertEquals(containsCheckCast, clazz.isPresent());
- assertEquals(containsCheckCast, clazz.isPresent());
if (clazz.isPresent()) {
assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract());
}
@@ -212,15 +212,18 @@
@Test
public void checkCastTest() throws Exception {
- testCheckCast(TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true);
+ testCheckCast(
+ TestMainWithCheckCast.class,
+ TestClassWithDefaultConstructor.class,
+ forceProguardCompatibility || parameters.isCfRuntime());
testCheckCast(TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
}
public void testClassForName(boolean allowObfuscation) throws Exception {
- Class mainClass = TestMainWithClassForName.class;
- Class forNameClass1 = TestClassWithDefaultConstructor.class;
- Class forNameClass2 = TestClassWithoutDefaultConstructor.class;
- List<Class> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2);
+ Class<?> mainClass = TestMainWithClassForName.class;
+ Class<?> forNameClass1 = TestClassWithDefaultConstructor.class;
+ Class<?> forNameClass2 = TestClassWithoutDefaultConstructor.class;
+ List<Class<?>> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2);
ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
proguardConfigurationBuilder.add(
"-keep class " + mainClass.getCanonicalName() + " {",
@@ -259,7 +262,7 @@
CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
assertEquals(3, proguardedInspector.allClasses().size());
assertTrue(proguardedInspector.clazz(mainClass).isPresent());
- for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
+ for (Class<?> clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
assertTrue(proguardedInspector.clazz(clazz).isPresent());
assertEquals(allowObfuscation, proguardedInspector.clazz(clazz).isRenamed());
}
@@ -273,8 +276,8 @@
}
public void testClassGetMembers(boolean allowObfuscation) throws Exception {
- Class mainClass = TestMainWithGetMembers.class;
- Class withMemberClass = TestClassWithMembers.class;
+ Class<?> mainClass = TestMainWithGetMembers.class;
+ Class<?> withMemberClass = TestClassWithMembers.class;
ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
proguardConfigurationBuilder.add(
@@ -338,8 +341,8 @@
}
public void testAtomicFieldUpdaters(boolean allowObfuscation) throws Exception {
- Class mainClass = TestMainWithAtomicFieldUpdater.class;
- Class withVolatileFields = TestClassWithVolatileFields.class;
+ Class<?> mainClass = TestMainWithAtomicFieldUpdater.class;
+ Class<?> withVolatileFields = TestClassWithVolatileFields.class;
ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
proguardConfigurationBuilder.add(
@@ -469,7 +472,7 @@
assert parameters.getApiLevel().getLevel() >= AndroidApiLevel.O.getLevel();
}
- Class mainClass = TestClass.class;
+ Class<?> mainClass = TestClass.class;
R8TestCompileResult compileResult =
testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
index c92d7e7..abd2407 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
@@ -87,7 +87,7 @@
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("In Api.api1 6", "Result 6");
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(KeepParameterNamesUnsortedLocalVariablesTableTest.class)
.addProgramClassFileData(dumpApi())
.addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
index 7d76052..0074a89 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.nio.file.Path;
@@ -71,22 +71,26 @@
@Test
public void arrayTypes() throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
- ZipUtils.zip(
- targetJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Target.class),
- ToolHelper.getClassFileForTestClass(TargetFieldType.class),
- ToolHelper.getClassFileForTestClass(TargetArgType.class),
- ToolHelper.getClassFileForTestClass(TargetReturnType.class),
- ToolHelper.getClassFileForTestClass(TargetInstantiatedType.class),
- ToolHelper.getClassFileForTestClass(TargetInstanceOfType.class),
- ToolHelper.getClassFileForTestClass(TargetCheckCastType.class));
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class));
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Target.class),
+ ToolHelper.getClassFileForTestClass(TargetFieldType.class),
+ ToolHelper.getClassFileForTestClass(TargetArgType.class),
+ ToolHelper.getClassFileForTestClass(TargetReturnType.class),
+ ToolHelper.getClassFileForTestClass(TargetInstantiatedType.class),
+ ToolHelper.getClassFileForTestClass(TargetInstanceOfType.class),
+ ToolHelper.getClassFileForTestClass(TargetCheckCastType.class),
+ ToolHelper.getClassFileForTestClass(TargetArrayCloneType.class),
+ ToolHelper.getClassFileForTestClass(TargetArrayCloneType2.class))
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
MissingReferencesConsumer consumer = new MissingReferencesConsumer();
@@ -99,7 +103,6 @@
.build());
assertEquals(
- consumer.tracedTypes,
ImmutableSet.of(
Reference.classFromClass(Target.class),
Reference.classFromClass(TargetFieldType.class),
@@ -107,7 +110,10 @@
Reference.classFromClass(TargetReturnType.class),
Reference.classFromClass(TargetInstantiatedType.class),
Reference.classFromClass(TargetInstanceOfType.class),
- Reference.classFromClass(TargetCheckCastType.class)));
+ Reference.classFromClass(TargetCheckCastType.class),
+ Reference.classFromClass(TargetArrayCloneType.class),
+ Reference.classFromClass(TargetArrayCloneType2.class)),
+ consumer.tracedTypes);
assertTrue(consumer.acceptFieldCalled);
assertTrue(consumer.acceptMethodCalled);
}
@@ -124,6 +130,10 @@
static class TargetCheckCastType {}
+ static class TargetArrayCloneType {}
+
+ static class TargetArrayCloneType2 {}
+
static class Target {
public static TargetFieldType[] field;
@@ -139,6 +149,8 @@
Object x = new TargetInstantiatedType[] {};
boolean y = null instanceof TargetInstanceOfType;
Object z = (TargetCheckCastType) null;
+ Object c = ((TargetArrayCloneType[]) null).clone();
+ Object c2 = ((TargetArrayCloneType2[][][][]) null).clone();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 5ea9bd0..77c1e53 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -21,18 +21,15 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.google.common.collect.ImmutableList;
-import java.io.BufferedOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
import kotlin.text.Charsets;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -213,20 +210,22 @@
Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer)
throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
- ZipUtils.zip(
- targetJar,
- ToolHelper.getClassPathForTests(),
- targetClasses.stream()
- .map(ToolHelper::getClassFileForTestClass)
- .collect(Collectors.toList()));
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- sourceClasses.stream()
- .map(ToolHelper::getClassFileForTestClass)
- .collect(Collectors.toList()));
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ targetClasses.stream()
+ .map(ToolHelper::getClassFileForTestClass)
+ .collect(Collectors.toList()))
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ sourceClasses.stream()
+ .map(ToolHelper::getClassFileForTestClass)
+ .collect(Collectors.toList()))
+ .build();
runAndCheckOutput(targetJar, sourceJar, format, expected, diagnosticsCheckerConsumer);
}
@@ -344,17 +343,19 @@
@Test
public void testMissingReference_errorToWarning() throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(OtherTarget.class))
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
Path output = dir.resolve("output.txt");
- ZipUtils.zip(
- targetJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(OtherTarget.class));
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class));
DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
TraceReferences.run(
TraceReferencesCommand.parse(
@@ -382,16 +383,6 @@
assertEquals(0, diagnosticsChecker.infos.size());
}
- public static void zip(Path zipFile, String path, byte[] data) throws IOException {
- try (ZipOutputStream stream =
- new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
- ZipEntry zipEntry = new ZipEntry(path);
- stream.putNextEntry(zipEntry);
- stream.write(data);
- stream.closeEntry();
- }
- }
-
private void checkTargetPartlyMissing(DiagnosticsChecker diagnosticsChecker) {
Field field;
Method method;
@@ -411,13 +402,17 @@
@Test
public void testMissingDefinition_printUses() throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
- zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class));
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
try {
runAndCheckOutput(
targetJar,
@@ -436,9 +431,17 @@
@Test
public void testMissingDefinition_keepRules() throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
- zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
ZipUtils.zip(
sourceJar,
ToolHelper.getClassPathForTests(),
@@ -465,13 +468,17 @@
@Test
public void testMissingDefinition_keepRulesAllowObfuscation() throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar = dir.resolve("target.jar");
- Path sourceJar = dir.resolve("source.jar");
- zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class));
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
try {
runAndCheckOutput(
targetJar,
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
new file mode 100644
index 0000000..b21d409
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
@@ -0,0 +1,220 @@
+// Copyright (c) 2020, 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.tracereferences;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsChecker;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+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 TraceReferencesDiagnosticTest extends TestBase {
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public TraceReferencesDiagnosticTest(TestParameters parameters) {}
+
+ @Test
+ public void traceReferencesDiagnosticClassesFieldsAndMethods() throws Throwable {
+ Path dir = temp.newFolder().toPath();
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class),
+ transformer(Target.class)
+ .removeFields(
+ (access, name, descriptor, signature, value) ->
+ name.equals("missingField1"))
+ .removeFields(
+ (access, name, descriptor, signature, value) ->
+ name.equals("missingField2"))
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) ->
+ name.equals("missingMethod"))
+ .transform())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
+
+ String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+ try {
+ DiagnosticsChecker.checkErrorsContains(
+ ImmutableList.of(
+ "Tracereferences found 3 classe(s), 2 field(s) and 4 method(s) without definition",
+ StringUtils.lines(
+ "Classe(s) without definition:",
+ prefix + "Target1;",
+ prefix + "Target2;",
+ prefix + "Target3;"),
+ StringUtils.lines(
+ "Field(s) without definition:",
+ prefix + "Target;missingField1:I",
+ prefix + "Target;missingField2:I"),
+ StringUtils.lines(
+ "Method(s) without definition:",
+ prefix + "Target1;<init>()V",
+ prefix + "Target2;<init>()V",
+ prefix + "Target3;<init>()V",
+ prefix + "Target;missingMethod()V")),
+ handler ->
+ TraceReferences.run(
+ TraceReferencesCommand.builder(handler)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(TraceReferencesConsumer.emptyConsumer())
+ .build()));
+ fail("Unexpected success");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void traceReferencesDiagnosticFieldsAndMethods() throws Throwable {
+ Path dir = temp.newFolder().toPath();
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Target1.class),
+ ToolHelper.getClassFileForTestClass(Target2.class),
+ ToolHelper.getClassFileForTestClass(Target3.class))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class),
+ transformer(Target.class)
+ .removeFields(
+ (access, name, descriptor, signature, value) ->
+ name.equals("missingField1"))
+ .removeFields(
+ (access, name, descriptor, signature, value) ->
+ name.equals("missingField2"))
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) ->
+ name.equals("missingMethod"))
+ .transform())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
+
+ String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+ try {
+ DiagnosticsChecker.checkErrorsContains(
+ ImmutableList.of(
+ "Tracereferences found 2 field(s) and 1 method(s) without definition",
+ StringUtils.lines(
+ "Field(s) without definition:",
+ prefix + "Target;missingField1:I",
+ prefix + "Target;missingField2:I"),
+ StringUtils.lines(
+ "Method(s) without definition:", prefix + "Target;missingMethod()V")),
+ handler ->
+ TraceReferences.run(
+ TraceReferencesCommand.builder(handler)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(TraceReferencesConsumer.emptyConsumer())
+ .build()));
+ fail("Unexpected success");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void traceReferencesDiagnosticMethods() throws Throwable {
+ Path dir = temp.newFolder().toPath();
+ Path targetJar =
+ ZipBuilder.builder(dir.resolve("target.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Target1.class),
+ ToolHelper.getClassFileForTestClass(Target2.class),
+ ToolHelper.getClassFileForTestClass(Target3.class))
+ .addBytes(
+ DescriptorUtils.getPathFromJavaType(Target.class),
+ transformer(Target.class)
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) ->
+ name.equals("missingMethod"))
+ .transform())
+ .build();
+ Path sourceJar =
+ ZipBuilder.builder(dir.resolve("source.jar"))
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(Source.class))
+ .build();
+
+ String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+ try {
+ DiagnosticsChecker.checkErrorsContains(
+ ImmutableList.of(
+ "Tracereferences found 1 method(s) without definition",
+ StringUtils.lines(
+ "Method(s) without definition:", prefix + "Target;missingMethod()V")),
+ handler ->
+ TraceReferences.run(
+ TraceReferencesCommand.builder(handler)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addSourceFiles(sourceJar)
+ .addTargetFiles(targetJar)
+ .setConsumer(TraceReferencesConsumer.emptyConsumer())
+ .build()));
+ fail("Unexpected success");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ static class Target1 {}
+
+ static class Target2 {}
+
+ static class Target3 {}
+
+ static class Target {
+ public static int missingField1;
+ public static int missingField2;
+
+ public static void missingMethod() {}
+ }
+
+ static class Source {
+ public static void source() {
+ new Target1();
+ new Target2();
+ new Target3();
+
+ Target.missingField1 = 1;
+ Target.missingField2 = 2;
+ Target.missingMethod();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
index 8b59d47..1387c3f 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
@@ -64,10 +64,7 @@
}
}
- @Test
- public void missingClassReferenced() throws Throwable {
- Path sourceDex = testForD8(Backend.DEX).addProgramClasses(Source.class).compile().writeToZip();
-
+ private void missingClassReferenced(Path sourceDex) throws Throwable {
DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
MissingReferencesConsumer consumer = new MissingReferencesConsumer();
@@ -89,14 +86,22 @@
}
@Test
- public void missingFieldAndMethodReferenced() throws Throwable {
- Path sourceDex =
+ public void missingClassReferencedInDexArchive() throws Throwable {
+ missingClassReferenced(
+ testForD8(Backend.DEX).addProgramClasses(Source.class).compile().writeToZip());
+ }
+
+ @Test
+ public void missingClassReferencedInDexFile() throws Throwable {
+ missingClassReferenced(
testForD8(Backend.DEX)
.addProgramClasses(Source.class)
- .addProgramClassFileData(getClassWithTargetRemoved())
.compile()
- .writeToZip();
+ .writeToDirectory()
+ .resolve("classes.dex"));
+ }
+ private void missingFieldAndMethodReferenced(Path sourceDex) throws Throwable {
DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
MissingReferencesConsumer consumer = new MissingReferencesConsumer();
@@ -117,6 +122,27 @@
assertTrue(consumer.acceptMethodCalled);
}
+ @Test
+ public void missingFieldAndMethodReferencedInDexArchive() throws Throwable {
+ missingFieldAndMethodReferenced(
+ testForD8(Backend.DEX)
+ .addProgramClasses(Source.class)
+ .addProgramClassFileData(getClassWithTargetRemoved())
+ .compile()
+ .writeToZip());
+ }
+
+ @Test
+ public void missingFieldAndMethodReferencedInDexFile() throws Throwable {
+ missingFieldAndMethodReferenced(
+ testForD8(Backend.DEX)
+ .addProgramClasses(Source.class)
+ .addProgramClassFileData(getClassWithTargetRemoved())
+ .compile()
+ .writeToDirectory()
+ .resolve("classes.dex"));
+ }
+
private byte[] getClassWithTargetRemoved() throws IOException {
return transformer(Target.class)
.removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("target"))
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
new file mode 100644
index 0000000..c113b0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, 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.utils.codeinspector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.utils.ThrowingAction;
+import java.util.function.Consumer;
+
+public class AssertUtils {
+
+ public static <E extends Throwable> void assertFailsCompilationIf(
+ boolean condition, ThrowingAction<E> action) throws E {
+ assertFailsCompilationIf(condition, action, null);
+ }
+
+ public static <E extends Throwable> void assertFailsCompilationIf(
+ boolean condition, ThrowingAction<E> action, Consumer<Throwable> consumer) throws E {
+ assertThrowsIf(condition, CompilationFailedException.class, action, consumer);
+ }
+
+ public static <E extends Throwable> void assertThrowsIf(
+ boolean condition, Class<? extends Throwable> clazz, ThrowingAction<E> action) throws E {
+ assertThrowsIf(condition, clazz, action, null);
+ }
+
+ public static <E extends Throwable> void assertThrowsIf(
+ boolean condition,
+ Class<? extends Throwable> clazz,
+ ThrowingAction<E> action,
+ Consumer<Throwable> consumer)
+ throws E {
+ if (condition) {
+ try {
+ action.execute();
+ fail("Expected action to fail with an exception, but succeeded");
+ } catch (Throwable e) {
+ assertEquals(clazz, e.getClass());
+ if (consumer != null) {
+ consumer.accept(e);
+ }
+ }
+ } else {
+ action.execute();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 29bebe1..c2a8cc8 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -100,6 +100,10 @@
};
}
+ public static Matcher<Subject> isAbsent() {
+ return not(isPresent());
+ }
+
public static Matcher<Subject> isPresent() {
return new TypeSafeMatcher<Subject>() {
@Override
diff --git a/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1 b/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1
new file mode 100644
index 0000000..175f01a
--- /dev/null
+++ b/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1
@@ -0,0 +1 @@
+51a99d63f0449fbe61d077ff5edbec397dd06a52
\ No newline at end of file
diff --git a/third_party/opensource-apps/anexplorer.tar.gz.sha1 b/third_party/opensource-apps/anexplorer.tar.gz.sha1
new file mode 100644
index 0000000..8ea778f
--- /dev/null
+++ b/third_party/opensource-apps/anexplorer.tar.gz.sha1
@@ -0,0 +1 @@
+e535845be72fed0ecf4ff622ccd7feeb59deea9f
\ No newline at end of file
diff --git a/third_party/opensource-apps/antennapod.tar.gz.sha1 b/third_party/opensource-apps/antennapod.tar.gz.sha1
new file mode 100644
index 0000000..fdb23b0
--- /dev/null
+++ b/third_party/opensource-apps/antennapod.tar.gz.sha1
@@ -0,0 +1 @@
+6819cb7dff48bfe09837a06aa2e9fd9fe06bcff5
\ No newline at end of file
diff --git a/tools/adb.py b/tools/adb.py
new file mode 100644
index 0000000..c6cece7
--- /dev/null
+++ b/tools/adb.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# Copyright (c) 2020, 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.
+
+import subprocess
+import time
+import utils
+
+def install_apk_on_emulator(apk, emulator_id, quiet=False):
+ cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk]
+ if quiet:
+ subprocess.check_output(cmd)
+ else:
+ subprocess.check_call(cmd)
+
+
+def uninstall_apk_on_emulator(app_id, emulator_id):
+ process = subprocess.Popen(
+ ['adb', '-s', emulator_id, 'uninstall', app_id],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = process.communicate()
+
+ if stdout.strip() == 'Success':
+ # Successfully uninstalled
+ return
+
+ if 'Unknown package: {}'.format(app_id) in stderr:
+ # Application not installed
+ return
+
+ # Check if the app is listed in packages
+ packages = subprocess.check_output(['adb', 'shell', 'pm', 'list', 'packages'])
+ if not 'package:' + app_id in packages:
+ return
+
+ raise Exception(
+ 'Unexpected result from `adb uninstall {}\nStdout: {}\nStderr: {}'.format(
+ app_id, stdout, stderr))
+
+
+def wait_for_emulator(emulator_id):
+ stdout = subprocess.check_output(['adb', 'devices'])
+ if '{}\tdevice'.format(emulator_id) in stdout:
+ return True
+
+ print('Emulator \'{}\' not connected; waiting for connection'.format(
+ emulator_id))
+
+ time_waited = 0
+ while True:
+ time.sleep(10)
+ time_waited += 10
+ stdout = subprocess.check_output(['adb', 'devices'])
+ if '{}\tdevice'.format(emulator_id) not in stdout:
+ print('... still waiting for connection')
+ if time_waited >= 5 * 60:
+ return False
+ else:
+ return True
+
+
+def run_monkey(app_id, emulator_id, apk, monkey_events, quiet, enable_logging):
+ if not wait_for_emulator(emulator_id):
+ return False
+
+ install_apk_on_emulator(apk, emulator_id, quiet)
+
+ # Intentionally using a constant seed such that the monkey generates the same
+ # event sequence for each shrinker.
+ random_seed = 42
+
+ cmd = ['adb', '-s', emulator_id, 'shell', 'monkey', '-p', app_id,
+ '-s', str(random_seed), str(monkey_events)]
+
+ try:
+ stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
+ succeeded = ('Events injected: {}'.format(monkey_events) in stdout)
+ except subprocess.CalledProcessError as e:
+ succeeded = False
+
+ uninstall_apk_on_emulator(app_id, emulator_id)
+
+ return succeeded
+
+
+def run_instrumented(app_id, test_id, emulator_id, apk, test_apk, quiet,
+ enable_logging,
+ test_runner='androidx.test.runner.AndroidJUnitRunner'):
+ if not wait_for_emulator(emulator_id):
+ return None
+
+ install_apk_on_emulator(apk, emulator_id, quiet)
+ install_apk_on_emulator(test_apk, emulator_id, quiet)
+
+ cmd = ['adb', '-s', emulator_id, 'shell', 'am', 'instrument', '-w',
+ '{}/{}'.format(test_id, test_runner)]
+
+ try:
+ stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
+ # The runner will print OK (X tests) if completed succesfully
+ succeeded = any("OK (" in s for s in stdout)
+ except subprocess.CalledProcessError as e:
+ succeeded = False
+
+ uninstall_apk_on_emulator(test_id, emulator_id)
+ uninstall_apk_on_emulator(app_id, emulator_id)
+
+ return succeeded
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6d7359b..48c596b 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -281,6 +281,8 @@
if dump.desugared_library_json():
cmd.extend(['--desugared-lib', dump.desugared_library_json()])
if compiler != 'd8' and dump.config_file():
+ if hasattr(args, 'config_file_consumer') and args.config_file_consumer:
+ args.config_file_consumer(dump.config_file())
cmd.extend(['--pg-conf', dump.config_file()])
if compiler != 'd8':
cmd.extend(['--pg-map-output', '%s.map' % out])
diff --git a/tools/r8_release.py b/tools/r8_release.py
index cc4bebd..3e743b3 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -10,7 +10,7 @@
import subprocess
import sys
import urllib
-import xml
+import xml.etree.ElementTree
import zipfile
import utils
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 04ca397..af6d337 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -1,8 +1,10 @@
#!/usr/bin/env python
-# Copyright (c) 2020, 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.
+# Copyright (c) 2020, 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.
+import adb
+import apk_masseur
import compiledump
import gradle
import jdk
@@ -22,7 +24,6 @@
def __getattr__(self, name):
return self.get(name, None)
-
# To generate the files for a new app, navigate to the app source folder and
# run:
# ./gradlew clean :app:assembleRelease -Dcom.android.tools.r8.dumpinputtodirectory=<path>
@@ -38,6 +39,8 @@
'id': None,
'name': None,
'dump_app': None,
+ 'apk_app': None,
+ 'apk_test': None,
'skip': False,
'url': None, # url is not used but nice to have for updating apps
'revision': None,
@@ -51,21 +54,80 @@
APPS = [
App({
+ 'id': 'com.numix.calculator',
+ 'name': 'Calculator',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-release.apk',
+ # Compiling tests fail: Library class android.content.res.XmlResourceParser
+ # implements program class org.xmlpull.v1.XmlPullParser. Nothing to really
+ # do about that.
+ 'id_test': 'com.numix.calculator.test',
+ 'dump_test': 'dump_test.zip',
+ 'apk_test': 'app-release-androidTest.apk',
+ 'url': 'https://github.com/numixproject/android-suite/tree/master/Calculator',
+ 'revision': 'f58e1b53f7278c9b675d5855842c6d8a44cccb1f',
+ 'folder': 'android-suite-calculator',
+ }),
+ App({
+ 'id': 'dev.dworks.apps.anexplorer.pro',
+ 'name': 'AnExplorer',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
+ 'url': 'https://github.com/christofferqa/AnExplorer',
+ 'revision': '365927477b8eab4052a1882d5e358057ae3dee4d',
+ 'folder': 'anexplorer',
+ }),
+ App({
+ 'id': 'de.danoeh.antennapod',
+ 'name': 'AntennaPod',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-free-release.apk',
+ # TODO(b/172452102): Tests and monkey do not work
+ 'id_test': 'de.danoeh.antennapod.test',
+ 'dump_test': 'dump_test.zip',
+ 'apk_test': 'app-free-release-androidTest.apk',
+ 'url': 'https://github.com/christofferqa/AntennaPod.git',
+ 'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06',
+ 'folder': 'antennapod',
+ # TODO(b/172450929): Fix recompilation
+ 'skip_recompilation': True
+ }),
+ App({
'id': 'com.example.applymapping',
'name': 'applymapping',
'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-release.apk',
+ 'id_test': 'com.example.applymapping.test',
+ 'dump_test': 'dump_test.zip',
+ 'apk_test': 'app-release-androidTest.apk',
'url': 'https://github.com/mkj-gram/applymapping',
'revision': 'e3ae14b8c16fa4718e5dea8f7ad00937701b3c48',
'folder': 'applymapping',
- 'skip_recompilation': True,
})
]
+def remove_print_lines(file):
+ with open(file) as f:
+ lines = f.readlines()
+ with open(file, 'w') as f:
+ for line in lines:
+ if '-printconfiguration' not in line:
+ f.write(line)
+
+
def download_app(app_sha):
utils.DownloadFromGoogleCloudStorage(app_sha)
+def is_logging_enabled_for(app, options):
+ if options.no_logging:
+ return False
+ if options.app_logging_filter and app.name not in options.app_logging_filter:
+ return False
+ return True
+
+
def is_minified_r8(shrinker):
return '-nolib' not in shrinker
@@ -87,6 +149,10 @@
return os.path.join(app_dir, app.dump_app)
+def dump_test_for_app(app_dir, app):
+ return os.path.join(app_dir, app.dump_test)
+
+
def get_results_for_app(app, options, temp_dir):
app_folder = app.folder if app.folder else app.name + "_" + app.revision
app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
@@ -146,11 +212,11 @@
for compilation_step in range(0, compilation_steps):
if status != 'success':
break
- print('Compiling {} of {}'.format(compilation_step, compilation_steps))
+ print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
result = {}
try:
start = time.time()
- (app_jar, new_recomp_jar) = \
+ (app_jar, mapping, new_recomp_jar) = \
build_app_with_shrinker(
app, options, temp_dir, app_dir, shrinker, compilation_step,
compilation_steps, recomp_jar)
@@ -159,13 +225,13 @@
result['build_status'] = 'success'
result['recompilation_status'] = 'success'
result['output_jar'] = app_jar
+ result['output_mapping'] = mapping
result['dex_size'] = dex_size
result['duration'] = int((end - start) * 1000) # Wall time
if (new_recomp_jar is None
and not is_last_build(compilation_step, compilation_steps)):
result['recompilation_status'] = 'failed'
warn('Failed to build {} with {}'.format(app.name, shrinker))
- results.append(result)
break
recomp_jar = new_recomp_jar
except Exception as e:
@@ -175,7 +241,51 @@
result['build_status'] = 'failed'
status = 'failed'
+ original_app_apk = os.path.join(app_dir, app.apk_app)
+ app_apk_destination = os.path.join(
+ temp_dir,"{}_{}.apk".format(app.id, compilation_step))
+
+ if result.get('build_status') == 'success' and options.monkey:
+ # Make a copy of the given APK, move the newly generated dex files into the
+ # copied APK, and then sign the APK.
+ apk_masseur.masseur(
+ original_app_apk, dex=app_jar, resources='META-INF/services/*',
+ out=app_apk_destination,
+ quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+ keystore=options.keystore)
+
+ result['monkey_status'] = 'success' if adb.run_monkey(
+ app.id, options.emulator_id, app_apk_destination, options.monkey_events,
+ options.quiet, is_logging_enabled_for(app, options)) else 'failed'
+
+ if result.get('build_status') == 'success' and options.run_tests:
+ if not os.path.isfile(app_apk_destination):
+ apk_masseur.masseur(
+ original_app_apk, dex=app_jar, resources='META-INF/services/*',
+ out=app_apk_destination,
+ quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+ keystore=options.keystore)
+
+ # Compile the tests with the mapping file.
+ test_jar = build_test_with_shrinker(
+ app, options, temp_dir, app_dir,shrinker, compilation_step,
+ result['output_mapping'])
+ original_test_apk = os.path.join(app_dir, app.apk_test)
+ test_apk_destination = os.path.join(
+ temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
+ apk_masseur.masseur(
+ original_test_apk, dex=test_jar, resources='META-INF/services/*',
+ out=test_apk_destination,
+ quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+ keystore=options.keystore)
+ result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
+ app.id, app.id_test, options.emulator_id, app_apk_destination,
+ test_apk_destination, options.quiet,
+ is_logging_enabled_for(app, options)) else 'failed'
+
results.append(result)
+ if result.get('recompilation_status') != 'success':
+ break
def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker,
@@ -192,18 +302,25 @@
'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
'debug_agent': options.debug_agent,
'program_jar': prev_recomp_jar,
- 'nolib': not is_minified_r8(shrinker)
+ 'nolib': not is_minified_r8(shrinker),
+ 'config_file_consumer': remove_print_lines,
})
- out_jar = os.path.join(temp_dir, "out.jar")
compile_result = compiledump.run1(temp_dir, args, [])
+
+ out_jar = os.path.join(temp_dir, "out.jar")
+ out_mapping = os.path.join(temp_dir, "out.jar.map")
app_jar = os.path.join(
temp_dir, '{}_{}_{}_dex_out.jar'.format(
app.name, shrinker, compilation_step_index))
+ app_mapping = os.path.join(
+ temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
+ app.name, shrinker, compilation_step_index))
if compile_result != 0 or not os.path.isfile(out_jar):
assert False, "Compilation of app_jar failed"
shutil.move(out_jar, app_jar)
+ shutil.move(out_mapping, app_mapping)
recomp_jar = None
if compilation_step_index < compilation_steps - 1:
@@ -216,7 +333,48 @@
app.name, shrinker, compilation_step_index))
shutil.move(out_jar, recomp_jar)
- return (app_jar, recomp_jar)
+ return (app_jar, app_mapping, recomp_jar)
+
+def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
+ compilation_step_index, mapping):
+ r8jar = os.path.join(
+ temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
+
+ def rewrite_file(file):
+ remove_print_lines(file)
+ with open(file) as f:
+ lines = f.readlines()
+ with open(file, 'w') as f:
+ for line in lines:
+ if '-applymapping' not in line:
+ f.write(line + '\n')
+ f.write("-applymapping " + mapping + '\n')
+
+ args = AttrDict({
+ 'dump': dump_test_for_app(app_dir, app),
+ 'r8_jar': r8jar,
+ 'ea': False if options.disable_assertions else True,
+ 'version': 'master',
+ 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
+ 'debug_agent': options.debug_agent,
+ 'nolib': not is_minified_r8(shrinker),
+ # The config file will have an -applymapping reference to an old map.
+ # Update it to point to mapping file build in the compilation of the app.
+ 'config_file_consumer': rewrite_file
+ })
+
+ compile_result = compiledump.run1(temp_dir, args, [])
+
+ out_jar = os.path.join(temp_dir, "out.jar")
+ test_jar = os.path.join(
+ temp_dir, '{}_{}_{}_test_out.jar'.format(
+ app.name, shrinker, compilation_step_index))
+
+ if compile_result != 0 or not os.path.isfile(out_jar):
+ assert False, "Compilation of test_jar failed"
+ shutil.move(out_jar, test_jar)
+
+ return test_jar
def log_results_for_apps(result_per_shrinker_per_app, options):
@@ -288,6 +446,21 @@
else:
print(msg)
+ if options.monkey:
+ monkey_status = result.get('monkey_status')
+ if monkey_status != 'success':
+ app_error = True
+ warn(' monkey: {}'.format(monkey_status))
+ else:
+ success(' monkey: {}'.format(monkey_status))
+
+ if options.run_tests and 'instrumentation_test_status' in result:
+ test_status = result.get('instrumentation_test_status')
+ if test_status != 'success':
+ warn(' instrumentation_tests: {}'.format(test_status))
+ else:
+ success(' instrumentation_tests: {}'.format(test_status))
+
recompilation_status = result.get('recompilation_status', '')
if recompilation_status == 'failed':
app_error = True
@@ -319,6 +492,9 @@
help='Disable assertions when compiling',
default=False,
action='store_true')
+ result.add_option('--emulator-id', '--emulator_id',
+ help='Id of the emulator to use',
+ default='emulator-5554')
result.add_option('--golem',
help='Running on golem, do not download',
default=False,
@@ -421,7 +597,6 @@
options.quiet = True
options.no_logging = True
-
with utils.TempDir() as temp_dir:
if options.hash:
# Download r8-<hash>.jar from