1 /* 2 * Copyright (c) 2015, 2017, 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 113 import java.util.concurrent.locks.ReentrantLock; 114 import jdk.test.lib.Platform; 115 import jdk.test.lib.process.ProcessTools; 116 import jdk.test.lib.process.OutputAnalyzer; 117 118 public class ReservedStackTest { 119 120 static class ReentrantLockTest { 121 122 private ReentrantLock lockArray[]; 123 // Frame sizes vary a lot between interpreted code and compiled code 124 // so the lock array has to be big enough to cover all cases. 125 // If test fails with message "Not conclusive test", try to increase 126 // LOCK_ARRAY_SIZE value 127 private static final int LOCK_ARRAY_SIZE = 8192; 128 private boolean stackOverflowErrorReceived; 129 StackOverflowError soe = null; 130 private int index = -1; 131 132 public void initialize() { 133 lockArray = new ReentrantLock[LOCK_ARRAY_SIZE]; 134 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 135 lockArray[i] = new ReentrantLock(); 136 } 137 stackOverflowErrorReceived = false; 138 } 139 140 public String getResult() { 141 if (!stackOverflowErrorReceived) { 142 return "ERROR: Not conclusive test: no StackOverflowError received"; 143 } 144 for (int i = 0; i < LOCK_ARRAY_SIZE; i++) { 145 if (lockArray[i].isLocked()) { 146 if (!lockArray[i].isHeldByCurrentThread()) { 147 StringBuilder s = new StringBuilder(); 148 s.append("FAILED: ReentrantLock "); 149 s.append(i); 150 s.append(" looks corrupted"); 151 return s.toString(); 152 } 153 } 154 } 155 return "PASSED"; 156 } 157 158 public void run() { 159 try { 160 lockAndCall(0); 161 } catch (StackOverflowError e) { 162 soe = e; 163 stackOverflowErrorReceived = true; 164 } 165 } 166 167 private void lockAndCall(int i) { 168 index = i; 169 if (i < LOCK_ARRAY_SIZE) { 170 lockArray[i].lock(); 171 lockAndCall(i + 1); 172 } 173 } 174 } 175 176 static class RunWithSOEContext implements Runnable { 177 178 int counter; 179 int deframe; 180 int decounter; 181 int setupSOEFrame; 182 int testStartFrame; 183 ReentrantLockTest test; 184 185 public RunWithSOEContext(ReentrantLockTest test, int deframe) { 186 this.test = test; 187 this.deframe = deframe; 188 } 189 190 @Override 191 @jdk.internal.vm.annotation.ReservedStackAccess 192 public void run() { 193 counter = 0; 194 decounter = deframe; 195 test.initialize(); 196 recursiveCall(); 197 System.out.println("Framework got StackOverflowError at frame = " + counter); 198 System.out.println("Test started execution at frame = " + (counter - deframe)); 199 String result = test.getResult(); 200 // The feature is not fully implemented on all platforms, 201 // corruptions are still possible. 202 if (isSupportedPlatform && !result.contains("PASSED")) { 203 System.out.println(result); 204 throw new Error(result); 205 } else { 206 // Either the test passed or this platform is not supported. 207 // On not supported platforms, we only expect the VM to 208 // not crash during the test. This is especially important 209 // on Windows where the detection of SOE in annotated 210 // sections is implemented but the reserved zone mechanism 211 // to avoid the corruption cannot be implemented yet 212 // because of JDK-8067946 213 System.out.println("PASSED"); 214 } 215 } 216 217 void recursiveCall() { 218 // Unused local variables to increase the frame size 219 long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19; 220 long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37; 221 counter++; 222 try { 223 recursiveCall(); 224 } catch (StackOverflowError e) { 225 } 226 decounter--; 227 if (decounter == 0) { 228 setupSOEFrame = counter; 229 testStartFrame = counter - deframe; 230 test.run(); 231 } 232 } 233 } 234 235 private static boolean isAlwaysSupportedPlatform() { 236 // Note: To date Aarch64 is the only platform that we don't statically 237 // know if it supports the reserved stack area. This is because the 238 // open Aarch64 port supports it and the Oracle arm64 port does not. 239 return Platform.isAix() || 240 (Platform.isLinux() && 241 (Platform.isPPC() || Platform.isS390x() || Platform.isX64() || 242 Platform.isX86())) || 243 Platform.isOSX() || 244 Platform.isSolaris(); 245 } 246 247 private static boolean isNeverSupportedPlatform() { 248 return !isAlwaysSupportedPlatform() && !Platform.isAArch64(); 249 } 250 251 private static boolean isSupportedPlatform; 252 253 private static void initIsSupportedPlatform() throws Exception { 254 // In order to dynamicaly determine if the platform supports the reserved 255 // stack area, run with -XX:StackReservedPages=1 and see if we get the 256 // expected warning message for platforms that don't support it. 257 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:StackReservedPages=1", "-version"); 258 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 259 System.out.println("StackReservedPages=1 log: [" + output.getOutput() + "]"); 260 if (output.getExitValue() != 0) { 261 String msg = "Could not launch with -XX:StackReservedPages=1: exit " + output.getExitValue(); 262 System.err.println("FAILED: " + msg); 263 throw new RuntimeException(msg); 264 } 265 266 isSupportedPlatform = true; 267 String matchStr = "Reserved Stack Area not supported on this platform"; 268 int match_idx = output.getOutput().indexOf(matchStr); 269 if (match_idx >= 0) { 270 isSupportedPlatform = false; 271 } 272 273 // Do a sanity check. Some platforms we know are always supported. Make sure 274 // we didn't determine that one of those platforms is not supported. 275 if (!isSupportedPlatform && isAlwaysSupportedPlatform()) { 276 String msg = "This platform should be supported: " + Platform.getOsArch(); 277 System.err.println("FAILED: " + msg); 278 throw new RuntimeException(msg); 279 } 280 281 // And some platforms we know are never supported. Make sure 282 // we didn't determine that one of those platforms is supported. 283 if (isSupportedPlatform && isNeverSupportedPlatform()) { 284 String msg = "This platform should not be supported: " + Platform.getOsArch(); 285 System.err.println("FAILED: " + msg); 286 throw new RuntimeException(msg); 287 } 288 } 289 290 public static void main(String[] args) throws Exception { 291 initIsSupportedPlatform(); 292 for (int i = 0; i < 1000; i++) { 293 // Each iteration has to be executed by a new thread. The test 294 // relies on the random size area pushed by the VM at the beginning 295 // of the stack of each Java thread it creates. 296 Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256)); 297 thread.start(); 298 try { 299 thread.join(); 300 } catch (InterruptedException ex) { } 301 } 302 } 303 }