Optimize empty Object[] -> null in reflection APIs
Performs conversion for:
* Class.getConstructor()
* Class.getDeclaredConstructor()
* Class.getMethod()
* Class.getDeclaredMethod()
* Method.invoke()
* Constructor.newInstance()
Bug: b/316155816
Change-Id: Iafce1fdd7204fe7d4372c5ab9c0b21f5eccb2d83
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 39c4168..1c568cd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -675,6 +675,7 @@
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
public final ConstructorMethods constructorMethods = new ConstructorMethods();
+ public final MethodMethods methodMethods = new MethodMethods();
public final EnumMembers enumMembers = new EnumMembers();
public final AndroidUtilLogMembers androidUtilLogMembers = new AndroidUtilLogMembers();
public final JavaLangReflectArrayMembers javaLangReflectArrayMembers =
@@ -1946,6 +1947,15 @@
}
}
+ public class MethodMethods {
+
+ public final DexMethod invoke =
+ createMethod(
+ methodType, createProto(objectType, objectType, objectArrayType), invokeMethodName);
+
+ private MethodMethods() {}
+ }
+
public class AndroidUtilLogMembers {
public final DexMethod i =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
new file mode 100644
index 0000000..db81e4b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class ClassOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod getConstructor;
+ private final DexMethod getDeclaredConstructor;
+ private final DexMethod getMethod;
+ private final DexMethod getDeclaredMethod;
+
+ ClassOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ getConstructor = dexItemFactory.classMethods.getConstructor;
+ getDeclaredConstructor = dexItemFactory.classMethods.getDeclaredConstructor;
+ getMethod = dexItemFactory.classMethods.getMethod;
+ getDeclaredMethod = dexItemFactory.classMethods.getDeclaredMethod;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.classType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(getConstructor)
+ || singleTargetReference.isIdenticalTo(getDeclaredConstructor)
+ || singleTargetReference.isIdenticalTo(getMethod)
+ || singleTargetReference.isIdenticalTo(getDeclaredMethod)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getLastArgument(), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java
new file mode 100644
index 0000000..718ed8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class ConstructorOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod newInstance;
+
+ ConstructorOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ newInstance = dexItemFactory.constructorMethods.newInstance;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.constructorType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(newInstance)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getArgument(1), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java
new file mode 100644
index 0000000..a81a0c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class EmptyVarargsUtil {
+ public static void replaceWithNullIfEmptyArray(
+ Value value,
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InternalOptions options,
+ AffectedValues affectedValues) {
+ if (value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)
+ && value.definition.asNewArrayEmpty().sizeIfConst() == 0) {
+ instructionIterator.previous();
+ value.replaceUsers(
+ instructionIterator.insertConstNullInstruction(code, options), affectedValues);
+ instructionIterator.next();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 1dc3ca2..1ea01a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -46,6 +46,9 @@
this.appView = appView;
timing.begin("Register optimizers");
PrimitiveMethodOptimizer.forEachPrimitiveOptimizer(appView, this::register);
+ register(new ClassOptimizer(appView));
+ register(new ConstructorOptimizer(appView));
+ register(new MethodOptimizer(appView));
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
register(new StringBuilderMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java
new file mode 100644
index 0000000..78b0149
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class MethodOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod invoke;
+
+ MethodOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ invoke = dexItemFactory.methodMethods.invoke;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.methodType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(this.invoke)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getArgument(2), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
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 4cdfc22..63e6bbb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5177,7 +5177,9 @@
int parametersSize =
newArrayEmpty != null
? newArrayEmpty.sizeIfConst()
- : newArrayFilled != null ? newArrayFilled.size() : -1;
+ : newArrayFilled != null
+ ? newArrayFilled.size()
+ : parametersValue.isAlwaysNull(appView) ? 0 : -1;
if (parametersSize < 0) {
return;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java
new file mode 100644
index 0000000..2384387
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2023, 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.library;
+
+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.Keep;
+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 com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyVarargsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EmptyVarargsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepAnnotation()
+ .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testMethod =
+ mainClassSubject.uniqueMethodWithOriginalName("testDeclared");
+ assertTrue(testMethod.streamInstructions().noneMatch(InstructionSubject::isNewArray));
+ testMethod = mainClassSubject.uniqueMethodWithOriginalName("testNonDeclared");
+ assertTrue(testMethod.streamInstructions().noneMatch(InstructionSubject::isNewArray));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("hi", "hi");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ try {
+ testDeclared();
+ testNonDeclared();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Keep
+ public Main() {}
+
+ @Keep
+ public void doPrint() {
+ System.out.println("hi");
+ }
+
+ @NeverInline
+ static void testNonDeclared() throws Exception {
+ Constructor<Main> ctor = Main.class.getConstructor();
+ Main instance = ctor.newInstance();
+ Method method = Main.class.getMethod("doPrint");
+ method.invoke(instance);
+ }
+
+ @NeverInline
+ static void testDeclared() throws Exception {
+ Constructor<Main> ctor = Main.class.getDeclaredConstructor();
+ Main instance = ctor.newInstance();
+ Method method = Main.class.getDeclaredMethod("doPrint");
+ method.invoke(instance);
+ }
+ }
+}