| /* | |
| * Copyright (c) 2015, 2024, 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 8197901 | |
| * @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/othervm 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.getInstance(RETAIN_CLASS_REFERENCE); | |
| private final String description = "StackWalker.getInstance(" + | |
| "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) | |
| // then 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.base/java.security.AccessController.doPrivileged(AccessController.java:310)\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.getInstance( | |
| EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES)); | |
| private final String description = "nStackWalker.getInstance(" + | |
| "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) | |
| // then 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: java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:94)\n" + | |
| "6: java.base/java.lang.reflect.Method.invoke(Method.java:520)\n" + | |
| "7: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" + | |
| "8: java.base/java.security.AccessController.doPrivileged(AccessController.java:310)\n" + | |
| "9: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" + | |
| "10: 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.getInstance( | |
| EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES)); | |
| private final String description = "StackWalker.getInstance(" + | |
| "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) | |
| // then 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:280)\n" + | |
| "2: VerifyStackTrace$$Lambda/0x0000000801001848.run(Unknown Source)\n" + | |
| "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:206)\n" + | |
| "4: java.base/java.lang.invoke.DirectMethodHandle$Holder.invokeVirtual(DirectMethodHandle$Holder)\n" + | |
| "5: java.base/java.lang.invoke.LambdaForm$MH/0x0000000801004800.invoke_MT(LambdaForm$MH)\n" + | |
| "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:219)\n" + | |
| "7: VerifyStackTrace.invoke(VerifyStackTrace.java:259)\n" + | |
| "8: java.base/java.lang.invoke.LambdaForm$DMH/0x0000000801002000.invokeStatic(LambdaForm$DMH)\n" + | |
| "9: java.base/java.lang.invoke.LambdaForm$MH/0x0000000801003000.invoke(LambdaForm$MH)\n" + | |
| "10: java.base/java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder)\n" + | |
| "11: java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(DirectMethodHandleAccessor.java:211)\n" + | |
| "12: java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:122)\n" + | |
| "13: java.base/java.lang.reflect.Method.invoke(Method.java:573)\n" + | |
| "14: VerifyStackTrace$1.run(VerifyStackTrace.java:292)\n" + | |
| "15: java.base/java.security.AccessController.doPrivileged(AccessController.java:312)\n" + | |
| "16: VerifyStackTrace.test(VerifyStackTrace.java:301)\n" + | |
| "17: VerifyStackTrace.main(VerifyStackTrace.java:254)\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.getInstance( | |
| EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES)); | |
| private final String description = "StackWalker.getInstance(" + | |
| "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("/0x[0-9a-f]+\\.run", "/xxxxxxxx.run") | |
| .replaceAll("/0x[0-9a-f]+\\.invoke", "/xxxxxxxx.invoke") | |
| // LFs may or may not be pre-generated, making frames differ | |
| .replaceAll("DirectMethodHandle\\$Holder", "LambdaForm\\$DMH") | |
| .replaceAll("Invokers\\$Holder", "LambdaForm\\$MH") | |
| .replaceAll("MH\\.invoke", "MH/xxxxxxxx.invoke") | |
| // invoke frames may or may not have basic method type | |
| // information encoded for diagnostic purposes | |
| .replaceAll("xx\\.invoke([A-Za-z]*)_[A-Z_]+", "xx.invoke$1") | |
| .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<Object> pa = new PrivilegedAction<Object>() { | |
| @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<!-- expected -->\n" | |
| + prepare(test.expected(), true) | |
| + "\n<-- actual -->\n" | |
| + prepare(builder.toString(), false)); | |
| throw new RuntimeException("Unexpected stack trace for: " + test.description()); | |
| } | |
| } | |
| } |