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