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 != 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: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 
initialize()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 
getResult()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 
run()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 
lockAndCall(int i)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 
RunWithSOEContext(ReentrantLockTest test, int deframe)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
run()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 
recursiveCall()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 
isAlwaysSupportedPlatform()235     private static boolean isAlwaysSupportedPlatform() {
236         return Platform.isAix() ||
237             (Platform.isLinux() &&
238              (Platform.isPPC() || Platform.isS390x() || Platform.isX64() ||
239               Platform.isX86() || Platform.isAArch64())) ||
240             Platform.isOSX() ||
241             Platform.isBSD() ||
242             Platform.isSolaris();
243     }
244 
isNeverSupportedPlatform()245     private static boolean isNeverSupportedPlatform() {
246         return !isAlwaysSupportedPlatform();
247     }
248 
249     private static boolean isSupportedPlatform;
250 
initIsSupportedPlatform()251     private static void initIsSupportedPlatform() throws Exception {
252         // In order to dynamicaly determine if the platform supports the reserved
253         // stack area, run with -XX:StackReservedPages=1 and see if we get the
254         // expected warning message for platforms that don't support it.
255         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:StackReservedPages=1", "-version");
256         OutputAnalyzer output = new OutputAnalyzer(pb.start());
257         System.out.println("StackReservedPages=1 log: [" + output.getOutput() + "]");
258         if (output.getExitValue() != 0) {
259             String msg = "Could not launch with -XX:StackReservedPages=1: exit " + output.getExitValue();
260             System.err.println("FAILED: " + msg);
261             throw new RuntimeException(msg);
262         }
263 
264         isSupportedPlatform = true;
265         String matchStr = "Reserved Stack Area not supported on this platform";
266         int match_idx = output.getOutput().indexOf(matchStr);
267         if (match_idx >= 0) {
268             isSupportedPlatform = false;
269         }
270 
271         // Do a sanity check. Some platforms we know are always supported. Make sure
272         // we didn't determine that one of those platforms is not supported.
273         if (!isSupportedPlatform && isAlwaysSupportedPlatform()) {
274             String msg  = "This platform should be supported: " + Platform.getOsArch();
275             System.err.println("FAILED: " +  msg);
276             throw new RuntimeException(msg);
277         }
278 
279         // And some platforms we know are never supported. Make sure
280         // we didn't determine that one of those platforms is supported.
281         if (isSupportedPlatform && isNeverSupportedPlatform()) {
282             String msg  = "This platform should not be supported: " + Platform.getOsArch();
283             System.err.println("FAILED: " +  msg);
284             throw new RuntimeException(msg);
285         }
286     }
287 
main(String[] args)288     public static void main(String[] args) throws Exception {
289         initIsSupportedPlatform();
290         for (int i = 0; i < 100; i++) {
291             // Each iteration has to be executed by a new thread. The test
292             // relies on the random size area pushed by the VM at the beginning
293             // of the stack of each Java thread it creates.
294             Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256));
295             thread.start();
296             try {
297                 thread.join();
298             } catch (InterruptedException ex) { }
299         }
300     }
301 }
302