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 -XX:-MemberNameInStackFrame VerifyStackTrace
  44  * @run main/othervm -XX:+MemberNameInStackFrame VerifyStackTrace
  45  * @run main/othervm/java.security.policy=stackwalk.policy VerifyStackTrace
  46  * @author danielfuchs
  47  */
  48 public class VerifyStackTrace {
  49 
  50     static interface TestCase {
  51         StackWalker walker();
  52         String description();
  53         String expected();
  54     }
  55     static final class TestCase1 implements TestCase {
  56         private final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
  57 
  58         private final String description = "StackWalker.getInstance(" +
  59             "StackWalker.Option.RETAIN_CLASS_REFERENCE)";
  60 
  61         // Note: line numbers and lambda hashes will be erased when
  62         //       comparing stack traces. However, the stack may change
  63         //       if some methods are being renamed in the code base.
  64         // If the  JDKcode base changes and the test fails because of that,
  65         // then after validating that the actual stack trace obtained
  66         // is indeed correct (no frames are skipped that shouldn't)
  67         // then you can cut & paste the <-- actual --> stack printed in the
  68         // test output in here:
  69         private final String expected =
  70             "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:209)\n" +
  71             "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:145)\n" +
  72             "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:158)\n" +
  73             "4: VerifyStackTrace.invoke(VerifyStackTrace.java:188)\n" +
  74             "5: VerifyStackTrace$1.run(VerifyStackTrace.java:218)\n" +
  75             "6: java.security.AccessController.doPrivileged(Native Method)\n" +
  76             "7: VerifyStackTrace.test(VerifyStackTrace.java:227)\n" +
  77             "8: VerifyStackTrace.main(VerifyStackTrace.java:182)\n";
  78 
  79         @Override public StackWalker walker() { return walker;}
  80         @Override public String description() { return description;}
  81         @Override public String expected()    { return expected;}
  82     }
  83     static final class TestCase2 implements TestCase {
  84         private final StackWalker walker = StackWalker.getInstance(
  85                 EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES));
  86 
  87         private final String description = "nStackWalker.getInstance(" +
  88             "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
  89             "StackWalker.Option.SHOW_REFLECT_FRAMES)";
  90 
  91         // Note: line numbers and lambda hashes will be erased when
  92         //       comparing stack traces. However, the stack may change
  93         //       if some methods are being renamed in the code base.
  94         // If the JDK code base changes and the test fails because of that,
  95         // then after validating that the actual stack trace obtained
  96         // is indeed correct (no frames are skipped that shouldn't)
  97         // then you can cut & paste the <-- actual --> stack printed in the
  98         // test output in here (don't forget the final \n):
  99         private final String expected =
 100             "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:211)\n" +
 101             "2: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:147)\n" +
 102             "3: VerifyStackTrace$Handle.run(VerifyStackTrace.java:160)\n" +
 103             "4: VerifyStackTrace.invoke(VerifyStackTrace.java:190)\n" +
 104             "5: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
 105             "6: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" +
 106             "7: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" +
 107             "8: java.lang.reflect.Method.invoke(Method.java:520)\n" +
 108             "9: VerifyStackTrace$1.run(VerifyStackTrace.java:220)\n" +
 109             "10: java.security.AccessController.doPrivileged(Native Method)\n" +
 110             "11: VerifyStackTrace.test(VerifyStackTrace.java:229)\n" +
 111             "12: VerifyStackTrace.main(VerifyStackTrace.java:185)\n";
 112 
 113         @Override public StackWalker walker() { return walker;}
 114         @Override public String description() { return description;}
 115         @Override public String expected()    { return expected;}
 116     }
 117     static class TestCase3 implements TestCase {
 118         private final StackWalker walker = StackWalker.getInstance(
 119                 EnumSet.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES));
 120 
 121         private final String description = "StackWalker.getInstance(" +
 122             "StackWalker.Option.RETAIN_CLASS_REFERENCE, " +
 123             "StackWalker.Option.SHOW_HIDDEN_FRAMES)";
 124 
 125         // Note: line numbers and lambda hashes will be erased when
 126         //       comparing stack traces. However, the stack may change
 127         //       if some methods are being renamed in the code base.
 128         // If the JDK code base changes and the test fails because of that,
 129         // then after validating that the actual stack trace obtained
 130         // is indeed correct (no frames are skipped that shouldn't)
 131         // then you can cut & paste the <-- actual --> stack printed in the
 132         // test output in here (don't forget the final \n):
 133         private final String expected =
 134             "1: VerifyStackTrace.lambda$test$1(VerifyStackTrace.java:213)\n" +
 135             "2: VerifyStackTrace$$Lambda$1/662441761.run(Unknown Source)\n" +
 136             "3: VerifyStackTrace$Handle.execute(VerifyStackTrace.java:149)\n" +
 137             "4: java.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(LambdaForm$DMH)\n" +
 138             "5: java.lang.invoke.LambdaForm$MH/1395089624.invoke_MT(LambdaForm$MH)\n" +
 139             "6: VerifyStackTrace$Handle.run(VerifyStackTrace.java:162)\n" +
 140             "7: VerifyStackTrace.invoke(VerifyStackTrace.java:192)\n" +
 141             "8: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
 142             "9: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" +
 143             "10: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" +
 144             "11: java.lang.reflect.Method.invoke(Method.java:520)\n" +
 145             "12: VerifyStackTrace$1.run(VerifyStackTrace.java:222)\n" +
 146             "13: java.security.AccessController.doPrivileged(Native Method)\n" +
 147             "14: VerifyStackTrace.test(VerifyStackTrace.java:231)\n" +
 148             "15: 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                     .replaceAll("\\$[0-9]+", "\\$??");
 208         } else {
 209             return produced;
 210         }
 211     }
 212 
 213 
 214     public static void main(String[] args) {
 215         test(new TestCase1());
 216         test(new TestCase2());
 217         test(new TestCase3());
 218         test(new TestCase4());
 219     }
 220 
 221     public static void invoke(Runnable run) {
 222         run.run();
 223     }
 224 
 225     static final class Recorder {
 226         boolean found; // stop recording after main
 227         public void recordSTE(long counter, StringBuilder s, StackFrame f) {
 228             if (found) return;
 229             found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
 230                     "main".equals(f.getMethodName());
 231             String line = String.format("%d: %s", counter, f.toStackTraceElement());
 232             s.append(line).append('\n');
 233             System.out.println(line);
 234         }
 235     }
 236 
 237 
 238     static void test(TestCase test) {
 239         System.out.println("\nTesting: " + test.description());
 240         final AtomicLong counter = new AtomicLong();
 241         final StringBuilder builder = new StringBuilder();
 242         final Recorder recorder = new Recorder();
 243         final Runnable run = () -> test.walker().forEach(
 244                 f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
 245         final Handle handle = new Handle(run);
 246 
 247         // We're not using lambda on purpose here. We want the anonymous
 248         // class on the stack.
 249         PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
 250             @Override
 251             public Object run() {
 252                 try {
 253                     return VerifyStackTrace.class
 254                             .getMethod("invoke", Runnable.class)
 255                             .invoke(null, handle);
 256                 } catch (NoSuchMethodException
 257                         | IllegalAccessException
 258                         | InvocationTargetException ex) {
 259                     System.out.flush();
 260                     throw new RuntimeException(ex);
 261                 }
 262             }
 263         };
 264         AccessController.doPrivileged(pa);
 265         System.out.println("Main found: " + recorder.found);
 266         if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
 267             System.out.flush();
 268             try {
 269                 // sleep to make it less likely that System.out & System.err will
 270                 // interleave.
 271                 Thread.sleep(1000);
 272             } catch (InterruptedException ex) {
 273             }
 274             System.err.println("\nUnexpected stack trace: "
 275                     + "\n<!-- expected -->\n"
 276                     + prepare(test.expected(), true)
 277                     + "\n<--  actual -->\n"
 278                     + prepare(builder.toString(), false));
 279             throw new RuntimeException("Unexpected stack trace  for: " + test.description());
 280         }
 281     }
 282 
 283 
 284 }