/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.EnumSet; import java.util.concurrent.atomic.AtomicLong; import java.lang.StackWalker.StackFrame; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Objects; import static java.lang.StackWalker.Option.*; /** * @test * @bug 8140450 * @summary Verify stack trace information obtained with respect to StackWalker * options, when the stack contains lambdas, method handle invoke * virtual calls, and reflection. * @run main VerifyStackTrace * @run main/othervm/java.security.policy=stackwalk.policy VerifyStackTrace * @author danielfuchs */ public class VerifyStackTrace { static interface TestCase { StackWalker walker(); String description(); String expected(); } static final class TestCase1 implements TestCase { private final StackWalker walker = StackWalker.create(RETAIN_CLASS_REFERENCE); private final String description = "StackWalker.create(" + "StackWalker.Option.RETAIN_CLASS_REFERENCE)"; // Note: line numbers and lambda hashes will be erased when // comparing stack traces. However, the stack may change // if some methods are being renamed in the code base. // If the JDKcode base changes and the test fails because of that, // then after validating that the actual stack trace obtained // is indeed correct (no frames are skipped that shouldn't) // the you can cut & paste the <-- actual --> stack printed in the // test output in here: private final String expected = "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:209)\n" + "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:145)\n" + "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:158)\n" + "4: VerifyStackTrace.invoke(VerifyStackTrace.java:188)\n" + "5: VerifyStackTrace$1.run(VerifyStackTrace.java:218)\n" + "6: java.security.AccessController.doPrivileged(Native Method)\n" + "7: VerifyStackTrace.test(VerifyStackTrace.java:227)\n" + "8: VerifyStackTrace.main(VerifyStackTrace.java:182)\n"; @Override public StackWalker walker() { return walker;} @Override public String description() { return description;} @Override public String expected() { return expected;} } static final class TestCase2 implements TestCase { private final StackWalker walker = StackWalker.create( EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES)); private final String description = "nStackWalker.create(" + "StackWalker.Option.RETAIN_CLASS_REFERENCE, " + "StackWalker.Option.SHOW_REFLECT_FRAMES)"; // Note: line numbers and lambda hashes will be erased when // comparing stack traces. However, the stack may change // if some methods are being renamed in the code base. // If the JDK code base changes and the test fails because of that, // then after validating that the actual stack trace obtained // is indeed correct (no frames are skipped that shouldn't) // the you can cut & paste the <-- actual --> stack printed in the // test output in here (don't forget the final \n): private final String expected = "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:211)\n" + "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:147)\n" + "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:160)\n" + "4: VerifyStackTrace.invoke(VerifyStackTrace.java:190)\n" + "5: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + "6: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + "7: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + "8: java.lang.reflect.Method.invoke(Method.java:520)\n" + "9: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" + "10: java.security.AccessController.doPrivileged(Native Method)\n" + "11: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" + "12: VerifyStackTrace.main(VerifyStackTrace.java:185)\n"; @Override public StackWalker walker() { return walker;} @Override public String description() { return description;} @Override public String expected() { return expected;} } static class TestCase3 implements TestCase { private final StackWalker walker = StackWalker.create( EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES)); private final String description = "StackWalker.create(" + "StackWalker.Option.RETAIN_CLASS_REFERENCE, " + "StackWalker.Option.SHOW_HIDDEN_FRAMES)"; // Note: line numbers and lambda hashes will be erased when // comparing stack traces. However, the stack may change // if some methods are being renamed in the code base. // If the JDK code base changes and the test fails because of that, // then after validating that the actual stack trace obtained // is indeed correct (no frames are skipped that shouldn't) // the you can cut & paste the <-- actual --> stack printed in the // test output in here (don't forget the final \n): private final String expected = "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:213)\n" + "2: VerifyStackTrace$$Lambda$1/662441761.run()\n" + "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" + "4: java.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(LambdaForm$DMH)\n" + "5: java.lang.invoke.LambdaForm$MH/1395089624.invoke_MT(LambdaForm$MH)\n" + "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:162)\n" + "7: VerifyStackTrace.invoke(VerifyStackTrace.java:192)\n" + "8: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + "9: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + "10: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + "11: java.lang.reflect.Method.invoke(Method.java:520)\n" + "12: VerifyStackTrace$1.run(VerifyStackTrace.java:222)\n" + "13: java.security.AccessController.doPrivileged(Native Method)\n" + "14: VerifyStackTrace.test(VerifyStackTrace.java:231)\n" + "15: VerifyStackTrace.main(VerifyStackTrace.java:188)\n"; @Override public StackWalker walker() { return walker;} @Override public String description() { return description;} @Override public String expected() { return expected;} } static final class TestCase4 extends TestCase3 { private final StackWalker walker = StackWalker.create( EnumSet.allOf(StackWalker.Option.class)); private final String description = "StackWalker.create(" + "StackWalker.Option.RETAIN_CLASS_REFERENCE, " + "StackWalker.Option.SHOW_HIDDEN_FRAMES, " + "StackWalker.Option.SHOW_REFLECT_FRAMES)"; @Override public StackWalker walker() {return walker;} @Override public String description() {return description;} } public static class Handle implements Runnable { Runnable impl; public Handle(Runnable run) { this.impl = run; } public void execute(Runnable run) { run.run(); } public void run() { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle handle = null; try { handle = lookup.findVirtual(Handle.class, "execute", MethodType.methodType(void.class, Runnable.class)); } catch(NoSuchMethodException | IllegalAccessException x) { throw new RuntimeException(x); } try { handle.invoke(this, impl); } catch(Error | RuntimeException x) { throw x; } catch(Throwable t) { throw new RuntimeException(t); } } } static String prepare(String produced, boolean eraseSensitiveInfo) { if (eraseSensitiveInfo) { // Erase sensitive information before comparing: // comparing line numbers is too fragile, so we just erase them // out before comparing. We also erase the hash-like names of // synthetic frames introduced by lambdas & method handles return produced.replaceAll(":[1-9][0-9]*\\)", ":00)") .replaceAll("/[0-9]+\\.run", "/xxxxxxxx.run") .replaceAll("/[0-9]+\\.invoke", "/xxxxxxxx.invoke") .replaceAll("\\$[0-9]+", "\\$??"); } else { return produced; } } public static void main(String[] args) { test(new TestCase1()); test(new TestCase2()); test(new TestCase3()); test(new TestCase4()); } public static void invoke(Runnable run) { run.run(); } static final class Recorder { boolean found; // stop recording after main public void recordSTE(long counter, StringBuilder s, StackFrame f) { if (found) return; found = VerifyStackTrace.class.equals(f.getDeclaringClass()) && "main".equals(f.getMethodName()); String line = String.format("%d: %s", counter, f.toStackTraceElement()); s.append(line).append('\n'); System.out.println(line); } } static void test(TestCase test) { System.out.println("\nTesting: " + test.description()); final AtomicLong counter = new AtomicLong(); final StringBuilder builder = new StringBuilder(); final Recorder recorder = new Recorder(); final Runnable run = () -> test.walker().forEach( f -> recorder.recordSTE(counter.incrementAndGet(), builder, f)); final Handle handle = new Handle(run); // We're not using lambda on purpose here. We want the anonymous // class on the stack. PrivilegedAction pa = new PrivilegedAction() { @Override public Object run() { try { return VerifyStackTrace.class .getMethod("invoke", Runnable.class) .invoke(null, handle); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { System.out.flush(); throw new RuntimeException(ex); } } }; AccessController.doPrivileged(pa); System.out.println("Main found: " + recorder.found); if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) { System.out.flush(); try { // sleep to make it less likely that System.out & System.err will // interleave. Thread.sleep(1000); } catch (InterruptedException ex) { } System.err.println("\nUnexpected stack trace: " + "\n\n" + prepare(test.expected(), true) + "\n<-- actual -->\n" + prepare(builder.toString(), false)); throw new RuntimeException("Unexpected stack trace for: " + test.description()); } } }