1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test ReservedStackTest 26 * @run main/othervm -XX:-Inline -XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer,setExclusiveOwnerThread ReservedStackTest 27 */ 28 29 /* The exclusion of java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread() 30 * from the compilable methods is required to ensure that the test will be able 31 * to trigger a StackOverflowError on the right method. 32 */ 33 34 35 /* 36 * Notes about this test: 37 * This test tries to reproduce a rare but nasty corruption bug that 38 * occurs when a StackOverflowError is thrown in some critical sections 39 * of the ReentrantLock implementation. 40 * 41 * Here's the critical section where a corruption could occur 42 * (from java.util.concurrent.ReentrantLock.java) 43 * 44 * final void lock() { 45 * if (compareAndSetState(0, 1)) 46 * setExclusiveOwnerThread(Thread.currentThread()); 47 * else 48 * acquire(1); 49 * } 50 * 51 * The corruption occurs when the compareAndSetState(0, 1) 52 * successfully update the status of the lock but he method 53 * fails to set the owner because of a stack overflow. 54 * HotSpot checks for stack overflow on method invocations. 55 * The test must trigger a stack overflow either when 56 * Thread.currentThread() or setExclusiveOwnerThread() is 57 * invoked. 58 * 59 * The test starts with a recursive invocation loop until a 60 * first StackOverflowError is thrown, the Error is catched 61 * and a few dozens frames are exited. Now the thread has 62 * little free space on its execution stack and will try 63 * to trigger a stack overflow in the critical section. 64 * The test has a huge array of ReentrantLocks instances. 65 * The thread invokes a recursive method which, at each 66 * of its invocation, tries to acquire the next lock 67 * in the array. The execution continues until a 68 * StackOverflowError is thrown or the end of the array 69 * is reached. 70 * If no StackOverflowError has been thrown, the test 71 * is non conclusive (recommendation: increase the size 72 * of the ReentrantLock array). 73 * The status of all Reentrant locks in the array is checked, 74 * if a corruption is detected, the test failed, otherwise 75 * the test passed. 76 * 77 * To have a chance that the stack overflow occurs on one 78 * of the two targeted method invocations, the test is 79 * repeated in different threads. Each Java thread has a 80 * random size area allocated at the beginning of its 81 * stack to prevent false sharing. The test is using this 82 * to have different stack alignments and hit the targeted 83 * methods (the test could have been writtent with a native 84 * method with alloca, but using different Java threads makes 85 * the test 100% Java). 86 * 87 * One additional trick is required to ensure that the stack 88 * overflow will occur on the Thread.currentThread() getter 89 * or the setExclusiveOwnerThread() setter. 90 * 91 * Potential stack overflows are detected by stack banging, 92 * at method invocation time. 93 * In interpreted code, the stack banging performed for the 94 * lock() method goes further than the stack banging performed 95 * for the getter or the setter method, so the potential stack 96 * overflow is detected before entering the critical section. 97 * In compiled code, the getter and the setter are in-lined, 98 * so the stack banging is only performed before entering the 99 * critical section. 100 * In order to have a stack banging that goes further for the 101 * getter/setter methods than for the lock() method, the test 102 * exploit the property that interpreter frames are (much) 103 * bigger than compiled code frames. When the test is run, 104 * a compiler option disables the compilation of the 105 * setExclusiveOwnerThread() method. 106 * 107 */ 108 109 import java.util.concurrent.locks.ReentrantLock; 110 111 public class ReservedStackTest { 112 113 private static boolean isWindows() { 114 return System.getProperty("os.name").toLowerCase().startsWith("win"); 115 } 116 117 static class ReentrantLockTest { 118 119 private ReentrantLock lockArray[]; 120 // Frame sizes vary a lot between interpreted code and compiled code 121 // so the lock array has to be big enough to cover all cases. 122 // If test fails with message "Not conclusive test", try to increase 123 //LOCK_ARRAY_SIZE value 124 private static final int LOCK_ARRAY_SIZE = 8192; 125 private boolean stackOverflowErrorReceived; 126 StackOverflowError soe = null; 127 private int index = -1; 128 129 public void initialize() { 130 lockArray = new ReentrantLock[LOCK_ARRAY_SIZE]; 131 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 132 lockArray[i] = new ReentrantLock(); 133 } 134 stackOverflowErrorReceived = false; 135 } 136 137 public String getResult() { 138 if (!stackOverflowErrorReceived) { 139 return "ERROR: Not conclusive test: no StackOverflowError received"; 140 } 141 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 142 if (lockArray[i].isLocked()) { 143 if (!lockArray[i].isHeldByCurrentThread()) { 144 StringBuilder s = new StringBuilder(); 145 s.append("FAILED: ReentrantLock "); 146 s.append(i); 147 s.append(" looks corrupted"); 148 return s.toString(); 149 } 150 } 151 } 152 return "PASSED"; 153 } 154 155 public void run() { 156 try { 157 lockAndCall(0); 158 } catch (StackOverflowError e) { 159 soe = e; 160 stackOverflowErrorReceived = true; 161 } 162 } 163 164 private void lockAndCall(int i) { 165 index = i; 166 if (i < LOCK_ARRAY_SIZE) { 167 lockArray[i].lock(); 168 lockAndCall(i + 1); 169 } 170 } 171 } 172 173 static class RunWithSOEContext implements Runnable { 174 175 int counter; 176 int deframe; 177 int decounter; 178 int setupSOEFrame; 179 int testStartFrame; 180 ReentrantLockTest test; 181 182 public RunWithSOEContext(ReentrantLockTest test, int deframe) { 183 this.test = test; 184 this.deframe = deframe; 185 } 186 187 @Override 188 @jdk.internal.vm.annotation.ReservedStackAccess 189 public void run() { 190 counter = 0; 191 decounter = deframe; 192 test.initialize(); 193 recursiveCall(); 194 System.out.println("Framework got StackOverflowError at frame = " + counter); 195 System.out.println("Test started execution at frame = " + (counter - deframe)); 196 String result = test.getResult(); 197 System.out.println(result); 198 // The feature is not fully implemented on Windows platforms, 199 // corruptions are still possible 200 if (!isWindows() && !result.contains("PASSED")) { 201 System.exit(-1); 202 } 203 } 204 205 void recursiveCall() { 206 // Unused local variables to increase the frame size 207 long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19; 208 long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37; 209 counter++; 210 try { 211 recursiveCall(); 212 } catch (StackOverflowError e) { 213 } 214 decounter--; 215 if (decounter == 0) { 216 setupSOEFrame = counter; 217 testStartFrame = counter - deframe; 218 test.run(); 219 } 220 } 221 } 222 223 public static void main(String[] args) { 224 for (int i = 0; i < 1000; i++) { 225 // Each iteration has to be executed by a new thread. The test 226 // relies on the random size area pushed by the VM at the beginning 227 // of the stack of each Java thread it creates. 228 Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256)); 229 thread.start(); 230 try { 231 thread.join(); 232 } catch (InterruptedException ex) { } 233 } 234 } 235 }