--- /dev/null 2015-09-01 10:30:13.517273143 +0200 +++ new/test/runtime/ReservedStack/ReservedStackTest.java 2015-11-04 14:33:01.567589238 +0100 @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2015, 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 ReservedStackTest + * @run main/othervm -XX:-Inline -XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer,setExclusiveOwnerThread ReservedStackTest + */ + +/* The exclusion of java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread() + * from the compilable methods is required to ensure that the test will be able + * to trigger a StackOverflowError on the right method. + */ + + +/* + * Notes about this test: + * This test tries to reproduce a rare but nasty corruption bug that + * occurs when a StackOverflowError is thrown in some critical sections + * of the ReentrantLock implementation. + * + * Here's the critical section where a corruption could occur + * (from java.util.concurrent.ReentrantLock.java) + * + * final void lock() { + * if (compareAndSetState(0, 1)) + * setExclusiveOwnerThread(Thread.currentThread()); + * else + * acquire(1); + * } + * + * The corruption occurs when the compareAndSetState(0, 1) + * successfully update the status of the lock but he method + * fails to set the owner because of a stack overflow. + * HotSpot checks for stack overflow on method invocations. + * The test must trigger a stack overflow either when + * Thread.currentThread() or setExclusiveOwnerThread() is + * invoked. + * + * The test starts with a recursive invocation loop until a + * first StackOverflowError is thrown, the Error is catched + * and a few dozens frames are exited. Now the thread has + * little free space on its execution stack and will try + * to trigger a stack overflow in the critical section. + * The test has a huge array of ReentrantLocks instances. + * The thread invokes a recursive method which, at each + * of its invocation, tries to acquire the next lock + * in the array. The execution continues until a + * StackOverflowError is thrown or the end of the array + * is reached. + * If no StackOverflowError has been thrown, the test + * is non conclusive (recommendation: increase the size + * of the ReentrantLock array). + * The status of all Reentrant locks in the array is checked, + * if a corruption is detected, the test failed, otherwise + * the test passed. + * + * To have a chance that the stack overflow occurs on one + * of the two targeted method invocations, the test is + * repeated in different threads. Each Java thread has a + * random size area allocated at the beginning of its + * stack to prevent false sharing. The test is using this + * to have different stack alignments and hit the targeted + * methods (the test could have been writtent with a native + * method with alloca, but using different Java threads makes + * the test 100% Java). + * + * One additional trick is required to ensure that the stack + * overflow will occur on the Thread.currentThread() getter + * or the setExclusiveOwnerThread() setter. + * + * Potential stack overflows are detected by stack banging, + * at method invocation time. + * In interpreted code, the stack banging performed for the + * lock() method goes further than the stack banging performed + * for the getter or the setter method, so the potential stack + * overflow is detected before entering the critical section. + * In compiled code, the getter and the setter are in-lined, + * so the stack banging is only performed before entering the + * critical section. + * In order to have a stack banging that goes further for the + * getter/setter methods than for the lock() method, the test + * exploit the property that interpreter frames are (much) + * bigger than compiled code frames. When the test is run, + * a compiler option disables the compilation of the + * setExclusiveOwnerThread() method. + * + */ + +import java.util.concurrent.locks.ReentrantLock; + +public class ReservedStackTest { + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().startsWith("win"); + } + + static class ReentrantLockTest { + + private ReentrantLock lockArray[]; + // Frame sizes vary a lot between interpreted code and compiled code + // so the lock array has to be big enough to cover all cases. + // If test fails with message "Not conclusive test", try to increase + //LOCK_ARRAY_SIZE value + private static final int LOCK_ARRAY_SIZE = 8192; + private boolean stackOverflowErrorReceived; + StackOverflowError soe = null; + private int index = -1; + + public void initialize() { + lockArray = new ReentrantLock[LOCK_ARRAY_SIZE]; + for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { + lockArray[i] = new ReentrantLock(); + } + stackOverflowErrorReceived = false; + } + + public String getResult() { + if (!stackOverflowErrorReceived) { + return "ERROR: Not conclusive test: no StackOverflowError received"; + } + for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { + if (lockArray[i].isLocked()) { + if (!lockArray[i].isHeldByCurrentThread()) { + StringBuilder s = new StringBuilder(); + s.append("FAILED: ReentrantLock "); + s.append(i); + s.append(" looks corrupted"); + return s.toString(); + } + } + } + return "PASSED"; + } + + public void run() { + try { + lockAndCall(0); + } catch (StackOverflowError e) { + soe = e; + stackOverflowErrorReceived = true; + } + } + + private void lockAndCall(int i) { + index = i; + if (i < LOCK_ARRAY_SIZE) { + lockArray[i].lock(); + lockAndCall(i + 1); + } + } + } + + static class RunWithSOEContext implements Runnable { + + int counter; + int deframe; + int decounter; + int setupSOEFrame; + int testStartFrame; + ReentrantLockTest test; + + public RunWithSOEContext(ReentrantLockTest test, int deframe) { + this.test = test; + this.deframe = deframe; + } + + @Override + @jdk.internal.vm.annotation.ReservedStackAccess + public void run() { + counter = 0; + decounter = deframe; + test.initialize(); + recursiveCall(); + System.out.println("Framework got StackOverflowError at frame = " + counter); + System.out.println("Test started execution at frame = " + (counter - deframe)); + String result = test.getResult(); + System.out.println(result); + // The feature is not fully implemented on Windows platforms, + // corruptions are still possible + if (!isWindows() && !result.contains("PASSED")) { + System.exit(-1); + } + } + + void recursiveCall() { + // Unused local variables to increase the frame size + long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19; + long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37; + counter++; + try { + recursiveCall(); + } catch (StackOverflowError e) { + } + decounter--; + if (decounter == 0) { + setupSOEFrame = counter; + testStartFrame = counter - deframe; + test.run(); + } + } + } + + public static void main(String[] args) { + for (int i = 0; i < 1000; i++) { + // Each iteration has to be executed by a new thread. The test + // relies on the random size area pushed by the VM at the beginning + // of the stack of each Java thread it creates. + Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256)); + thread.start(); + try { + thread.join(); + } catch (InterruptedException ex) { } + } + } +}