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