1 /*
   2  * Copyright (c) 2015, 2018, 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  *
  27  * @requires vm.opt.DeoptimizeALot == null | vm.opt.DeoptimizeALot == false
  28  * @library /test/lib
  29  * @modules java.base/jdk.internal.misc
  30  * @modules java.base/jdk.internal.vm.annotation
  31  *
  32  * @run main/othervm -XX:MaxInlineLevel=2 -XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer.setExclusiveOwnerThread ReservedStackTest
  33  */
  34 
  35 /* The exclusion of java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread()
  36  * from the compilable methods is required to ensure that the test will be able
  37  * to trigger a StackOverflowError on the right method.
  38  */
  39 
  40 
  41 /*
  42  * Notes about this test:
  43  * This test tries to reproduce a rare but nasty corruption bug that
  44  * occurs when a StackOverflowError is thrown in some critical sections
  45  * of the ReentrantLock implementation.
  46  *
  47  * Here's the critical section where a corruption could occur
  48  * (from java.util.concurrent.ReentrantLock.java)
  49  *
  50  * final void lock() {
  51  *     if (compareAndSetState(0, 1))
  52  *         setExclusiveOwnerThread(Thread.currentThread());
  53  *     else
  54  *         acquire(1);
  55  * }
  56  *
  57  * The corruption occurs when the compareAndSetState(0, 1)
  58  * successfully updates the status of the lock but the method
  59  * fails to set the owner because of a stack overflow.
  60  * HotSpot checks for stack overflow on method invocations.
  61  * The test must trigger a stack overflow either when
  62  * Thread.currentThread() or setExclusiveOwnerThread() is
  63  * invoked.
  64  *
  65  * The test starts with a recursive invocation loop until a
  66  * first StackOverflowError is thrown, the Error is caught
  67  * and a few dozen frames are exited. Now the thread has
  68  * little free space on its execution stack and will try
  69  * to trigger a stack overflow in the critical section.
  70  * The test has a huge array of ReentrantLocks instances.
  71  * The thread invokes a recursive method which, at each
  72  * of its invocations, tries to acquire the next lock
  73  * in the array. The execution continues until a
  74  * StackOverflowError is thrown or the end of the array
  75  * is reached.
  76  * If no StackOverflowError has been thrown, the test
  77  * is non conclusive (recommendation: increase the size
  78  * of the ReentrantLock array).
  79  * The status of all Reentrant locks in the array is checked,
  80  * if a corruption is detected, the test failed, otherwise
  81  * the test passed.
  82  *
  83  * To have a chance that the stack overflow occurs on one
  84  * of the two targeted method invocations, the test is
  85  * repeated in different threads. Each Java thread has a
  86  * random size area allocated at the beginning of its
  87  * stack to prevent false sharing. The test relies on this
  88  * to have different stack alignments when it hits the targeted
  89  * methods (the test could have been written with a native
  90  * method with alloca, but using different Java threads makes
  91  * the test 100% Java).
  92  *
  93  * One additional trick is required to ensure that the stack
  94  * overflow will occur on the Thread.currentThread() getter
  95  * or the setExclusiveOwnerThread() setter.
  96  *
  97  * Potential stack overflows are detected by stack banging,
  98  * at method invocation time.
  99  * In interpreted code, the stack banging performed for the
 100  * lock() method goes further than the stack banging performed
 101  * for the getter or the setter method, so the potential stack
 102  * overflow is detected before entering the critical section.
 103  * In compiled code, the getter and the setter are in-lined,
 104  * so the stack banging is only performed before entering the
 105  * critical section.
 106  * In order to have a stack banging that goes further for the
 107  * getter/setter methods than for the lock() method, the test
 108  * exploits the property that interpreter frames are (much)
 109  * bigger than compiled code frames. When the test is run,
 110  * a compiler option disables the compilation of the
 111  * setExclusiveOwnerThread() method.
 112  *
 113  */
 114 
 115 import java.util.concurrent.locks.ReentrantLock;
 116 import jdk.test.lib.Platform;
 117 import jdk.test.lib.process.ProcessTools;
 118 import jdk.test.lib.process.OutputAnalyzer;
 119 
 120 public class ReservedStackTest {
 121 
 122     static class ReentrantLockTest {
 123 
 124         private ReentrantLock lockArray[];
 125         // Frame sizes vary a lot between interpreted code and compiled code
 126         // so the lock array has to be big enough to cover all cases.
 127         // If test fails with message "Not conclusive test", try to increase
 128         // LOCK_ARRAY_SIZE value
 129         private static final int LOCK_ARRAY_SIZE = 8192;
 130         private boolean stackOverflowErrorReceived;
 131         StackOverflowError soe = null;
 132         private int index = -1;
 133 
 134         public void initialize() {
 135             lockArray = new ReentrantLock[LOCK_ARRAY_SIZE];
 136             for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
 137                 lockArray[i] = new ReentrantLock();
 138             }
 139             stackOverflowErrorReceived = false;
 140         }
 141 
 142         public String getResult() {
 143             if (!stackOverflowErrorReceived) {
 144                 return "ERROR: Not conclusive test: no StackOverflowError received";
 145             }
 146             for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
 147                 if (lockArray[i].isLocked()) {
 148                     if (!lockArray[i].isHeldByCurrentThread()) {
 149                         StringBuilder s = new StringBuilder();
 150                         s.append("FAILED: ReentrantLock ");
 151                         s.append(i);
 152                         s.append(" looks corrupted");
 153                         return s.toString();
 154                     }
 155                 }
 156             }
 157             return "PASSED";
 158         }
 159 
 160         public void run() {
 161             try {
 162                 lockAndCall(0);
 163             } catch (StackOverflowError e) {
 164                 soe = e;
 165                 stackOverflowErrorReceived = true;
 166                 throw e;
 167             }
 168         }
 169 
 170         private void lockAndCall(int i) {
 171             index = i;
 172             if (i < LOCK_ARRAY_SIZE) {
 173                 lockArray[i].lock();
 174                 lockAndCall(i + 1);
 175             }
 176         }
 177     }
 178 
 179     static class RunWithSOEContext implements Runnable {
 180 
 181         int counter;
 182         int deframe;
 183         int decounter;
 184         int setupSOEFrame;
 185         int testStartFrame;
 186         ReentrantLockTest test;
 187 
 188         public RunWithSOEContext(ReentrantLockTest test, int deframe) {
 189             this.test = test;
 190             this.deframe = deframe;
 191         }
 192 
 193         @Override
 194         @jdk.internal.vm.annotation.ReservedStackAccess
 195         public void run() {
 196             counter = 0;
 197             decounter = deframe;
 198             test.initialize();
 199             recursiveCall();
 200             String result = test.getResult();
 201             // The feature is not fully implemented on all platforms,
 202             // corruptions are still possible.
 203             if (isSupportedPlatform && !result.contains("PASSED")) {
 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 < 100; 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 }