/* * 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) { } } } }