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 
  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
  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 {
  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.security.AccessController.doPrivileged(java.base/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: jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base/Native Method)\n" +
 104             "6: jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base/NativeMethodAccessorImpl.java:62)\n" +
 105             "7: jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base/DelegatingMethodAccessorImpl.java:43)\n" +
 106             "8: java.lang.reflect.Method.invoke(java.base/Method.java:520)\n" +
 107             "9: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" +
 108             "10: java.security.AccessController.doPrivileged(java.base/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.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(java.base/LambdaForm$DMH)\n" +
 137             "5: java.lang.invoke.LambdaForm$MH/1395089624.invoke_MT(java.base/LambdaForm$MH)\n" +
 138             "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:162)\n" +
 139             "7: VerifyStackTrace.invoke(VerifyStackTrace.java:192)\n" +
 140             "8: jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base/Native Method)\n" +
 141             "9: jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base/NativeMethodAccessorImpl.java:62)\n" +
 142             "10: jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base/DelegatingMethodAccessorImpl.java:43)\n" +
 143             "11: java.lang.reflect.Method.invoke(java.base/Method.java:520)\n" +
 144             "12: VerifyStackTrace$1.run(VerifyStackTrace.java:222)\n" +
 145             "13: java.security.AccessController.doPrivileged(java.base/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("-internal/", "/").replaceAll("-ea/", "/")
 205                     .replaceAll("java.base@(\\d+\\.){0,3}(\\d+)/", "java.base/")
 206                     .replaceAll("/[0-9]+\\.run", "/xxxxxxxx.run")
 207                     .replaceAll("/[0-9]+\\.invoke", "/xxxxxxxx.invoke")
 208                     .replaceAll("DirectMethodHandle\\$Holder", "LambdaForm\\$DMH")
 209                     .replaceAll("DMH\\.invoke", "DMH/xxxxxxxx.invoke")
 210                     .replaceAll("\\$[0-9]+", "\\$??");
 211         } else {
 212             return produced;
 213         }
 214     }
 215 
 216 
 217     public static void main(String[] args) {
 218         test(new TestCase1());
 219         test(new TestCase2());
 220         test(new TestCase3());
 221         test(new TestCase4());
 222     }
 223 
 224     public static void invoke(Runnable run) {
 225         run.run();
 226     }
 227 
 228     static final class Recorder {
 229         boolean found; // stop recording after main
 230         public void recordSTE(long counter, StringBuilder s, StackFrame f) {
 231             if (found) return;
 232             found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
 233                     "main".equals(f.getMethodName());
 234             String line = String.format("%d: %s", counter, f.toStackTraceElement());
 235             s.append(line).append('\n');
 236             System.out.println(line);
 237         }
 238     }
 239 
 240 
 241     static void test(TestCase test) {
 242         System.out.println("\nTesting: " + test.description());
 243         final AtomicLong counter = new AtomicLong();
 244         final StringBuilder builder = new StringBuilder();
 245         final Recorder recorder = new Recorder();
 246         final Runnable run = () -> test.walker().forEach(
 247                 f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
 248         final Handle handle = new Handle(run);
 249 
 250         // We're not using lambda on purpose here. We want the anonymous
 251         // class on the stack.
 252         PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
 253             @Override
 254             public Object run() {
 255                 try {
 256                     return VerifyStackTrace.class
 257                             .getMethod("invoke", Runnable.class)
 258                             .invoke(null, handle);
 259                 } catch (NoSuchMethodException
 260                         | IllegalAccessException
 261                         | InvocationTargetException ex) {
 262                     System.out.flush();
 263                     throw new RuntimeException(ex);
 264                 }
 265             }
 266         };
 267         AccessController.doPrivileged(pa);
 268         System.out.println("Main found: " + recorder.found);
 269         if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
 270             System.out.flush();
 271             try {
 272                 // sleep to make it less likely that System.out & System.err will
 273                 // interleave.
 274                 Thread.sleep(1000);
 275             } catch (InterruptedException ex) {
 276             }
 277             System.err.println("\nUnexpected stack trace: "
 278                     + "\n<!-- expected -->\n"
 279                     + prepare(test.expected(), true)
 280                     + "\n<--  actual -->\n"
 281                     + prepare(builder.toString(), false));
 282             throw new RuntimeException("Unexpected stack trace  for: " + test.description());
 283         }
 284     }
 285 
 286 
 287 }