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 {
  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.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 
  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: 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 
 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\\10.run(Unknown Source)\n" +
 135             "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" +
 136             "4: java.base/java.lang.invoke.LambdaForm$DMH\\11.invokeVirtual_LL_V(LambdaForm$DMH)\n" +
 137             "5: java.base/java.lang.invoke.LambdaForm$MH\\12.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 
 150         @Override public StackWalker walker() { return walker;}
 151         @Override public String description() { return description;}
 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 
 164         @Override public StackWalker walker() {return walker;}
 165         @Override public String description() {return description;}
 166     }
 167 
 168     public static class Handle implements Runnable {
 169 
 170         Runnable impl;
 171         public Handle(Runnable run) {
 172             this.impl = run;
 173         }
 174 
 175         public void execute(Runnable run) {
 176             run.run();
 177         }
 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("\\\\[0-9]+\\.run", "/xxxxxxxx.run")
 206                     .replaceAll("\\\\[0-9]+\\.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 
 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 
 228     public static void invoke(Runnable run) {
 229         run.run();
 230     }
 231 
 232     static final class Recorder {
 233         boolean found; // stop recording after main
 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 
 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 }