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.base/java.security.AccessController.doPrivileged(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: 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(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.base/java.lang.invoke.LambdaForm$DMH/2008017533.invokeVirtual_LL_V(LambdaForm$DMH)\n" +
 137             "5: java.base/java.lang.invoke.LambdaForm$MH/1395089624.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.doPrivileged(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("/[0-9]+\\.run", "/xxxxxxxx.run")
 205                     .replaceAll("/[0-9]+\\.invoke", "/xxxxxxxx.invoke")
 206                     // LFs may or may not be pre-generated, making frames differ
 207                     .replaceAll("DirectMethodHandle\\$Holder", "LambdaForm\\$DMH")
 208                     .replaceAll("Invokers\\$Holder", "LambdaForm\\$MH")
 209                     .replaceAll("MH\\.invoke", "MH/xxxxxxxx.invoke")
 210                     // invoke frames may or may not have basic method type
 211                     // information encoded for diagnostic purposes
 212                     .replaceAll("xx\\.invoke([A-Za-z]*)_[A-Z_]+", "xx.invoke$1")
 213                     .replaceAll("\\$[0-9]+", "\\$??");
 214         } else {
 215             return produced;
 216         }
 217     }
 218 
 219 
 220     public static void main(String[] args) {
 221         test(new TestCase1());
 222         test(new TestCase2());
 223         test(new TestCase3());
 224         test(new TestCase4());
 225     }
 226 
 227     public static void invoke(Runnable run) {
 228         run.run();
 229     }
 230 
 231     static final class Recorder {
 232         boolean found; // stop recording after main
 233         public void recordSTE(long counter, StringBuilder s, StackFrame f) {
 234             if (found) return;
 235             found = VerifyStackTrace.class.equals(f.getDeclaringClass()) &&
 236                     "main".equals(f.getMethodName());
 237             String line = String.format("%d: %s", counter, f.toStackTraceElement());
 238             s.append(line).append('\n');
 239             System.out.println(line);
 240         }
 241     }
 242 
 243 
 244     static void test(TestCase test) {
 245         System.out.println("\nTesting: " + test.description());
 246         final AtomicLong counter = new AtomicLong();
 247         final StringBuilder builder = new StringBuilder();
 248         final Recorder recorder = new Recorder();
 249         final Runnable run = () -> test.walker().forEach(
 250                 f -> recorder.recordSTE(counter.incrementAndGet(), builder, f));
 251         final Handle handle = new Handle(run);
 252 
 253         // We're not using lambda on purpose here. We want the anonymous
 254         // class on the stack.
 255         PrivilegedAction<Object> pa = new PrivilegedAction<Object>() {
 256             @Override
 257             public Object run() {
 258                 try {
 259                     return VerifyStackTrace.class
 260                             .getMethod("invoke", Runnable.class)
 261                             .invoke(null, handle);
 262                 } catch (NoSuchMethodException
 263                         | IllegalAccessException
 264                         | InvocationTargetException ex) {
 265                     System.out.flush();
 266                     throw new RuntimeException(ex);
 267                 }
 268             }
 269         };
 270         AccessController.doPrivileged(pa);
 271         System.out.println("Main found: " + recorder.found);
 272         if (!Objects.equals(prepare(test.expected(), true), prepare(builder.toString(), true))) {
 273             System.out.flush();
 274             try {
 275                 // sleep to make it less likely that System.out & System.err will
 276                 // interleave.
 277                 Thread.sleep(1000);
 278             } catch (InterruptedException ex) {
 279             }
 280             System.err.println("\nUnexpected stack trace: "
 281                     + "\n<!-- expected -->\n"
 282                     + prepare(test.expected(), true)
 283                     + "\n<--  actual -->\n"
 284                     + prepare(builder.toString(), false));
 285             throw new RuntimeException("Unexpected stack trace  for: " + test.description());
 286         }
 287     }
 288 
 289 
 290 }