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  * @library /testlibrary
  27  * @modules java.base/jdk.internal.vm.annotation
  28  * @build jdk.test.lib.*
  29  * @run main/othervm -XX:-Inline -XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer.setExclusiveOwnerThread ReservedStackTest
  30  */
  31 
  32 /* The exclusion of java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread()
  33  * from the compilable methods is required to ensure that the test will be able
  34  * to trigger a StackOverflowError on the right method.
  35  */
  36 
  37 
  38 /*
  39  * Notes about this test:
  40  * This test tries to reproduce a rare but nasty corruption bug that
  41  * occurs when a StackOverflowError is thrown in some critical sections
  42  * of the ReentrantLock implementation.
  43  *
  44  * Here's the critical section where a corruption could occur
  45  * (from java.util.concurrent.ReentrantLock.java)
  46  *
  47  * final void lock() {
  48  *     if (compareAndSetState(0, 1))
  49  *         setExclusiveOwnerThread(Thread.currentThread());
  50  *     else
  51  *         acquire(1);
  52  * }
  53  *
  54  * The corruption occurs when the compareAndSetState(0, 1)
  55  * successfully updates the status of the lock but the method
  56  * fails to set the owner because of a stack overflow.
  57  * HotSpot checks for stack overflow on method invocations.
  58  * The test must trigger a stack overflow either when
  59  * Thread.currentThread() or setExclusiveOwnerThread() is
  60  * invoked.
  61  *
  62  * The test starts with a recursive invocation loop until a
  63  * first StackOverflowError is thrown, the Error is caught
  64  * and a few dozen frames are exited. Now the thread has
  65  * little free space on its execution stack and will try
  66  * to trigger a stack overflow in the critical section.
  67  * The test has a huge array of ReentrantLocks instances.
  68  * The thread invokes a recursive method which, at each
  69  * of its invocations, tries to acquire the next lock
  70  * in the array. The execution continues until a
  71  * StackOverflowError is thrown or the end of the array
  72  * is reached.
  73  * If no StackOverflowError has been thrown, the test
  74  * is non conclusive (recommendation: increase the size
  75  * of the ReentrantLock array).
  76  * The status of all Reentrant locks in the array is checked,
  77  * if a corruption is detected, the test failed, otherwise
  78  * the test passed.
  79  *
  80  * To have a chance that the stack overflow occurs on one
  81  * of the two targeted method invocations, the test is
  82  * repeated in different threads. Each Java thread has a
  83  * random size area allocated at the beginning of its
  84  * stack to prevent false sharing. The test relies on this
  85  * to have different stack alignments when it hits the targeted
  86  * methods (the test could have been written with a native
  87  * method with alloca, but using different Java threads makes
  88  * the test 100% Java).
  89  *
  90  * One additional trick is required to ensure that the stack
  91  * overflow will occur on the Thread.currentThread() getter
  92  * or the setExclusiveOwnerThread() setter.
  93  *
  94  * Potential stack overflows are detected by stack banging,
  95  * at method invocation time.
  96  * In interpreted code, the stack banging performed for the
  97  * lock() method goes further than the stack banging performed
  98  * for the getter or the setter method, so the potential stack
  99  * overflow is detected before entering the critical section.
 100  * In compiled code, the getter and the setter are in-lined,
 101  * so the stack banging is only performed before entering the
 102  * critical section.
 103  * In order to have a stack banging that goes further for the
 104  * getter/setter methods than for the lock() method, the test
 105  * exploits the property that interpreter frames are (much)
 106  * bigger than compiled code frames. When the test is run,
 107  * a compiler option disables the compilation of the
 108  * setExclusiveOwnerThread() method.
 109  *
 110  */
 111 
 112 import java.util.concurrent.locks.ReentrantLock;
 113 import jdk.test.lib.Platform;
 114 
 115 public class ReservedStackTest {
 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             // The feature is not fully implemented on all platforms,
 198             // corruptions are still possible
 199             boolean supportedPlatform = Platform.isSolaris() || Platform.isOSX()
 200                 || (Platform.isLinux() && (Platform.isX86() || Platform.isX64()));
 201             if (supportedPlatform && !result.contains("PASSED")) {
 202                 System.out.println(result);
 203                 throw new Error(result);
 204             } else {
 205                 // Either the test passed or this platform is not supported.
 206                 // On not supported platforms, we only expect the VM to
 207                 // not crash during the test. This is especially important
 208                 // on Windows where the detection of SOE in annotated
 209                 // sections is implemented but the reserved zone mechanism
 210                 // to avoid the corruption cannot be implemented yet
 211                 // because of JDK-8067946
 212                 System.out.println("PASSED");
 213             }
 214         }
 215 
 216         void recursiveCall() {
 217             // Unused local variables to increase the frame size
 218             long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19;
 219             long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37;
 220             counter++;
 221             try {
 222                 recursiveCall();
 223             } catch (StackOverflowError e) {
 224             }
 225             decounter--;
 226             if (decounter == 0) {
 227                 setupSOEFrame = counter;
 228                 testStartFrame = counter - deframe;
 229                 test.run();
 230             }
 231         }
 232     }
 233 
 234     public static void main(String[] args) {
 235         for (int i = 0; i < 1000; i++) {
 236             // Each iteration has to be executed by a new thread. The test
 237             // relies on the random size area pushed by the VM at the beginning
 238             // of the stack of each Java thread it creates.
 239             Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256));
 240             thread.start();
 241             try {
 242                 thread.join();
 243             } catch (InterruptedException ex) { }
 244         }
 245     }
 246 }