/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Tests for detecting throwing methods for code sinking. */ public class Main { // // Some "runtime library" methods. // static private void doThrow(String par) { throw new Error("you are null: " + par); } static private void checkNotNullDirect(Object obj, String par) { if (obj == null) throw new Error("you are null: " + par); } static private void checkNotNullSplit(Object obj, String par) { if (obj == null) doThrow(par); } static private void checkNotNullSplitAlt(Object obj, String par) { if (obj != null) return; doThrow(par); } // // Various ways of enforcing non-null parameter. // In all cases, par should be subject to code sinking. // /// CHECK-START: void Main.doit1(int[]) code_sinking (before) /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: <<Tst:z\d+>> Equal /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] /// CHECK: Throw /// CHECK: end_block // /// CHECK-START: void Main.doit1(int[]) code_sinking (after) /// CHECK: begin_block /// CHECK: <<Tst:z\d+>> Equal /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] /// CHECK: Throw /// CHECK: end_block static public void doit1(int[] a) { String par = "a"; if (a == null) throw new Error("you are null: " + par); for (int i = 0; i < a.length; i++) { a[i] = 1; } } /// CHECK-START: void Main.doit2(int[]) code_sinking (before) /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: <<Tst:z\d+>> NotEqual /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow /// CHECK: end_block // /// CHECK-START: void Main.doit2(int[]) code_sinking (after) /// CHECK: begin_block /// CHECK: <<Tst:z\d+>> NotEqual /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow /// CHECK: end_block static public void doit2(int[] a) { String par = "a"; if (a == null) doThrow(par); for (int i = 0; i < a.length; i++) { a[i] = 2; } } /// CHECK-START: void Main.doit3(int[]) code_sinking (before) /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: <<Tst:z\d+>> Equal /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] /// CHECK: Throw /// CHECK: end_block // /// CHECK-START: void Main.doit3(int[]) code_sinking (after) /// CHECK: begin_block /// CHECK: <<Tst:z\d+>> Equal /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] /// CHECK: Throw /// CHECK: end_block static public void doit3(int[] a) { String par = "a"; checkNotNullDirect(a, par); for (int i = 0; i < a.length; i++) { a[i] = 3; } } /// CHECK-START: void Main.doit4(int[]) code_sinking (before) /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: <<Tst:z\d+>> NotEqual /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow /// CHECK: end_block // /// CHECK-START: void Main.doit4(int[]) code_sinking (after) /// CHECK: begin_block /// CHECK: <<Tst:z\d+>> NotEqual /// CHECK: If [<<Tst>>] /// CHECK: end_block /// CHECK: begin_block /// CHECK: <<Str:l\d+>> LoadString /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow /// CHECK: end_block static public void doit4(int[] a) { String par = "a"; checkNotNullSplit(a, par); // resembles Kotlin runtime lib // (test is lined, doThrow is not) for (int i = 0; i < a.length; i++) { a[i] = 4; } } // Ensures Phi values are merged properly. static public int doit5(int[] a) { int t = 100; String par = "a"; if (a == null) { doThrow(par); } else { t = 1000; } for (int i = 0; i < a.length; i++) { a[i] = 5; } // Phi on t, even though doThrow never reaches. return t; } // // Various ways of exploiting non-null parameter. // In all cases, implicit null checks are redundant. // /// CHECK-START: int Main.deleteNullCheck(int[]) dead_code_elimination$after_inlining (before) /// CHECK: <<Par:l\d+>> ParameterValue /// CHECK: <<Zero:i\d+>> IntConstant 0 /// CHECK: <<Null:l\d+>> NullCheck [<<Par>>] /// CHECK: <<Len:i\d+>> ArrayLength [<<Null>>] /// CHECK: <<Check:i\d+>> BoundsCheck [<<Zero>>,<<Len>>] /// CHECK: <<Get:i\d+>> ArrayGet [<<Null>>,<<Check>>] /// CHECK: Return [<<Get>>] // /// CHECK-START: int Main.deleteNullCheck(int[]) dead_code_elimination$after_inlining (after) /// CHECK: <<Par:l\d+>> ParameterValue /// CHECK: <<Zero:i\d+>> IntConstant 0 /// CHECK: <<BT:l\d+>> BoundType [<<Par>>] /// CHECK: <<Len:i\d+>> ArrayLength [<<BT>>] /// CHECK: <<Check:i\d+>> BoundsCheck [<<Zero>>,<<Len>>] /// CHECK: <<Get:i\d+>> ArrayGet [<<BT>>,<<Check>>] /// CHECK: Return [<<Get>>] // /// CHECK-START: int Main.deleteNullCheck(int[]) dead_code_elimination$after_inlining (after) /// CHECK-NOT: NullCheck static public int deleteNullCheck(int[] a) { checkNotNullSplit(a, "a"); return a[0]; } /// CHECK-START: int Main.deleteNullCheckAlt(int[]) dead_code_elimination$after_inlining (before) /// CHECK: NullCheck // /// CHECK-START: int Main.deleteNullCheckAlt(int[]) dead_code_elimination$after_inlining (after) /// CHECK-NOT: NullCheck static public int deleteNullCheckAlt(int[] a) { checkNotNullSplitAlt(a, "a"); return a[0]; } /// CHECK-START: int Main.deleteNullChecks3(int[], int[], int[]) dead_code_elimination$after_inlining (before) /// CHECK: NullCheck /// CHECK: NullCheck /// CHECK: NullCheck // /// CHECK-START: int Main.deleteNullChecks3(int[], int[], int[]) dead_code_elimination$after_inlining (after) /// CHECK-NOT: NullCheck static public int deleteNullChecks3(int[] a, int[] b, int[] c) { checkNotNullSplit(a, "a"); checkNotNullSplit(b, "b"); checkNotNullSplit(c, "c"); return a[0] + b[0] + c[0]; } // // Test driver. // static public void main(String[] args) { int[] a = new int[100]; for (int i = 0; i < 100; i++) { a[i] = 0; } try { doit1(null); System.out.println("should not reach this!"); } catch (Error e) { doit1(a); } for (int i = 0; i < 100; i++) { expectEquals(1, a[i]); } try { doit2(null); System.out.println("should not reach this!"); } catch (Error e) { doit2(a); } for (int i = 0; i < 100; i++) { expectEquals(2, a[i]); } try { doit3(null); System.out.println("should not reach this!"); } catch (Error e) { doit3(a); } for (int i = 0; i < 100; i++) { expectEquals(3, a[i]); } try { doit4(null); System.out.println("should not reach this!"); } catch (Error e) { doit4(a); } for (int i = 0; i < 100; i++) { expectEquals(4, a[i]); } try { doit5(null); System.out.println("should not reach this!"); } catch (Error e) { expectEquals(1000, doit5(a)); } for (int i = 0; i < 100; i++) { expectEquals(5, a[i]); } int[] x = { 11 } ; expectEquals(11, deleteNullCheck(x)); int[] y = { 55 } ; int[] z = { 22 } ; expectEquals(88, deleteNullChecks3(x, y, z)); try { deleteNullCheck(null); System.out.println("should not reach this!"); } catch (Error e) { } System.out.println("passed"); } private static void expectEquals(int expected, int result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } }