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