/* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8066103 * @summary C2's range check smearing allows out of bound array accesses * @library /testlibrary /../../test/lib /compiler/whitebox * @build TestRangeCheckSmearing * @run main ClassFileInstaller sun.hotspot.WhiteBox * @run main ClassFileInstaller com.oracle.java.testlibrary.Platform * @run main/othervm -ea -Xmixed -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:-BackgroundCompilation -XX:-UseOnStackReplacement TestRangeCheckSmearing * */ import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; import sun.hotspot.WhiteBox; import sun.hotspot.code.NMethod; import com.oracle.java.testlibrary.Platform; public class TestRangeCheckSmearing { private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); @Retention(RetentionPolicy.RUNTIME) @interface Args { int[] value(); } // first range check is i + max of all constants @Args({0, 8}) static int m1(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+9]; if (allaccesses) { res += array[i+8]; res += array[i+7]; res += array[i+6]; res += array[i+5]; res += array[i+4]; res += array[i+3]; res += array[i+2]; res += array[i+1]; } return res; } // first range check is i + min of all constants @Args({0, -9}) static int m2(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+1]; if (allaccesses) { res += array[i+2]; res += array[i+3]; res += array[i+4]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } // first range check is not i + min/max of all constants @Args({0, 8}) static int m3(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; if (allaccesses) { res += array[i+2]; res += array[i+1]; res += array[i+4]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({0, -9}) static int m4(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; if (allaccesses) { res += array[i+4]; res += array[i+1]; res += array[i+2]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({0, -3}) static int m5(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i+2]; if (allaccesses) { res += array[i+1]; res += array[i+4]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({0, 6}) static int m6(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i+4]; if (allaccesses) { res += array[i+2]; res += array[i+1]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({0, 6}) static int m7(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i+2]; res += array[i+4]; if (allaccesses) { res += array[i+1]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({0, -3}) static int m8(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i+4]; res += array[i+2]; if (allaccesses) { res += array[i+1]; res += array[i+5]; res += array[i+6]; res += array[i+7]; res += array[i+8]; res += array[i+9]; } return res; } @Args({6, 15}) static int m9(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; if (allaccesses) { res += array[i-2]; res += array[i-1]; res += array[i-4]; res += array[i-5]; res += array[i-6]; } return res; } @Args({3, 12}) static int m10(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; if (allaccesses) { res += array[i-2]; res += array[i-1]; res += array[i-3]; res += array[i+4]; res += array[i+5]; res += array[i+6]; } return res; } @Args({3, -3}) static int m11(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i-2]; if (allaccesses) { res += array[i+5]; res += array[i+6]; } return res; } @Args({3, 6}) static int m12(int[] array, int i, boolean allaccesses) { int res = 0; res += array[i+3]; res += array[i+6]; if (allaccesses) { res += array[i-2]; res += array[i-3]; } return res; } // check that identical range check is replaced by dominating one // only when correct @Args({0}) static int m13(int[] array, int i, boolean ignore) { int res = 0; res += array[i+3]; res += array[i+3]; return res; } @Args({2, 0}) static int m14(int[] array, int i, boolean ignore) { int res = 0; res += array[i]; res += array[i-2]; res += array[i]; // If range check below were to be removed first this cannot be considered identical to first range check res += array[i-1]; // range check removed so i-1 array access depends on previous check return res; } static int[] m15_dummy = new int[10]; @Args({2, 0}) static int m15(int[] array, int i, boolean ignore) { int res = 0; res += array[i]; // When the loop is optimized out we don't want the // array[i-1] access which is dependent on array[i]'s // range check to become dependent on the identical range // check above. int[] array2 = m15_dummy; int j = 0; for (; j < 10; j++); if (j == 10) { array2 = array; } res += array2[i-2]; res += array2[i]; res += array2[i-1]; // range check removed so i-1 array access depends on previous check return res; } @Args({2, 0}) static int m16(int[] array, int i, boolean ignore) { int res = 0; res += array[i]; res += array[i-1]; res += array[i-1]; res += array[i-2]; return res; } @Args({2, 0}) static int m17(int[] array, int i, boolean ignore) { int res = 0; res += array[i]; res += array[i-2]; res += array[i-2]; res += array[i+2]; res += array[i+2]; res += array[i-1]; res += array[i-1]; return res; } static public void main(String[] args) { if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) { throw new AssertionError("Background compilation enabled"); } new TestRangeCheckSmearing().doTests(); } boolean success = true; boolean exception = false; final int[] array = new int[10]; final HashMap tests = new HashMap<>(); { final Class TEST_PARAM_TYPES[] = { int[].class, int.class, boolean.class }; for (Method m : this.getClass().getDeclaredMethods()) { if (m.getName().matches("m[0-9]+")) { assert(Modifier.isStatic(m.getModifiers())) : m; assert(m.getReturnType() == int.class) : m; assert(Arrays.equals(m.getParameterTypes(), TEST_PARAM_TYPES)) : m; tests.put(m.getName(), m); } } } void invokeTest(Method m, int[] array, int index, boolean z) { try { m.invoke(null, array, index, z); } catch (ReflectiveOperationException roe) { Throwable ex = roe.getCause(); if (ex instanceof ArrayIndexOutOfBoundsException) throw (ArrayIndexOutOfBoundsException) ex; throw new AssertionError(roe); } } void doTest(String name) { Method m = tests.get(name); tests.remove(name); int[] args = m.getAnnotation(Args.class).value(); int index0 = args[0], index1; boolean exceptionRequired = true; if (args.length == 2) { index1 = args[1]; } else { // no negative test for this one assert(args.length == 1); assert(name.equals("m13")); exceptionRequired = false; index1 = index0; } // Get the method compiled. if (!WHITE_BOX.isMethodCompiled(m)) { // If not, try to compile it with C2 if(!WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION)) { // C2 compiler not available, try to compile with C1 WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_SIMPLE); } } if (!WHITE_BOX.isMethodCompiled(m)) { throw new RuntimeException(m + " not compiled"); } // valid access invokeTest(m, array, index0, true); if (!WHITE_BOX.isMethodCompiled(m)) { throw new RuntimeException(m + " deoptimized on valid array access"); } exception = false; boolean test_success = true; try { invokeTest(m, array, index1, false); } catch(ArrayIndexOutOfBoundsException aioob) { exception = true; System.out.println("ArrayIndexOutOfBoundsException thrown in "+name); } if (!exception) { System.out.println("ArrayIndexOutOfBoundsException was not thrown in "+name); } if (Platform.isServer()) { if (exceptionRequired == WHITE_BOX.isMethodCompiled(m)) { System.out.println((exceptionRequired?"Didn't deoptimized":"deoptimized") + " in "+name); test_success = false; } } if (exception != exceptionRequired) { System.out.println((exceptionRequired?"exception required but not thrown":"not exception required but thrown") + " in "+name); test_success = false; } if (!test_success) { success = false; System.out.println("TEST FAILED: "+name); } } void doTests() { doTest("m1"); doTest("m2"); doTest("m3"); doTest("m4"); doTest("m5"); doTest("m6"); doTest("m7"); doTest("m8"); doTest("m9"); doTest("m10"); doTest("m11"); doTest("m12"); doTest("m13"); doTest("m14"); doTest("m15"); doTest("m16"); doTest("m17"); if (!success) { throw new RuntimeException("Some tests failed"); } assert(tests.isEmpty()) : tests; } }