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 static java.lang.StackWalker.Option.*;
  25 import java.lang.StackWalker.StackFrame;
  26 import java.util.Arrays;
  27 import java.util.EnumSet;
  28 import java.util.HashSet;
  29 import java.util.List;
  30 import java.util.Random;
  31 import java.util.Set;
  32 import java.util.TreeSet;
  33 
  34 import jdk.testlibrary.RandomFactory;
  35 
  36 /**
  37  * @test
  38  * @bug 8140450
  39  * @summary Stack Walk Test (use -Dseed=X to set PRNG seed)
  40  * @library /lib/testlibrary
  41  * @build jdk.testlibrary.*
  42  * @compile StackRecorderUtil.java
  43  * @run main/othervm StackWalkTest
  44  * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest
  45  * @run main/othervm StackWalkTest -random
  46  * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest -random
  47  * @author danielfuchs, bchristi
  48  * @key randomness
  49  */
  50 public class StackWalkTest {
  51     // TODO: test rest of StackWalker options (SHOW_*_FRAMES)
  52     // TODO: record unexpected classes instead of just dying
  53     // TODO: add -verbose flag
  54     // TODO: add real checking to Lambda inner-walk
  55 
  56     private final static boolean verbose = false;
  57     private final static int randomIters = 200;
  58     private final static int DEFAULT_MAX_STACK_FRAMES = 1024; //StackWalker.MAX_STACK_FRAMES
  59     private final static int MAX_RANDOM_DEPTH = 2000;
  60     private final static boolean CREATE_STACK_TRACE_ELEMENTS = true;
  61 
  62     private static boolean random = false;
  63 
  64     static final Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList(
  65             "sun.reflect.NativeMethodAccessorImpl",
  66             "sun.reflect.DelegatingMethodAccessorImpl",
  67             "java.lang.reflect.Method",
  68             "com.sun.javatest.regtest.MainWrapper$MainThread",
  69             "com.sun.javatest.regtest.agent.MainWrapper$MainThread",
  70             "java.lang.Thread"
  71     ));
  72     static final List<Class<?>> streamPipelines = Arrays.asList(
  73         classForName("java.util.stream.AbstractPipeline"),
  74         classForName("java.util.stream.TerminalOp")
  75     );
  76     static Class<?> classForName(String name) {
  77         try {
  78             return Class.forName(name);
  79         } catch (ClassNotFoundException e){
  80             throw new RuntimeException(e);
  81         }
  82     }
  83 
  84     private static boolean isStreamPipeline(Class<?> clazz) {
  85         for (Class<?> c : streamPipelines) {
  86             if (c.isAssignableFrom(clazz)) {
  87                 return true;
  88             }
  89         }
  90         return false;
  91     }
  92 
  93     StackRecorderUtil recorder;
  94     int count = 0;
  95     boolean didWalk = false;
  96 
  97     final int maxDepth;
  98     final Set<StackWalker.Option> swOptions;
  99 
 100     public StackWalkTest() {
 101         this(DEFAULT_MAX_STACK_FRAMES, null);
 102     }
 103 
 104     public StackWalkTest(Set<StackWalker.Option> swOptions) {
 105         this(DEFAULT_MAX_STACK_FRAMES, swOptions);
 106     }
 107 
 108     public StackWalkTest(int maxDepth) {
 109         this(maxDepth, null);
 110     }
 111 
 112     public StackWalkTest(int maxDepth, Set<StackWalker.Option> swOptions) {
 113         this.maxDepth = maxDepth;
 114         this.swOptions = swOptions != null
 115                             ? swOptions
 116                             : EnumSet.noneOf(StackWalker.Option.class);
 117     }
 118 
 119     private StackWalker createStackWalker() {
 120         // test the other ctors, if applicable
 121         if (this.maxDepth == DEFAULT_MAX_STACK_FRAMES) {
 122             if (swOptions.isEmpty()) {
 123                 return StackWalker.create();
 124             } else {
 125                 return StackWalker.create(swOptions);
 126             }
 127         }
 128         return new StackWalker(maxDepth, swOptions);
 129     }
 130     public void consume(StackFrame sf) {
 131         if (count == 0 && swOptions.contains(StackWalker.Option.RETAIN_CLASS_REFERENCE)
 132                 && isStreamPipeline(sf.getDeclaringClass())) {
 133             return;
 134         }
 135         if (verbose) {
 136             System.out.println("\t" + sf.getClassName() + "." + sf.getMethodName());
 137         }
 138         if (count >= recorder.frameCount()) {
 139             // We've gone past main()...
 140             if (infrastructureClasses.contains(sf.getClassName())) {
 141                 // safe to ignore
 142                 return;
 143             }
 144         }
 145         try {
 146             recorder.compareFrame(count, sf);
 147         } catch (IndexOutOfBoundsException e) {
 148             // Extra non-infra frame in stream
 149             throw new RuntimeException("extra non-infra stack frame at count "
 150                     + count + ": <" + sf + ">", e);
 151         }
 152         count++;
 153 
 154         // Maybe we should had some kind of checking inside that lambda
 155         // too. For the moment we should be satisfied if it doesn't throw
 156         // any exception and doesn't make the outer walk fail...
 157 
 158         // Use SW w/ limited stack depth, as this can take a long time for larger stack depths -
 159         // for each frame, it walks to whole stack again...
 160         StackWalker sw = StackWalker.create(swOptions);
 161         sw.walk(s -> { s.limit(128).forEach(f -> {
 162             // TODO need to check the correctness of StackFrame & StackTraceElement here
 163             f.getMethodName();
 164             f.getClassName();
 165             f.getFileName();
 166             f.getLineNumber();
 167             if (CREATE_STACK_TRACE_ELEMENTS) {
 168                 StackTraceElement st = f.toStackTraceElement();
 169                 st.getClassName();
 170                 st.getMethodName();
 171                 st.getFileName();
 172                 st.getLineNumber();
 173             }
 174         }); return null;});
 175     }
 176 
 177     public class Call {
 178         public void walk(int total, int markAt) {
 179             recorder.add(Call.class, "walk", "StackWalkTest.java");
 180             long swFrameCount = createStackWalker().walk(s -> s.count());
 181 
 182             if (verbose) {
 183                 System.out.println("Call.walk() total=" + total + ", markAt=" +
 184                         markAt + ", maxDepth="+maxDepth);
 185                 System.out.println("recorder frames:");
 186                 for (StackRecorderUtil.TestFrame f : recorder) {
 187                     System.out.println("\t" + f.declaringClass + "." + f.methodName);
 188                 }
 189                 System.out.println("\nStackWalker recorded " + swFrameCount + " frames");
 190                 System.out.flush();
 191             }
 192             if (swFrameCount > maxDepth) {
 193                 throw new RuntimeException("StackWalker maxDepth is " +maxDepth
 194                         + ", but stream has " + swFrameCount + " frames");
 195             }
 196             long recFrameCount = (long)recorder.frameCount();
 197             if (recFrameCount <= maxDepth && swFrameCount < recFrameCount) {
 198                 throw new RuntimeException("StackWalker recorded fewer frames ("
 199                         + (swFrameCount) + ") than expected ("
 200                         + recorder.frameCount() + "v)");
 201             }
 202             if (verbose) {
 203                 System.out.println("StackWalker frames:");
 204             }
 205             createStackWalker().forEach(StackWalkTest.this::consume);
 206             didWalk = true;
 207         }
 208         public void call(int total, int current, int markAt) {
 209             recorder.add(Call.class, "call", "StackWalkTest.java");
 210             if (current < total) {
 211                 testCall.call(total, current+1, markAt);
 212             } else {
 213                 walk(total, markAt);
 214             }
 215         }
 216     }
 217 
 218     public class Marker extends Call {
 219         @Override
 220         public void call(int total, int current, int markAt) {
 221             recorder.add(Marker.class, "call", "StackWalkTest.java");
 222             if (current < total) {
 223                 testCall.call(total, current+1, markAt);
 224             } else {
 225                 walk(total, markAt);
 226             }
 227         }
 228     }
 229     private Call markerCall = new Marker();
 230 
 231     public class Test extends Call {
 232         @Override
 233         public void call(int total, int current, int markAt) {
 234             recorder.add(Test.class, "call", "StackWalkTest.java");
 235             if (current < total) {
 236                 int nexti = current + 1;
 237                 if (nexti==markAt) {
 238                     markerCall.call(total, nexti, markAt);
 239                 } else {
 240                     testCall.call2(total, nexti, markAt);
 241                 }
 242             } else {
 243                 walk(total, markAt);
 244             }
 245         }
 246         public void call2(int total, int current, int markAt) {
 247             recorder.add(Test.class, "call2", "StackWalkTest.java");
 248             if (current < total) {
 249                 int nexti = current + 1;
 250                 if (nexti==markAt) {
 251                     markerCall.call(total, nexti, markAt);
 252                 } else {
 253                     test2Call.call(total, nexti, markAt);
 254                 }
 255             } else {
 256                 walk(total, markAt);
 257             }
 258         }
 259     }
 260     private Test testCall = new Test();
 261 
 262     /** Inherits call() from Call */
 263     public class Test2 extends Call {}
 264     private Test2 test2Call = new Test2();
 265 
 266     public void runTest(Class callerClass, String callerMethod, int stackDepth,
 267                         int markAt) {
 268         if (didWalk) {
 269             throw new IllegalStateException("StackWalkTest already used");
 270         }
 271         assert markAt <= stackDepth : "markAt(" + markAt + ") > stackDepth("
 272                 + stackDepth + ")";
 273         System.out.print("runTest(" + swOptions
 274                 + "), maxDepth=" + maxDepth);
 275 
 276         recorder = new StackRecorderUtil(swOptions);
 277         recorder.add(callerClass, callerMethod, "StackWalkTest.java");
 278         recorder.add(StackWalkTest.class, "runTest", "StackWalkTest.java");
 279 
 280         Test test1 = new Test();
 281         test1.call(stackDepth, 0, markAt);
 282 
 283         System.out.println(" finished");
 284         if (!didWalk) {
 285             throw new IllegalStateException("Test wasn't actually performed");
 286         }
 287     }
 288 
 289     public static void main(String[] args) {
 290         for(String arg : args) {
 291             if ("-random".equals(arg)) {
 292                 random = true;
 293             }
 294         }
 295         if (random) {
 296             Random rng = RandomFactory.getRandom();
 297             for (int iters = 0; iters < randomIters; iters++) {
 298                 Set<StackWalker.Option> opts = new HashSet<>();
 299                 if (rng.nextBoolean()) {
 300                     opts.add(RETAIN_CLASS_REFERENCE);
 301                 }
 302 
 303                 int depth = 1 + rng.nextInt(MAX_RANDOM_DEPTH);
 304 
 305                 StackWalkTest swt;
 306                 if (rng.nextBoolean() && depth > 1) {
 307                     swt = new StackWalkTest(1+rng.nextInt(depth-1), opts);
 308                 } else {
 309                     swt = new StackWalkTest(opts);
 310                 }
 311 
 312                 int markAt = rng.nextInt(depth+1);
 313                 System.out.print(depth + "@" + markAt + " ");
 314                 System.out.flush();
 315                 swt.runTest(StackWalkTest.class, "main", depth, markAt);
 316             }
 317         } else {
 318             // Long stack, default maxDepth
 319             StackWalkTest swt;
 320             swt = new StackWalkTest();
 321             swt.runTest(StackWalkTest.class, "main", 2000, 10);
 322 
 323             // Long stack, matching maxDepth
 324             swt = new StackWalkTest(2000);
 325             swt.runTest(StackWalkTest.class, "main", 2000, 10);
 326 
 327             // Long stack, maximum maxDepth
 328             swt = new StackWalkTest(Integer.MAX_VALUE);
 329             swt.runTest(StackWalkTest.class, "main", 2000, 10);
 330 
 331             //
 332             // Single batch
 333             //
 334             swt = new StackWalkTest(); // default maxDepth
 335             swt.runTest(StackWalkTest.class, "main", 6, 3);
 336 
 337             swt = new StackWalkTest(4); // maxDepth < stack
 338             swt.runTest(StackWalkTest.class, "main", 6, 3);
 339 
 340             swt = new StackWalkTest(2); // maxDepth < marker
 341             swt.runTest(StackWalkTest.class, "main", 6, 4);
 342 
 343             //
 344             // 2 batches
 345             //
 346             swt = new StackWalkTest(); // default maxDepth
 347             swt.runTest(StackWalkTest.class, "main", 24, 10);
 348             swt = new StackWalkTest(18); // maxDepth < stack
 349             swt.runTest(StackWalkTest.class, "main", 24, 10);
 350             swt = new StackWalkTest(8); // maxDepth < marker
 351             swt.runTest(StackWalkTest.class, "main", 24, 10);
 352 
 353             //
 354             // 3 batch
 355             //
 356             swt = new StackWalkTest(); // default maxDepth
 357             swt.runTest(StackWalkTest.class, "main", 60, 20);
 358             swt = new StackWalkTest(35); // maxDepth < stack
 359             swt.runTest(StackWalkTest.class, "main", 60, 20);
 360             swt = new StackWalkTest(8); // maxDepth < marker
 361             swt.runTest(StackWalkTest.class, "main", 60, 20);
 362 
 363             //
 364             // StackWalker.Options
 365             //
 366             swt = new StackWalkTest();
 367             swt.runTest(StackWalkTest.class, "main", 50, 10);
 368 
 369             swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE));
 370             swt.runTest(StackWalkTest.class, "main", 80, 40);
 371 
 372             swt = new StackWalkTest(Integer.MAX_VALUE, EnumSet.of(RETAIN_CLASS_REFERENCE));
 373             swt.runTest(StackWalkTest.class, "main", 2000, 1048);
 374         }
 375     }
 376 }