1 /*
2  * Copyright (c) 2015, 2018, 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 import java.lang.reflect.InvocationTargetException;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.util.EnumSet;
28 import java.util.concurrent.atomic.AtomicLong;
29 import java.lang.StackWalker.StackFrame;
30 import java.lang.invoke.MethodHandle;
31 import java.lang.invoke.MethodHandles;
32 import java.lang.invoke.MethodType;
33 import java.util.Objects;
34 
35 import static java.lang.StackWalker.Option.*;
36 
37 /**
38  * @test
39  * @bug 8140450 8197901
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  */
47 public class VerifyStackTrace {
48 
49     static interface TestCase {
walker()50         StackWalker walker();
description()51         String description();
expected()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(AccessController.java:310)\n" +
75             "7: VerifyStackTrace.test(VerifyStackTrace.java:227)\n" +
76             "8: VerifyStackTrace.main(VerifyStackTrace.java:182)\n";
77 
walker()78         @Override public StackWalker walker() { return walker;}
description()79         @Override public String description() { return description;}
expected()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(AccessController.java:310)\n" +
109             "11: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" +
110             "12: VerifyStackTrace.main(VerifyStackTrace.java:185)\n";
111 
walker()112         @Override public StackWalker walker() { return walker;}
description()113         @Override public String description() { return description;}
expected()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/0x00000007c0089430.run(Unknown Source)\n" +
135             "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" +
136             "4: java.base/java.lang.invoke.LambdaForm$DMH/0x00000007c008a830.invokeVirtual_LL_V(LambdaForm$DMH)\n" +
137             "5: java.base/java.lang.invoke.LambdaForm$MH/0x00000007c008a830.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.executePrivileged(AccessController.java:759)\n" +
146             "14: java.base/java.security.AccessController.doPrivileged(AccessController.java:310)\n" +
147             "15: VerifyStackTrace.test(VerifyStackTrace.java:231)\n" +
148             "16: VerifyStackTrace.main(VerifyStackTrace.java:188)\n";
149 
walker()150         @Override public StackWalker walker() { return walker;}
description()151         @Override public String description() { return description;}
expected()152         @Override public String expected()    { return expected;}
153     }
154 
155     static final class TestCase4 extends TestCase3 {
156         private final StackWalker walker = StackWalker.getInstance(
157                 EnumSet.allOf(StackWalker.Option.class));
158 
159         private final String description = "StackWalker.getInstance(" +
160             "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
161             "StackWalker.Option.SHOW_HIDDEN_FRAMES, " +
162             "StackWalker.Option.SHOW_REFLECT_FRAMES)";
163 
walker()164         @Override public StackWalker walker() {return walker;}
description()165         @Override public String description() {return description;}
166     }
167 
168     public static class Handle implements Runnable {
169 
170         Runnable impl;
Handle(Runnable run)171         public Handle(Runnable run) {
172             this.impl = run;
173         }
174 
execute(Runnable run)175         public void execute(Runnable run) {
176             run.run();
177         }
178 
run()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 
prepare(String produced, boolean eraseSensitiveInfo)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("/0x[0-9a-f]+\\.run", "/xxxxxxxx.run")
206                     .replaceAll("/0x[0-9a-f]+\\.invoke", "/xxxxxxxx.invoke")
207                     // LFs may or may not be pre-generated, making frames differ
208                     .replaceAll("DirectMethodHandle\\$Holder", "LambdaForm\\$DMH")
209                     .replaceAll("Invokers\\$Holder", "LambdaForm\\$MH")
210                     .replaceAll("MH\\.invoke", "MH/xxxxxxxx.invoke")
211                     // invoke frames may or may not have basic method type
212                     // information encoded for diagnostic purposes
213                     .replaceAll("xx\\.invoke([A-Za-z]*)_[A-Z_]+", "xx.invoke$1")
214                     .replaceAll("\\$[0-9]+", "\\$??");
215         } else {
216             return produced;
217         }
218     }
219 
220 
main(String[] args)221     public static void main(String[] args) {
222         test(new TestCase1());
223         test(new TestCase2());
224         test(new TestCase3());
225         test(new TestCase4());
226     }
227 
invoke(Runnable run)228     public static void invoke(Runnable run) {
229         run.run();
230     }
231 
232     static final class Recorder {
233         boolean found; // stop recording after main
recordSTE(long counter, StringBuilder s, StackFrame f)234         public void recordSTE(long counter, StringBuilder s, StackFrame f) {
235             if (found) return;
236             found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
237                     "main".equals(f.getMethodName());
238             String line = String.format("%d: %s", counter, f.toStackTraceElement());
239             s.append(line).append('\n');
240             System.out.println(line);
241         }
242     }
243 
244 
test(TestCase test)245     static void test(TestCase test) {
246         System.out.println("\nTesting: " + test.description());
247         final AtomicLong counter = new AtomicLong();
248         final StringBuilder builder = new StringBuilder();
249         final Recorder recorder = new Recorder();
250         final Runnable run = () -> test.walker().forEach(
251                 f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
252         final Handle handle = new Handle(run);
253 
254         // We're not using lambda on purpose here. We want the anonymous
255         // class on the stack.
256         PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
257             @Override
258             public Object run() {
259                 try {
260                     return VerifyStackTrace.class
261                             .getMethod("invoke", Runnable.class)
262                             .invoke(null, handle);
263                 } catch (NoSuchMethodException
264                         | IllegalAccessException
265                         | InvocationTargetException ex) {
266                     System.out.flush();
267                     throw new RuntimeException(ex);
268                 }
269             }
270         };
271         AccessController.doPrivileged(pa);
272         System.out.println("Main found: " + recorder.found);
273         if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
274             System.out.flush();
275             try {
276                 // sleep to make it less likely that System.out & System.err will
277                 // interleave.
278                 Thread.sleep(1000);
279             } catch (InterruptedException ex) {
280             }
281             System.err.println("\nUnexpected stack trace: "
282                     + "\n<!-- expected -->\n"
283                     + prepare(test.expected(), true)
284                     + "\n<--  actual -->\n"
285                     + prepare(builder.toString(), false));
286             throw new RuntimeException("Unexpected stack trace  for: " + test.description());
287         }
288     }
289 
290 
291 }
292