| // Copyright (c) 2019, 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.rewrite.arrays; |
| |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| 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.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| @RunWith(Parameterized.class) |
| public class ArrayLengthRewriteTest extends TestBase { |
| @Parameters(name = "{0}, debug = {1}") |
| public static Iterable<?> data() { |
| return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values()); |
| } |
| |
| private final TestParameters parameters; |
| private final boolean debugMode; |
| |
| public ArrayLengthRewriteTest(TestParameters parameters, boolean debugMode) { |
| this.parameters = parameters; |
| this.debugMode = debugMode; |
| } |
| |
| private static final String[] expectedOutput = { |
| "boolean 1", |
| "byte 2", |
| "char 3", |
| "double 4", |
| "float 5", |
| "int 6", |
| "long 7", |
| "short 8", |
| "class java.lang.String 1", |
| "class java.lang.Object 2", |
| "class java.lang.String 3", |
| "NPE", |
| "class java.lang.Object 2", |
| "boolean 1", |
| "class java.lang.String 0", |
| "class java.lang.String 1", |
| "class java.lang.String 2", |
| "class java.lang.String 1", |
| "class java.lang.String 1", |
| }; |
| |
| @Test public void d8() throws Exception { |
| assumeTrue(parameters.isDexRuntime()); |
| |
| testForD8() |
| .setMinApi(parameters.getRuntime()) |
| .setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE) |
| .addProgramClasses(Main.class) |
| .run(parameters.getRuntime(), Main.class) |
| .assertSuccessWithOutputLines(expectedOutput) |
| .inspect(this::inspect); |
| } |
| |
| @Test public void r8() throws Exception { |
| testForR8(parameters.getBackend()) |
| .setMinApi(parameters.getRuntime()) |
| .setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE) |
| .addProgramClasses(Main.class) |
| .addKeepMainRule(Main.class) |
| .enableInliningAnnotations() |
| .run(parameters.getRuntime(), Main.class) |
| .assertSuccessWithOutputLines(expectedOutput) |
| .inspect(this::inspect); |
| } |
| |
| private void inspect(CodeInspector inspector) { |
| ClassSubject mainClass = inspector.clazz(Main.class); |
| assertTrue(mainClass.isPresent()); |
| |
| MethodSubject primitives = mainClass.uniqueMethodWithName("primitives"); |
| assertArrayLengthCallCount(primitives, debugMode ? 8 : 0); |
| |
| MethodSubject nonNullReferences = mainClass.uniqueMethodWithName("nonNullReferences"); |
| assertArrayLengthCallCount(nonNullReferences, debugMode ? 3 : 0); |
| |
| // No assertion on nullReference() because it's seen as always throwing an NPE and |
| // the array-length instruction is removed. The output check validates behavior. |
| |
| MethodSubject argument = mainClass.uniqueMethodWithName("argument"); |
| assertArrayLengthCallCount(argument, 1); |
| |
| MethodSubject phi = mainClass.uniqueMethodWithName("phi"); |
| assertArrayLengthCallCount(phi, 1); |
| |
| // TODO(139489070): these should be rewritten and result in 0 array-length bytecodes |
| MethodSubject staticConstants = mainClass.uniqueMethodWithName("staticConstants"); |
| assertArrayLengthCallCount(staticConstants, 3); |
| |
| MethodSubject staticNonConstants = mainClass.uniqueMethodWithName("staticNonConstants"); |
| assertArrayLengthCallCount(staticNonConstants, 2); |
| } |
| |
| private static void assertArrayLengthCallCount(MethodSubject subject, int expected) { |
| assertTrue(subject.isPresent()); |
| long actual = subject.streamInstructions() |
| .filter(InstructionSubject::isArrayLength) |
| .count(); |
| assertEquals(expected, actual); |
| } |
| |
| public static final class Main { |
| public static void main(String[] args) { |
| primitives(); |
| nonNullReferences(); |
| nullReferences(); |
| argument("one", "two"); |
| phi(); |
| staticConstants(); |
| staticNonConstants(); |
| } |
| |
| @NeverInline |
| private static void primitives() { |
| boolean[] booleanArray = new boolean[1]; |
| byte[] byteArray = new byte[2]; |
| char[] charArray = new char[3]; |
| double[] doubleArray = new double[4]; |
| float[] floatArray = new float[5]; |
| int[] intArray = new int[6]; |
| long[] longArray = new long[7]; |
| short[] shortArray = new short[8]; |
| |
| System.out.println(booleanArray.getClass().getComponentType() + " " + booleanArray.length); |
| System.out.println(byteArray.getClass().getComponentType() + " " + byteArray.length); |
| System.out.println(charArray.getClass().getComponentType() + " " + charArray.length); |
| System.out.println(doubleArray.getClass().getComponentType() + " " + doubleArray.length); |
| System.out.println(floatArray.getClass().getComponentType() + " " + floatArray.length); |
| System.out.println(intArray.getClass().getComponentType() + " " + intArray.length); |
| System.out.println(longArray.getClass().getComponentType() + " " + longArray.length); |
| System.out.println(shortArray.getClass().getComponentType() + " " + shortArray.length); |
| } |
| |
| @NeverInline |
| private static void nonNullReferences() { |
| String[] stringArray = new String[1]; |
| Object[] objectArray = new Object[2]; |
| Object[] stringAsObjectArray = new String[3]; |
| |
| System.out.println(stringArray.getClass().getComponentType() + " " + stringArray.length); |
| System.out.println(objectArray.getClass().getComponentType() + " " + objectArray.length); |
| System.out.println( |
| stringAsObjectArray.getClass().getComponentType() + " " + stringAsObjectArray.length); |
| } |
| |
| @NeverInline |
| private static void nullReferences() { |
| String[] nullArray = null; |
| try { |
| System.out.println(nullArray.length); |
| } catch (NullPointerException expected) { |
| System.out.println("NPE"); |
| } |
| } |
| |
| @NeverInline |
| private static void argument(Object... input) { |
| System.out.println(input.getClass().getComponentType() + " " + input.length); |
| } |
| |
| @NeverInline |
| private static void phi() { |
| boolean[] booleanArray; |
| if (System.nanoTime() > 0) { |
| booleanArray = new boolean[1]; |
| } else { |
| booleanArray = new boolean[2]; |
| } |
| System.out.println(booleanArray.getClass().getComponentType() + " " + booleanArray.length); |
| } |
| |
| private static final String[] zero = {}; |
| private static final String[] one = { "one" }; |
| private static final String[] two = { "one", "two" }; |
| |
| @NeverInline |
| private static void staticConstants() { |
| System.out.println(zero.getClass().getComponentType() + " " + zero.length); |
| System.out.println(one.getClass().getComponentType() + " " + one.length); |
| System.out.println(two.getClass().getComponentType() + " " + two.length); |
| } |
| |
| private static String[] mutable = { "one" }; |
| private static final String[] runtimeInit = { System.lineSeparator() }; |
| |
| @NeverInline |
| private static void staticNonConstants() { |
| System.out.println(mutable.getClass().getComponentType() + " " + mutable.length); |
| System.out.println(runtimeInit.getClass().getComponentType() + " " + runtimeInit.length); |
| } |
| } |
| } |