VerifyStackTrace.java revision 13901:b2a69d66dc65
1213237Sgonzo/*
2213237Sgonzo * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3213237Sgonzo * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4213237Sgonzo *
5213237Sgonzo * This code is free software; you can redistribute it and/or modify it
6213237Sgonzo * under the terms of the GNU General Public License version 2 only, as
7213237Sgonzo * published by the Free Software Foundation.
8213237Sgonzo *
9213237Sgonzo * This code is distributed in the hope that it will be useful, but WITHOUT
10213237Sgonzo * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11213237Sgonzo * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12213237Sgonzo * version 2 for more details (a copy is included in the LICENSE file that
13213237Sgonzo * accompanied this code).
14213237Sgonzo *
15213237Sgonzo * You should have received a copy of the GNU General Public License version
16213237Sgonzo * 2 along with this work; if not, write to the Free Software Foundation,
17213237Sgonzo * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18213237Sgonzo *
19213237Sgonzo * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20213237Sgonzo * or visit www.oracle.com if you need additional information or have any
21213237Sgonzo * questions.
22213237Sgonzo */
23213237Sgonzo
24213237Sgonzoimport java.lang.reflect.InvocationTargetException;
25213237Sgonzoimport java.security.AccessController;
26213237Sgonzoimport java.security.PrivilegedAction;
27213237Sgonzoimport java.util.EnumSet;
28213237Sgonzoimport java.util.concurrent.atomic.AtomicLong;
29213237Sgonzoimport java.lang.StackWalker.StackFrame;
30213237Sgonzoimport java.lang.invoke.MethodHandle;
31213237Sgonzoimport java.lang.invoke.MethodHandles;
32213237Sgonzoimport java.lang.invoke.MethodType;
33213237Sgonzoimport java.util.Objects;
34213237Sgonzo
35213237Sgonzoimport static java.lang.StackWalker.Option.*;
36213237Sgonzo
37213237Sgonzo/**
38213237Sgonzo * @test
39213237Sgonzo * @bug 8140450
40213237Sgonzo * @summary Verify stack trace information obtained with respect to StackWalker
41213237Sgonzo *          options, when the stack contains lambdas, method handle invoke
42213237Sgonzo *          virtual calls, and reflection.
43213237Sgonzo * @run main/othervm -XX:-MemberNameInStackFrame VerifyStackTrace
44213237Sgonzo * @run main/othervm -XX:+MemberNameInStackFrame VerifyStackTrace
45213237Sgonzo * @run main/othervm/java.security.policy=stackwalk.policy VerifyStackTrace
46213237Sgonzo * @author danielfuchs
47213237Sgonzo */
48213237Sgonzopublic class VerifyStackTrace {
49213237Sgonzo
50213237Sgonzo    static interface TestCase {
51213237Sgonzo        StackWalker walker();
52213237Sgonzo        String description();
53213237Sgonzo        String expected();
54213237Sgonzo    }
55213237Sgonzo    static final class TestCase1 implements TestCase {
56213237Sgonzo        private final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
57213237Sgonzo
58213237Sgonzo        private final String description = "StackWalker.getInstance(" +
59213237Sgonzo            "StackWalker.Option.RETAIN_CLASS_REFERENCE)";
60213237Sgonzo
61213237Sgonzo        // Note: line numbers and lambda hashes will be erased when
62213237Sgonzo        //       comparing stack traces. However, the stack may change
63298738Smmel        //       if some methods are being renamed in the code base.
64298738Smmel        // If the  JDKcode base changes and the test fails because of that,
65298738Smmel        // then after validating that the actual stack trace obtained
66298738Smmel        // is indeed correct (no frames are skipped that shouldn't)
67298738Smmel        // then you can cut & paste the <-- actual --> stack printed in the
68298738Smmel        // test output in here:
69298738Smmel        private final String expected =
70298738Smmel            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:209)\n" +
71298738Smmel            "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:145)\n" +
72298738Smmel            "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:158)\n" +
73324755Sian            "4: VerifyStackTrace.invoke(VerifyStackTrace.java:188)\n" +
74324755Sian            "5: VerifyStackTrace$1.run(VerifyStackTrace.java:218)\n" +
75298738Smmel            "6: java.security.AccessController.doPrivileged(java.base/Native Method)\n" +
76298738Smmel            "7: VerifyStackTrace.test(VerifyStackTrace.java:227)\n" +
77298738Smmel            "8: VerifyStackTrace.main(VerifyStackTrace.java:182)\n";
78298738Smmel
79298738Smmel        @Override public StackWalker walker() { return walker;}
80298738Smmel        @Override public String description() { return description;}
81298738Smmel        @Override public String expected()    { return expected;}
82298738Smmel    }
83298738Smmel    static final class TestCase2 implements TestCase {
84298738Smmel        private final StackWalker walker = StackWalker.getInstance(
85213237Sgonzo                EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES));
86213237Sgonzo
87213237Sgonzo        private final String description = "nStackWalker.getInstance(" +
88213237Sgonzo            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
89213237Sgonzo            "StackWalker.Option.SHOW_REFLECT_FRAMES)";
90213237Sgonzo
91213237Sgonzo        // Note: line numbers and lambda hashes will be erased when
92213237Sgonzo        //       comparing stack traces. However, the stack may change
93213237Sgonzo        //       if some methods are being renamed in the code base.
94213237Sgonzo        // If the JDK code base changes and the test fails because of that,
95213237Sgonzo        // then after validating that the actual stack trace obtained
96213237Sgonzo        // is indeed correct (no frames are skipped that shouldn't)
97213237Sgonzo        // then you can cut & paste the <-- actual --> stack printed in the
98213237Sgonzo        // test output in here (don't forget the final \n):
99213237Sgonzo        private final String expected =
100324755Sian            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:211)\n" +
101324755Sian            "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:147)\n" +
102324755Sian            "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:160)\n" +
103324755Sian            "4: VerifyStackTrace.invoke(VerifyStackTrace.java:190)\n" +
104324755Sian            "5: sun.reflect.NativeMethodAccessorImpl.invoke0(java.base/Native Method)\n" +
105324755Sian            "6: sun.reflect.NativeMethodAccessorImpl.invoke(java.base/NativeMethodAccessorImpl.java:62)\n" +
106324755Sian            "7: sun.reflect.DelegatingMethodAccessorImpl.invoke(java.base/DelegatingMethodAccessorImpl.java:43)\n" +
107324755Sian            "8: java.lang.reflect.Method.invoke(java.base/Method.java:520)\n" +
108324755Sian            "9: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" +
109324755Sian            "10: java.security.AccessController.doPrivileged(java.base/Native Method)\n" +
110324755Sian            "11: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" +
111324755Sian            "12: VerifyStackTrace.main(VerifyStackTrace.java:185)\n";
112324755Sian
113324755Sian        @Override public StackWalker walker() { return walker;}
114324755Sian        @Override public String description() { return description;}
115324755Sian        @Override public String expected()    { return expected;}
116324755Sian    }
117324755Sian    static class TestCase3 implements TestCase {
118324755Sian        private final StackWalker walker = StackWalker.getInstance(
119324755Sian                EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES));
120324755Sian
121324755Sian        private final String description = "StackWalker.getInstance(" +
122324755Sian            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
123324755Sian            "StackWalker.Option.SHOW_HIDDEN_FRAMES)";
124324755Sian
125324755Sian        // Note: line numbers and lambda hashes will be erased when
126324755Sian        //       comparing stack traces. However, the stack may change
127324755Sian        //       if some methods are being renamed in the code base.
128324755Sian        // If the JDK code base changes and the test fails because of that,
129324755Sian        // then after validating that the actual stack trace obtained
130324755Sian        // is indeed correct (no frames are skipped that shouldn't)
131324755Sian        // then you can cut & paste the <-- actual --> stack printed in the
132324755Sian        // test output in here (don't forget the final \n):
133324755Sian        private final String expected =
134324755Sian            "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:213)\n" +
135324755Sian            "2: VerifyStackTrace$$Lambda$1/662441761.run(Unknown Source)\n" +
136324755Sian            "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" +
137324755Sian            "4: java.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(java.base/LambdaForm$DMH)\n" +
138324755Sian            "5: java.lang.invoke.LambdaForm$MH/1395089624.invoke_MT(java.base/LambdaForm$MH)\n" +
139324755Sian            "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:162)\n" +
140324755Sian            "7: VerifyStackTrace.invoke(VerifyStackTrace.java:192)\n" +
141324755Sian            "8: sun.reflect.NativeMethodAccessorImpl.invoke0(java.base/Native Method)\n" +
142324755Sian            "9: sun.reflect.NativeMethodAccessorImpl.invoke(java.base/NativeMethodAccessorImpl.java:62)\n" +
143324755Sian            "10: sun.reflect.DelegatingMethodAccessorImpl.invoke(java.base/DelegatingMethodAccessorImpl.java:43)\n" +
144324755Sian            "11: java.lang.reflect.Method.invoke(java.base/Method.java:520)\n" +
145324755Sian            "12: VerifyStackTrace$1.run(VerifyStackTrace.java:222)\n" +
146324755Sian            "13: java.security.AccessController.doPrivileged(java.base/Native Method)\n" +
147324755Sian            "14: VerifyStackTrace.test(VerifyStackTrace.java:231)\n" +
148324755Sian            "15: VerifyStackTrace.main(VerifyStackTrace.java:188)\n";
149324755Sian
150324755Sian        @Override public StackWalker walker() { return walker;}
151324755Sian        @Override public String description() { return description;}
152324755Sian        @Override public String expected()    { return expected;}
153324755Sian    }
154324755Sian
155324755Sian    static final class TestCase4 extends TestCase3 {
156324755Sian        private final StackWalker walker = StackWalker.getInstance(
157324755Sian                EnumSet.allOf(StackWalker.Option.class));
158324755Sian
159324755Sian        private final String description = "StackWalker.getInstance(" +
160324755Sian            "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
161324755Sian            "StackWalker.Option.SHOW_HIDDEN_FRAMES, " +
162324755Sian            "StackWalker.Option.SHOW_REFLECT_FRAMES)";
163324755Sian
164324755Sian        @Override public StackWalker walker() {return walker;}
165213237Sgonzo        @Override public String description() {return description;}
166213237Sgonzo    }
167213237Sgonzo
168213237Sgonzo    public static class Handle implements Runnable {
169213237Sgonzo
170213237Sgonzo        Runnable impl;
171213237Sgonzo        public Handle(Runnable run) {
172213237Sgonzo            this.impl = run;
173279761Sloos        }
174324755Sian
175324755Sian        public void execute(Runnable run) {
176213237Sgonzo            run.run();
177213237Sgonzo        }
178
179        public void run() {
180            MethodHandles.Lookup lookup = MethodHandles.lookup();
181            MethodHandle handle = null;
182            try {
183                handle = lookup.findVirtual(Handle.class, "execute",
184                        MethodType.methodType(void.class, Runnable.class));
185            } catch(NoSuchMethodException | IllegalAccessException x) {
186                throw new RuntimeException(x);
187            }
188            try {
189                handle.invoke(this, impl);
190            } catch(Error | RuntimeException x) {
191                throw x;
192            } catch(Throwable t) {
193                throw new RuntimeException(t);
194            }
195        }
196    }
197
198    static String prepare(String produced, boolean eraseSensitiveInfo) {
199        if (eraseSensitiveInfo) {
200            // Erase sensitive information before comparing:
201            // comparing line numbers is too fragile, so we just erase them
202            // out before comparing. We also erase the hash-like names of
203            // synthetic frames introduced by lambdas & method handles
204            return produced.replaceAll(":[1-9][0-9]*\\)", ":00)")
205                    .replaceAll("-internal/", "/").replaceAll("-ea/", "/")
206                    .replaceAll("java.base@[0-9]+/", "java.base/")
207                    .replaceAll("/[0-9]+\\.run", "/xxxxxxxx.run")
208                    .replaceAll("/[0-9]+\\.invoke", "/xxxxxxxx.invoke")
209                    .replaceAll("\\$[0-9]+", "\\$??");
210        } else {
211            return produced;
212        }
213    }
214
215
216    public static void main(String[] args) {
217        test(new TestCase1());
218        test(new TestCase2());
219        test(new TestCase3());
220        test(new TestCase4());
221    }
222
223    public static void invoke(Runnable run) {
224        run.run();
225    }
226
227    static final class Recorder {
228        boolean found; // stop recording after main
229        public void recordSTE(long counter, StringBuilder s, StackFrame f) {
230            if (found) return;
231            found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
232                    "main".equals(f.getMethodName());
233            String line = String.format("%d: %s", counter, f.toStackTraceElement());
234            s.append(line).append('\n');
235            System.out.println(line);
236        }
237    }
238
239
240    static void test(TestCase test) {
241        System.out.println("\nTesting: " + test.description());
242        final AtomicLong counter = new AtomicLong();
243        final StringBuilder builder = new StringBuilder();
244        final Recorder recorder = new Recorder();
245        final Runnable run = () -> test.walker().forEach(
246                f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
247        final Handle handle = new Handle(run);
248
249        // We're not using lambda on purpose here. We want the anonymous
250        // class on the stack.
251        PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
252            @Override
253            public Object run() {
254                try {
255                    return VerifyStackTrace.class
256                            .getMethod("invoke", Runnable.class)
257                            .invoke(null, handle);
258                } catch (NoSuchMethodException
259                        | IllegalAccessException
260                        | InvocationTargetException ex) {
261                    System.out.flush();
262                    throw new RuntimeException(ex);
263                }
264            }
265        };
266        AccessController.doPrivileged(pa);
267        System.out.println("Main found: " + recorder.found);
268        if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
269            System.out.flush();
270            try {
271                // sleep to make it less likely that System.out & System.err will
272                // interleave.
273                Thread.sleep(1000);
274            } catch (InterruptedException ex) {
275            }
276            System.err.println("\nUnexpected stack trace: "
277                    + "\n<!-- expected -->\n"
278                    + prepare(test.expected(), true)
279                    + "\n<--  actual -->\n"
280                    + prepare(builder.toString(), false));
281            throw new RuntimeException("Unexpected stack trace  for: " + test.description());
282        }
283    }
284
285
286}
287