ReservedStackTest.java revision 12531:ccbd743a62e3
1/*
2 * Copyright (c) 2015, 2016, 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
113import java.util.concurrent.locks.ReentrantLock;
114import jdk.test.lib.Platform;
115
116public class ReservedStackTest {
117
118    static class ReentrantLockTest {
119
120        private ReentrantLock lockArray[];
121        // Frame sizes vary a lot between interpreted code and compiled code
122        // so the lock array has to be big enough to cover all cases.
123        // If test fails with message "Not conclusive test", try to increase
124        // LOCK_ARRAY_SIZE value
125        private static final int LOCK_ARRAY_SIZE = 8192;
126        private boolean stackOverflowErrorReceived;
127        StackOverflowError soe = null;
128        private int index = -1;
129
130        public void initialize() {
131            lockArray = new ReentrantLock[LOCK_ARRAY_SIZE];
132            for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
133                lockArray[i] = new ReentrantLock();
134            }
135            stackOverflowErrorReceived = false;
136        }
137
138        public String getResult() {
139            if (!stackOverflowErrorReceived) {
140                return "ERROR: Not conclusive test: no StackOverflowError received";
141            }
142            for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
143                if (lockArray[i].isLocked()) {
144                    if (!lockArray[i].isHeldByCurrentThread()) {
145                        StringBuilder s = new StringBuilder();
146                        s.append("FAILED: ReentrantLock ");
147                        s.append(i);
148                        s.append(" looks corrupted");
149                        return s.toString();
150                    }
151                }
152            }
153            return "PASSED";
154        }
155
156        public void run() {
157            try {
158                lockAndCall(0);
159            } catch (StackOverflowError e) {
160                soe = e;
161                stackOverflowErrorReceived = true;
162            }
163        }
164
165        private void lockAndCall(int i) {
166            index = i;
167            if (i < LOCK_ARRAY_SIZE) {
168                lockArray[i].lock();
169                lockAndCall(i + 1);
170            }
171        }
172    }
173
174    static class RunWithSOEContext implements Runnable {
175
176        int counter;
177        int deframe;
178        int decounter;
179        int setupSOEFrame;
180        int testStartFrame;
181        ReentrantLockTest test;
182
183        public RunWithSOEContext(ReentrantLockTest test, int deframe) {
184            this.test = test;
185            this.deframe = deframe;
186        }
187
188        @Override
189        @jdk.internal.vm.annotation.ReservedStackAccess
190        public void run() {
191            counter = 0;
192            decounter = deframe;
193            test.initialize();
194            recursiveCall();
195            System.out.println("Framework got StackOverflowError at frame = " + counter);
196            System.out.println("Test started execution at frame = " + (counter - deframe));
197            String result = test.getResult();
198            // The feature is not fully implemented on all platforms,
199            // corruptions are still possible.
200            boolean supportedPlatform =
201                Platform.isAix() ||
202                (Platform.isLinux() &&
203                  (Platform.isPPC() || Platform.isS390x() || Platform.isX64() ||
204                   Platform.isX86() || Platform.isAArch64())) ||
205                Platform.isOSX() ||
206                Platform.isSolaris();
207            if (supportedPlatform && !result.contains("PASSED")) {
208                System.out.println(result);
209                throw new Error(result);
210            } else {
211                // Either the test passed or this platform is not supported.
212                // On not supported platforms, we only expect the VM to
213                // not crash during the test. This is especially important
214                // on Windows where the detection of SOE in annotated
215                // sections is implemented but the reserved zone mechanism
216                // to avoid the corruption cannot be implemented yet
217                // because of JDK-8067946
218                System.out.println("PASSED");
219            }
220        }
221
222        void recursiveCall() {
223            // Unused local variables to increase the frame size
224            long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19;
225            long l20, l21, l22, l23, l24, l25, l26, l27, l28, l30, l31, l32, l33, l34, l35, l36, l37;
226            counter++;
227            try {
228                recursiveCall();
229            } catch (StackOverflowError e) {
230            }
231            decounter--;
232            if (decounter == 0) {
233                setupSOEFrame = counter;
234                testStartFrame = counter - deframe;
235                test.run();
236            }
237        }
238    }
239
240    public static void main(String[] args) {
241        for (int i = 0; i < 1000; i++) {
242            // Each iteration has to be executed by a new thread. The test
243            // relies on the random size area pushed by the VM at the beginning
244            // of the stack of each Java thread it creates.
245            Thread thread = new Thread(new RunWithSOEContext(new ReentrantLockTest(), 256));
246            thread.start();
247            try {
248                thread.join();
249            } catch (InterruptedException ex) { }
250        }
251    }
252}
253