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 java.lang.reflect.InvocationTargetException;
25import java.security.AccessController;
26import java.security.PrivilegedAction;
27import java.util.EnumSet;
28import java.util.concurrent.atomic.AtomicLong;
29import java.lang.StackWalker.StackFrame;
30import java.lang.invoke.MethodHandle;
31import java.lang.invoke.MethodHandles;
32import java.lang.invoke.MethodType;
33import java.util.Objects;
34
35import static java.lang.StackWalker.Option.*;
36
37/**
38 * @test
39 * @bug 8140450
40 * @summary Verify stack trace information obtained with respect to StackWalker
41 *          options, when the stack contains lambdas, method handle invoke
42 *          virtual calls, and reflection.
43 * @run main/othervm VerifyStackTrace
44 * @run main/othervm/java.security.policy=stackwalk.policy VerifyStackTrace
45 * @author danielfuchs
46 */
47public class VerifyStackTrace {
48
49    static interface TestCase {
50        StackWalker walker();
51        String description();
52        String expected();
53    }
54    static final class TestCase1 implements TestCase {
55        private final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
56
57        private final String description = "StackWalker.getInstance(" +
58            "StackWalker.Option.RETAIN_CLASS_REFERENCE)";
59
60        // Note: line numbers and lambda hashes will be erased when
61        //       comparing stack traces. However, the stack may change
62        //       if some methods are being renamed in the code base.
63        // If the  JDKcode base changes and the test fails because of that,
64        // then after validating that the actual stack trace obtained
65        // is indeed correct (no frames are skipped that shouldn't)
66        // then you can cut & paste the <-- actual --> stack printed in the
67        // test output in here:
68        private final String expected =
69            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:209)\n" +
70            "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:145)\n" +
71            "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:158)\n" +
72            "4: VerifyStackTrace.invoke(VerifyStackTrace.java:188)\n" +
73            "5: VerifyStackTrace$1.run(VerifyStackTrace.java:218)\n" +
74            "6: java.base/java.security.AccessController.doPrivileged(Native Method)\n" +
75            "7: VerifyStackTrace.test(VerifyStackTrace.java:227)\n" +
76            "8: VerifyStackTrace.main(VerifyStackTrace.java:182)\n";
77
78        @Override public StackWalker walker() { return walker;}
79        @Override public String description() { return description;}
80        @Override public String expected()    { return expected;}
81    }
82    static final class TestCase2 implements TestCase {
83        private final StackWalker walker = StackWalker.getInstance(
84                EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES));
85
86        private final String description = "nStackWalker.getInstance(" +
87            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
88            "StackWalker.Option.SHOW_REFLECT_FRAMES)";
89
90        // Note: line numbers and lambda hashes will be erased when
91        //       comparing stack traces. However, the stack may change
92        //       if some methods are being renamed in the code base.
93        // If the JDK code base changes and the test fails because of that,
94        // then after validating that the actual stack trace obtained
95        // is indeed correct (no frames are skipped that shouldn't)
96        // then you can cut & paste the <-- actual --> stack printed in the
97        // test output in here (don't forget the final \n):
98        private final String expected =
99            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:211)\n" +
100            "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:147)\n" +
101            "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:160)\n" +
102            "4: VerifyStackTrace.invoke(VerifyStackTrace.java:190)\n" +
103            "5: java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
104            "6: java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" +
105            "7: java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" +
106            "8: java.base/java.lang.reflect.Method.invoke(Method.java:520)\n" +
107            "9: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" +
108            "10: java.base/java.security.AccessController.doPrivileged(Native Method)\n" +
109            "11: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" +
110            "12: VerifyStackTrace.main(VerifyStackTrace.java:185)\n";
111
112        @Override public StackWalker walker() { return walker;}
113        @Override public String description() { return description;}
114        @Override public String expected()    { return expected;}
115    }
116    static class TestCase3 implements TestCase {
117        private final StackWalker walker = StackWalker.getInstance(
118                EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES));
119
120        private final String description = "StackWalker.getInstance(" +
121            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
122            "StackWalker.Option.SHOW_HIDDEN_FRAMES)";
123
124        // Note: line numbers and lambda hashes will be erased when
125        //       comparing stack traces. However, the stack may change
126        //       if some methods are being renamed in the code base.
127        // If the JDK code base changes and the test fails because of that,
128        // then after validating that the actual stack trace obtained
129        // is indeed correct (no frames are skipped that shouldn't)
130        // then you can cut & paste the <-- actual --> stack printed in the
131        // test output in here (don't forget the final \n):
132        private final String expected =
133            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:213)\n" +
134            "2: VerifyStackTrace$$Lambda$1/662441761.run(Unknown Source)\n" +
135            "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" +
136            "4: java.base/java.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(LambdaForm$DMH)\n" +
137            "5: java.base/java.lang.invoke.LambdaForm$MH/1395089624.invoke_MT(LambdaForm$MH)\n" +
138            "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:162)\n" +
139            "7: VerifyStackTrace.invoke(VerifyStackTrace.java:192)\n" +
140            "8: java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
141            "9: java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" +
142            "10: java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" +
143            "11: java.base/java.lang.reflect.Method.invoke(Method.java:520)\n" +
144            "12: VerifyStackTrace$1.run(VerifyStackTrace.java:222)\n" +
145            "13: java.base/java.security.AccessController.doPrivileged(Native Method)\n" +
146            "14: VerifyStackTrace.test(VerifyStackTrace.java:231)\n" +
147            "15: VerifyStackTrace.main(VerifyStackTrace.java:188)\n";
148
149        @Override public StackWalker walker() { return walker;}
150        @Override public String description() { return description;}
151        @Override public String expected()    { return expected;}
152    }
153
154    static final class TestCase4 extends TestCase3 {
155        private final StackWalker walker = StackWalker.getInstance(
156                EnumSet.allOf(StackWalker.Option.class));
157
158        private final String description = "StackWalker.getInstance(" +
159            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
160            "StackWalker.Option.SHOW_HIDDEN_FRAMES, " +
161            "StackWalker.Option.SHOW_REFLECT_FRAMES)";
162
163        @Override public StackWalker walker() {return walker;}
164        @Override public String description() {return description;}
165    }
166
167    public static class Handle implements Runnable {
168
169        Runnable impl;
170        public Handle(Runnable run) {
171            this.impl = run;
172        }
173
174        public void execute(Runnable run) {
175            run.run();
176        }
177
178        public void run() {
179            MethodHandles.Lookup lookup = MethodHandles.lookup();
180            MethodHandle handle = null;
181            try {
182                handle = lookup.findVirtual(Handle.class, "execute",
183                        MethodType.methodType(void.class, Runnable.class));
184            } catch(NoSuchMethodException | IllegalAccessException x) {
185                throw new RuntimeException(x);
186            }
187            try {
188                handle.invoke(this, impl);
189            } catch(Error | RuntimeException x) {
190                throw x;
191            } catch(Throwable t) {
192                throw new RuntimeException(t);
193            }
194        }
195    }
196
197    static String prepare(String produced, boolean eraseSensitiveInfo) {
198        if (eraseSensitiveInfo) {
199            // Erase sensitive information before comparing:
200            // comparing line numbers is too fragile, so we just erase them
201            // out before comparing. We also erase the hash-like names of
202            // synthetic frames introduced by lambdas & method handles
203            return produced.replaceAll(":[1-9][0-9]*\\)", ":00)")
204                    .replaceAll("/[0-9]+\\.run", "/xxxxxxxx.run")
205                    .replaceAll("/[0-9]+\\.invoke", "/xxxxxxxx.invoke")
206                    // LFs may or may not be pre-generated, making frames differ
207                    .replaceAll("DirectMethodHandle\\$Holder", "LambdaForm\\$DMH")
208                    .replaceAll("Invokers\\$Holder", "LambdaForm\\$MH")
209                    .replaceAll("MH\\.invoke", "MH/xxxxxxxx.invoke")
210                    // invoke frames may or may not have basic method type
211                    // information encoded for diagnostic purposes
212                    .replaceAll("xx\\.invoke([A-Za-z]*)_[A-Z_]+", "xx.invoke$1")
213                    .replaceAll("\\$[0-9]+", "\\$??");
214        } else {
215            return produced;
216        }
217    }
218
219
220    public static void main(String[] args) {
221        test(new TestCase1());
222        test(new TestCase2());
223        test(new TestCase3());
224        test(new TestCase4());
225    }
226
227    public static void invoke(Runnable run) {
228        run.run();
229    }
230
231    static final class Recorder {
232        boolean found; // stop recording after main
233        public void recordSTE(long counter, StringBuilder s, StackFrame f) {
234            if (found) return;
235            found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
236                    "main".equals(f.getMethodName());
237            String line = String.format("%d: %s", counter, f.toStackTraceElement());
238            s.append(line).append('\n');
239            System.out.println(line);
240        }
241    }
242
243
244    static void test(TestCase test) {
245        System.out.println("\nTesting: " + test.description());
246        final AtomicLong counter = new AtomicLong();
247        final StringBuilder builder = new StringBuilder();
248        final Recorder recorder = new Recorder();
249        final Runnable run = () -> test.walker().forEach(
250                f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
251        final Handle handle = new Handle(run);
252
253        // We're not using lambda on purpose here. We want the anonymous
254        // class on the stack.
255        PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
256            @Override
257            public Object run() {
258                try {
259                    return VerifyStackTrace.class
260                            .getMethod("invoke", Runnable.class)
261                            .invoke(null, handle);
262                } catch (NoSuchMethodException
263                        | IllegalAccessException
264                        | InvocationTargetException ex) {
265                    System.out.flush();
266                    throw new RuntimeException(ex);
267                }
268            }
269        };
270        AccessController.doPrivileged(pa);
271        System.out.println("Main found: " + recorder.found);
272        if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
273            System.out.flush();
274            try {
275                // sleep to make it less likely that System.out & System.err will
276                // interleave.
277                Thread.sleep(1000);
278            } catch (InterruptedException ex) {
279            }
280            System.err.println("\nUnexpected stack trace: "
281                    + "\n<!-- expected -->\n"
282                    + prepare(test.expected(), true)
283                    + "\n<--  actual -->\n"
284                    + prepare(builder.toString(), false));
285            throw new RuntimeException("Unexpected stack trace  for: " + test.description());
286        }
287    }
288
289
290}
291