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
24import static java.lang.StackWalker.Option.*;
25import java.lang.StackWalker.StackFrame;
26import java.util.Arrays;
27import java.util.EnumSet;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Random;
31import java.util.Set;
32import java.util.TreeSet;
33
34import jdk.testlibrary.RandomFactory;
35
36/**
37 * @test
38 * @bug 8140450
39 * @summary Stack Walk Test (use -Dseed=X to set PRNG seed)
40 * @library /lib/testlibrary
41 * @build jdk.testlibrary.*
42 * @compile StackRecorderUtil.java
43 * @run main/othervm StackWalkTest
44 * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest
45 * @run main/othervm StackWalkTest -random:50
46 * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest -random:50
47 * @author danielfuchs, bchristi
48 * @key randomness
49 */
50public class StackWalkTest {
51    private static boolean random = false;
52    private static boolean verbose = false;
53    private static int randomRuns = 50;
54
55    private static final int MAX_RANDOM_DEPTH = 1000;
56
57    static final Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList(
58            "jdk.internal.reflect.NativeMethodAccessorImpl",
59            "jdk.internal.reflect.DelegatingMethodAccessorImpl",
60            "java.lang.reflect.Method",
61            "com.sun.javatest.regtest.MainWrapper$MainThread",
62            "com.sun.javatest.regtest.agent.MainWrapper$MainThread",
63            "java.lang.Thread"
64    ));
65    static final List<Class<?>> streamPipelines = Arrays.asList(
66        classForName("java.util.stream.AbstractPipeline"),
67        classForName("java.util.stream.TerminalOp")
68    );
69    static Class<?> classForName(String name) {
70        try {
71            return Class.forName(name);
72        } catch (ClassNotFoundException e){
73            throw new RuntimeException(e);
74        }
75    }
76
77    private static boolean isStreamPipeline(Class<?> clazz) {
78        for (Class<?> c : streamPipelines) {
79            if (c.isAssignableFrom(clazz)) {
80                return true;
81            }
82        }
83        return false;
84    }
85
86    StackRecorderUtil recorder;
87    int count = 0;
88    boolean didWalk = false;
89
90    final int estDepth;
91    final Set<StackWalker.Option> swOptions;
92
93    public StackWalkTest() {
94        this(EnumSet.noneOf(StackWalker.Option.class), -1);
95    }
96
97    public StackWalkTest(Set<StackWalker.Option> swOptions) {
98        this(swOptions, -1);
99    }
100
101    public StackWalkTest(int estimatedDepth) {
102        this(EnumSet.noneOf(StackWalker.Option.class), -1);
103    }
104
105    public StackWalkTest(Set<StackWalker.Option> swOptions, int estimatedDepth) {
106        this.swOptions = swOptions;
107        this.estDepth = estimatedDepth;
108    }
109
110    private StackWalker createStackWalker() {
111        // test all StackWalker factory methods
112        if (this.estDepth < 0) {
113            if (swOptions.isEmpty()) {
114                return StackWalker.getInstance();
115            } else {
116                return StackWalker.getInstance(swOptions);
117            }
118        }
119        return StackWalker.getInstance(swOptions, estDepth);
120    }
121    public void consume(StackFrame sf) {
122        if (count == 0 && swOptions.contains(StackWalker.Option.RETAIN_CLASS_REFERENCE)
123                && isStreamPipeline(sf.getDeclaringClass())) {
124            return;
125        }
126        if (verbose) {
127            System.out.println("\t" + sf.getClassName() + "." + sf.getMethodName());
128        }
129        if (count >= recorder.frameCount()) {
130            // We've gone past main()...
131            if (infrastructureClasses.contains(sf.getClassName())) {
132                // safe to ignore
133                return;
134            }
135        }
136        try {
137            recorder.compareFrame(count, sf);
138        } catch (IndexOutOfBoundsException e) {
139            // Extra non-infra frame in stream
140            throw new RuntimeException("extra non-infra stack frame at count "
141                    + count + ": <" + sf + ">", e);
142        }
143        count++;
144    }
145
146    public class Call {
147        public void walk(int total, int markAt) {
148            recorder.add(Call.class, "walk", "StackWalkTest.java");
149            long swFrameCount = createStackWalker().walk(s -> s.count());
150
151            if (verbose) {
152                System.out.println("Call.walk() total=" + total + ", markAt=" + markAt);
153                System.out.println("recorder frames:");
154                for (StackRecorderUtil.TestFrame f : recorder) {
155                    System.out.println("\t" + f.declaringClass + "." + f.methodName);
156                }
157                System.out.println("\nStackWalker recorded " + swFrameCount + " frames");
158                System.out.flush();
159            }
160            long recFrameCount = (long)recorder.frameCount();
161            if (swFrameCount < recFrameCount) {
162                throw new RuntimeException("StackWalker recorded fewer frames ("+
163                        swFrameCount + ") than recorded ("+ recorder.frameCount() +
164                        ") - " + "estimatedDepth set to " + estDepth);
165            }
166            if (verbose) {
167                System.out.println("StackWalker frames:");
168            }
169            createStackWalker().forEach(StackWalkTest.this::consume);
170            didWalk = true;
171        }
172        public void call(int total, int current, int markAt) {
173            recorder.add(Call.class, "call", "StackWalkTest.java");
174            if (current < total) {
175                testCall.call(total, current+1, markAt);
176            } else {
177                walk(total, markAt);
178            }
179        }
180    }
181
182    public class Marker extends Call {
183        @Override
184        public void call(int total, int current, int markAt) {
185            recorder.add(Marker.class, "call", "StackWalkTest.java");
186            if (current < total) {
187                testCall.call(total, current+1, markAt);
188            } else {
189                walk(total, markAt);
190            }
191        }
192    }
193    private Call markerCall = new Marker();
194
195    public class Test extends Call {
196        @Override
197        public void call(int total, int current, int markAt) {
198            recorder.add(Test.class, "call", "StackWalkTest.java");
199            if (current < total) {
200                int nexti = current + 1;
201                if (nexti==markAt) {
202                    markerCall.call(total, nexti, markAt);
203                } else {
204                    testCall.call2(total, nexti, markAt);
205                }
206            } else {
207                walk(total, markAt);
208            }
209        }
210        public void call2(int total, int current, int markAt) {
211            recorder.add(Test.class, "call2", "StackWalkTest.java");
212            if (current < total) {
213                int nexti = current + 1;
214                if (nexti==markAt) {
215                    markerCall.call(total, nexti, markAt);
216                } else {
217                    test2Call.call(total, nexti, markAt);
218                }
219            } else {
220                walk(total, markAt);
221            }
222        }
223    }
224    private Test testCall = new Test();
225
226    /** Inherits call() from Call */
227    public class Test2 extends Call {}
228    private Test2 test2Call = new Test2();
229
230    public void runTest(Class callerClass, String callerMethod, int stackDepth,
231                        int markAt) {
232        if (didWalk) {
233            throw new IllegalStateException("StackWalkTest already used");
234        }
235        // Test may run into StackOverflow when running in -Xcomp mode on deep stack
236        assert stackDepth <= 1000;
237        assert markAt <= stackDepth : "markAt(" + markAt + ") > stackDepth("
238                + stackDepth + ")";
239        System.out.print("runTest(" + swOptions
240                + "), estimatedDepth=" + estDepth);
241
242        recorder = new StackRecorderUtil(swOptions);
243        recorder.add(callerClass, callerMethod, "StackWalkTest.java");
244        recorder.add(StackWalkTest.class, "runTest", "StackWalkTest.java");
245
246        Test test1 = new Test();
247        test1.call(stackDepth, 0, markAt);
248
249        System.out.println(" finished");
250        if (!didWalk) {
251            throw new IllegalStateException("Test wasn't actually performed");
252        }
253    }
254
255    public static void main(String[] args) {
256        String rand = "-random";
257        String randItems = "-random:";
258        for(String arg : args) {
259            if (arg.startsWith(rand)) {
260                random = true;
261                try {
262                    if(arg.startsWith(randItems)) {
263                        randomRuns = Integer.valueOf(arg.substring(randItems.length()));
264                    }
265                } catch(NumberFormatException e) {}
266            } else if("-verbose".equals(arg)) {
267                verbose = true;
268            }
269        }
270        if (random) {
271            Random rng = RandomFactory.getRandom();
272            for (int iters = 0; iters < randomRuns; iters++) {
273                Set<StackWalker.Option> opts = new HashSet<>();
274                if (rng.nextBoolean()) {
275                    opts.add(RETAIN_CLASS_REFERENCE);
276                }
277
278                int depth = 1 + rng.nextInt(MAX_RANDOM_DEPTH);
279
280                StackWalkTest swt;
281                if (rng.nextBoolean() && depth > 1) {
282                    // Test that specifying an estimatedDepth doesn't prevent
283                    // full stack traversal
284                    swt = new StackWalkTest(opts, 1+rng.nextInt(depth-1));
285                } else {
286                    swt = new StackWalkTest(opts);
287                }
288
289                int markAt = rng.nextInt(depth+1);
290                System.out.print(depth + "@" + markAt + " ");
291                System.out.flush();
292                swt.runTest(StackWalkTest.class, "main", depth, markAt);
293            }
294        } else {
295            // Long stack, default maxDepth
296            StackWalkTest swt;
297            swt = new StackWalkTest();
298            swt.runTest(StackWalkTest.class, "main", 1000, 10);
299
300            // Long stack, matching maxDepth
301            swt = new StackWalkTest(2000);
302            swt.runTest(StackWalkTest.class, "main", 1000, 10);
303
304            // Long stack, maximum maxDepth
305            swt = new StackWalkTest(Integer.MAX_VALUE);
306            swt.runTest(StackWalkTest.class, "main", 1000, 10);
307
308            //
309            // Single batch
310            //
311            swt = new StackWalkTest(); // default maxDepth
312            swt.runTest(StackWalkTest.class, "main", 6, 3);
313
314            swt = new StackWalkTest(4); // maxDepth < stack
315            swt.runTest(StackWalkTest.class, "main", 6, 3);
316
317            swt = new StackWalkTest(2); // maxDepth < marker
318            swt.runTest(StackWalkTest.class, "main", 6, 4);
319
320            //
321            // 2 batches
322            //
323            swt = new StackWalkTest(); // default maxDepth
324            swt.runTest(StackWalkTest.class, "main", 24, 10);
325            swt = new StackWalkTest(18); // maxDepth < stack
326            swt.runTest(StackWalkTest.class, "main", 24, 10);
327            swt = new StackWalkTest(8); // maxDepth < marker
328            swt.runTest(StackWalkTest.class, "main", 24, 10);
329
330            //
331            // 3 batch
332            //
333            swt = new StackWalkTest(); // default maxDepth
334            swt.runTest(StackWalkTest.class, "main", 60, 20);
335            swt = new StackWalkTest(35); // maxDepth < stack
336            swt.runTest(StackWalkTest.class, "main", 60, 20);
337            swt = new StackWalkTest(8); // maxDepth < marker
338            swt.runTest(StackWalkTest.class, "main", 60, 20);
339
340            //
341            // StackWalker.Options
342            //
343            swt = new StackWalkTest();
344            swt.runTest(StackWalkTest.class, "main", 50, 10);
345
346            swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE));
347            swt.runTest(StackWalkTest.class, "main", 80, 40);
348
349            swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE), 50);
350            swt.runTest(StackWalkTest.class, "main", 1000, 524);
351        }
352    }
353}
354