ReservedStackTest.java revision 12531:ccbd743a62e3
1/* 2 * Copyright (c) 2015, 2016, 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 * @library /test/lib 27 * @modules java.base/jdk.internal.misc 28 * @modules java.base/jdk.internal.vm.annotation 29 * @run main/othervm -Xint ReservedStackTest 30 * @run main/othervm -XX:-Inline -XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer.setExclusiveOwnerThread ReservedStackTest 31 */ 32 33/* The exclusion of java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread() 34 * from the compilable methods is required to ensure that the test will be able 35 * to trigger a StackOverflowError on the right method. 36 */ 37 38 39/* 40 * Notes about this test: 41 * This test tries to reproduce a rare but nasty corruption bug that 42 * occurs when a StackOverflowError is thrown in some critical sections 43 * of the ReentrantLock implementation. 44 * 45 * Here's the critical section where a corruption could occur 46 * (from java.util.concurrent.ReentrantLock.java) 47 * 48 * final void lock() { 49 * if (compareAndSetState(0, 1)) 50 * setExclusiveOwnerThread(Thread.currentThread()); 51 * else 52 * acquire(1); 53 * } 54 * 55 * The corruption occurs when the compareAndSetState(0, 1) 56 * successfully updates the status of the lock but the method 57 * fails to set the owner because of a stack overflow. 58 * HotSpot checks for stack overflow on method invocations. 59 * The test must trigger a stack overflow either when 60 * Thread.currentThread() or setExclusiveOwnerThread() is 61 * invoked. 62 * 63 * The test starts with a recursive invocation loop until a 64 * first StackOverflowError is thrown, the Error is caught 65 * and a few dozen frames are exited. Now the thread has 66 * little free space on its execution stack and will try 67 * to trigger a stack overflow in the critical section. 68 * The test has a huge array of ReentrantLocks instances. 69 * The thread invokes a recursive method which, at each 70 * of its invocations, tries to acquire the next lock 71 * in the array. The execution continues until a 72 * StackOverflowError is thrown or the end of the array 73 * is reached. 74 * If no StackOverflowError has been thrown, the test 75 * is non conclusive (recommendation: increase the size 76 * of the ReentrantLock array). 77 * The status of all Reentrant locks in the array is checked, 78 * if a corruption is detected, the test failed, otherwise 79 * the test passed. 80 * 81 * To have a chance that the stack overflow occurs on one 82 * of the two targeted method invocations, the test is 83 * repeated in different threads. Each Java thread has a 84 * random size area allocated at the beginning of its 85 * stack to prevent false sharing. The test relies on this 86 * to have different stack alignments when it hits the targeted 87 * methods (the test could have been written with a native 88 * method with alloca, but using different Java threads makes 89 * the test 100% Java). 90 * 91 * One additional trick is required to ensure that the stack 92 * overflow will occur on the Thread.currentThread() getter 93 * or the setExclusiveOwnerThread() setter. 94 * 95 * Potential stack overflows are detected by stack banging, 96 * at method invocation time. 97 * In interpreted code, the stack banging performed for the 98 * lock() method goes further than the stack banging performed 99 * for the getter or the setter method, so the potential stack 100 * overflow is detected before entering the critical section. 101 * In compiled code, the getter and the setter are in-lined, 102 * so the stack banging is only performed before entering the 103 * critical section. 104 * In order to have a stack banging that goes further for the 105 * getter/setter methods than for the lock() method, the test 106 * exploits the property that interpreter frames are (much) 107 * bigger than compiled code frames. When the test is run, 108 * a compiler option disables the compilation of the 109 * setExclusiveOwnerThread() method. 110 * 111 */ 112 113import java.util.concurrent.locks.ReentrantLock; 114import jdk.test.lib.Platform; 115 116public class ReservedStackTest { 117 118 static class ReentrantLockTest { 119 120 private ReentrantLock lockArray[]; 121 // Frame sizes vary a lot between interpreted code and compiled code 122 // so the lock array has to be big enough to cover all cases. 123 // If test fails with message "Not conclusive test", try to increase 124 // LOCK_ARRAY_SIZE value 125 private static final int LOCK_ARRAY_SIZE = 8192; 126 private boolean stackOverflowErrorReceived; 127 StackOverflowError soe = null; 128 private int index = -1; 129 130 public void initialize() { 131 lockArray = new ReentrantLock[LOCK_ARRAY_SIZE]; 132 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 133 lockArray[i] = new ReentrantLock(); 134 } 135 stackOverflowErrorReceived = false; 136 } 137 138 public String getResult() { 139 if (!stackOverflowErrorReceived) { 140 return "ERROR: Not conclusive test: no StackOverflowError received"; 141 } 142 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 143 if (lockArray[i].isLocked()) { 144 if (!lockArray[i].isHeldByCurrentThread()) { 145 StringBuilder s = new StringBuilder(); 146 s.append("FAILED: ReentrantLock "); 147 s.append(i); 148 s.append(" looks corrupted"); 149 return s.toString(); 150 } 151 } 152 } 153 return "PASSED"; 154 } 155 156 public void run() { 157 try { 158 lockAndCall(0); 159 } catch (StackOverflowError e) { 160 soe = e; 161 stackOverflowErrorReceived = true; 162 } 163 } 164 165 private void lockAndCall(int i) { 166 index = i; 167 if (i < LOCK_ARRAY_SIZE) { 168 lockArray[i].lock(); 169 lockAndCall(i + 1); 170 } 171 } 172 } 173 174 static class RunWithSOEContext implements Runnable { 175 176 int counter; 177 int deframe; 178 int decounter; 179 int setupSOEFrame; 180 int testStartFrame; 181 ReentrantLockTest test; 182 183 public RunWithSOEContext(ReentrantLockTest test, int deframe) { 184 this.test = test; 185 this.deframe = deframe; 186 } 187 188 @Override 189 @jdk.internal.vm.annotation.ReservedStackAccess 190 public void run() { 191 counter = 0; 192 decounter = deframe; 193 test.initialize(); 194 recursiveCall(); 195 System.out.println("Framework got StackOverflowError at frame = " + counter); 196 System.out.println("Test started execution at frame = " + (counter - deframe)); 197 String result = test.getResult(); 198 // The feature is not fully implemented on all platforms, 199 // corruptions are still possible. 200 boolean supportedPlatform = 201 Platform.isAix() || 202 (Platform.isLinux() && 203 (Platform.isPPC() || Platform.isS390x() || Platform.isX64() || 204 Platform.isX86() || Platform.isAArch64())) || 205 Platform.isOSX() || 206 Platform.isSolaris(); 207 if (supportedPlatform && !result.contains("PASSED")) { 208 System.out.println(result); 209 throw new Error(result); 210 } else { 211 // Either the test passed or this platform is not supported. 212 // On not supported platforms, we only expect the VM to 213 // not crash during the test. This is especially important 214 // on Windows where the detection of SOE in annotated 215 // sections is implemented but the reserved zone mechanism 216 // to avoid the corruption cannot be implemented yet 217 // because of JDK-8067946 218 System.out.println("PASSED"); 219 } 220 } 221 222 void recursiveCall() { 223 // Unused local variables to increase the frame size 224 long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19; 225 long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37; 226 counter++; 227 try { 228 recursiveCall(); 229 } catch (StackOverflowError e) { 230 } 231 decounter--; 232 if (decounter == 0) { 233 setupSOEFrame = counter; 234 testStartFrame = counter - deframe; 235 test.run(); 236 } 237 } 238 } 239 240 public static void main(String[] args) { 241 for (int i = 0; i < 1000; i++) { 242 // Each iteration has to be executed by a new thread. The test 243 // relies on the random size area pushed by the VM at the beginning 244 // of the stack of each Java thread it creates. 245 Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256)); 246 thread.start(); 247 try { 248 thread.join(); 249 } catch (InterruptedException ex) { } 250 } 251 } 252} 253