1 /*
   2  * Copyright (c) 2015, 2017, 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.test.lib.RandomFactory;
  35 
  36 /**
  37  * @test
  38  * @bug 8140450
  39  * @summary Stack Walk Test (use -Dseed=X to set PRNG seed)
  40  * @library /test/lib
  41  * @build jdk.test.lib.RandomFactory
  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:50
  46  * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest -random:50
  47  * @author danielfuchs, bchristi
  48  * @key randomness
  49  */
  50 public class StackWalkTest {
  51     private static boolean random = false;
  52     private static boolean verbose = false;
  53     private static int randomRuns = 50;
  54 
  55     private static final int MAX_RANDOM_DEPTH = 1000;
  56 
  57     static final Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList(
  58             "jdk.internal.reflect.NativeMethodAccessorImpl",
  59             "jdk.internal.reflect.DelegatingMethodAccessorImpl",
  60             "java.lang.reflect.Method",
  61             "com.sun.javatest.regtest.MainWrapper$MainThread",
  62             "com.sun.javatest.regtest.agent.MainWrapper$MainThread",
  63             "java.lang.Thread"
  64     ));
  65     static final List<Class<?>> streamPipelines = Arrays.asList(
  66         classForName("java.util.stream.AbstractPipeline"),
  67         classForName("java.util.stream.TerminalOp")
  68     );
  69     static Class<?> classForName(String name) {
  70         try {
  71             return Class.forName(name);
  72         } catch (ClassNotFoundException e){
  73             throw new RuntimeException(e);
  74         }
  75     }
  76 
  77     private static boolean isStreamPipeline(Class<?> clazz) {
  78         for (Class<?> c : streamPipelines) {
  79             if (c.isAssignableFrom(clazz)) {
  80                 return true;
  81             }
  82         }
  83         return false;
  84     }
  85 
  86     StackRecorderUtil recorder;
  87     int count = 0;
  88     boolean didWalk = false;
  89 
  90     final int estDepth;
  91     final Set<StackWalker.Option> swOptions;
  92 
  93     public StackWalkTest() {
  94         this(EnumSet.noneOf(StackWalker.Option.class), -1);
  95     }
  96 
  97     public StackWalkTest(Set<StackWalker.Option> swOptions) {
  98         this(swOptions, -1);
  99     }
 100 
 101     public StackWalkTest(int estimatedDepth) {
 102         this(EnumSet.noneOf(StackWalker.Option.class), -1);
 103     }
 104 
 105     public StackWalkTest(Set<StackWalker.Option> swOptions, int estimatedDepth) {
 106         this.swOptions = swOptions;
 107         this.estDepth = estimatedDepth;
 108     }
 109 
 110     private StackWalker createStackWalker() {
 111         // test all StackWalker factory methods
 112         if (this.estDepth < 0) {
 113             if (swOptions.isEmpty()) {
 114                 return StackWalker.getInstance();
 115             } else {
 116                 return StackWalker.getInstance(swOptions);
 117             }
 118         }
 119         return StackWalker.getInstance(swOptions, estDepth);
 120     }
 121     public void consume(StackFrame sf) {
 122         if (count == 0 && swOptions.contains(StackWalker.Option.RETAIN_CLASS_REFERENCE)
 123                 && isStreamPipeline(sf.getDeclaringClass())) {
 124             return;
 125         }
 126         if (verbose) {
 127             System.out.println("\t" + sf.getClassName() + "." + sf.getMethodName());
 128         }
 129         if (count >= recorder.frameCount()) {
 130             // We've gone past main()...
 131             if (infrastructureClasses.contains(sf.getClassName())) {
 132                 // safe to ignore
 133                 return;
 134             }
 135         }
 136         try {
 137             recorder.compareFrame(count, sf);
 138         } catch (IndexOutOfBoundsException e) {
 139             // Extra non-infra frame in stream
 140             throw new RuntimeException("extra non-infra stack frame at count "
 141                     + count + ": <" + sf + ">", e);
 142         }
 143         count++;
 144     }
 145 
 146     public class Call {
 147         public void walk(int total, int markAt) {
 148             recorder.add(Call.class, "walk", "StackWalkTest.java");
 149             long swFrameCount = createStackWalker().walk(s -> s.count());
 150 
 151             if (verbose) {
 152                 System.out.println("Call.walk() total=" + total + ", markAt=" + markAt);
 153                 System.out.println("recorder frames:");
 154                 for (StackRecorderUtil.TestFrame f : recorder) {
 155                     System.out.println("\t" + f.declaringClass + "." + f.methodName);
 156                 }
 157                 System.out.println("\nStackWalker recorded " + swFrameCount + " frames");
 158                 System.out.flush();
 159             }
 160             long recFrameCount = (long)recorder.frameCount();
 161             if (swFrameCount < recFrameCount) {
 162                 throw new RuntimeException("StackWalker recorded fewer frames ("+
 163                         swFrameCount + ") than recorded ("+ recorder.frameCount() +
 164                         ") - " + "estimatedDepth set to " + estDepth);
 165             }
 166             if (verbose) {
 167                 System.out.println("StackWalker frames:");
 168             }
 169             createStackWalker().forEach(StackWalkTest.this::consume);
 170             didWalk = true;
 171         }
 172         public void call(int total, int current, int markAt) {
 173             recorder.add(Call.class, "call", "StackWalkTest.java");
 174             if (current < total) {
 175                 testCall.call(total, current+1, markAt);
 176             } else {
 177                 walk(total, markAt);
 178             }
 179         }
 180     }
 181 
 182     public class Marker extends Call {
 183         @Override
 184         public void call(int total, int current, int markAt) {
 185             recorder.add(Marker.class, "call", "StackWalkTest.java");
 186             if (current < total) {
 187                 testCall.call(total, current+1, markAt);
 188             } else {
 189                 walk(total, markAt);
 190             }
 191         }
 192     }
 193     private Call markerCall = new Marker();
 194 
 195     public class Test extends Call {
 196         @Override
 197         public void call(int total, int current, int markAt) {
 198             recorder.add(Test.class, "call", "StackWalkTest.java");
 199             if (current < total) {
 200                 int nexti = current + 1;
 201                 if (nexti==markAt) {
 202                     markerCall.call(total, nexti, markAt);
 203                 } else {
 204                     testCall.call2(total, nexti, markAt);
 205                 }
 206             } else {
 207                 walk(total, markAt);
 208             }
 209         }
 210         public void call2(int total, int current, int markAt) {
 211             recorder.add(Test.class, "call2", "StackWalkTest.java");
 212             if (current < total) {
 213                 int nexti = current + 1;
 214                 if (nexti==markAt) {
 215                     markerCall.call(total, nexti, markAt);
 216                 } else {
 217                     test2Call.call(total, nexti, markAt);
 218                 }
 219             } else {
 220                 walk(total, markAt);
 221             }
 222         }
 223     }
 224     private Test testCall = new Test();
 225 
 226     /** Inherits call() from Call */
 227     public class Test2 extends Call {}
 228     private Test2 test2Call = new Test2();
 229 
 230     public void runTest(Class callerClass, String callerMethod, int stackDepth,
 231                         int markAt) {
 232         if (didWalk) {
 233             throw new IllegalStateException("StackWalkTest already used");
 234         }
 235         // Test may run into StackOverflow when running in -Xcomp mode on deep stack
 236         assert stackDepth <= 1000;
 237         assert markAt <= stackDepth : "markAt(" + markAt + ") > stackDepth("
 238                 + stackDepth + ")";
 239         System.out.print("runTest(" + swOptions
 240                 + "), estimatedDepth=" + estDepth);
 241 
 242         recorder = new StackRecorderUtil(swOptions);
 243         recorder.add(callerClass, callerMethod, "StackWalkTest.java");
 244         recorder.add(StackWalkTest.class, "runTest", "StackWalkTest.java");
 245 
 246         Test test1 = new Test();
 247         test1.call(stackDepth, 0, markAt);
 248 
 249         System.out.println(" finished");
 250         if (!didWalk) {
 251             throw new IllegalStateException("Test wasn't actually performed");
 252         }
 253     }
 254 
 255     public static void main(String[] args) {
 256         String rand = "-random";
 257         String randItems = "-random:";
 258         for(String arg : args) {
 259             if (arg.startsWith(rand)) {
 260                 random = true;
 261                 try {
 262                     if(arg.startsWith(randItems)) {
 263                         randomRuns = Integer.valueOf(arg.substring(randItems.length()));
 264                     }
 265                 } catch(NumberFormatException e) {}
 266             } else if("-verbose".equals(arg)) {
 267                 verbose = true;
 268             }
 269         }
 270         if (random) {
 271             Random rng = RandomFactory.getRandom();
 272             for (int iters = 0; iters < randomRuns; iters++) {
 273                 Set<StackWalker.Option> opts = new HashSet<>();
 274                 if (rng.nextBoolean()) {
 275                     opts.add(RETAIN_CLASS_REFERENCE);
 276                 }
 277 
 278                 int depth = 1 + rng.nextInt(MAX_RANDOM_DEPTH);
 279 
 280                 StackWalkTest swt;
 281                 if (rng.nextBoolean() && depth > 1) {
 282                     // Test that specifying an estimatedDepth doesn't prevent
 283                     // full stack traversal
 284                     swt = new StackWalkTest(opts, 1+rng.nextInt(depth-1));
 285                 } else {
 286                     swt = new StackWalkTest(opts);
 287                 }
 288 
 289                 int markAt = rng.nextInt(depth+1);
 290                 System.out.print(depth + "@" + markAt + " ");
 291                 System.out.flush();
 292                 swt.runTest(StackWalkTest.class, "main", depth, markAt);
 293             }
 294         } else {
 295             // Long stack, default maxDepth
 296             StackWalkTest swt;
 297             swt = new StackWalkTest();
 298             swt.runTest(StackWalkTest.class, "main", 1000, 10);
 299 
 300             // Long stack, matching maxDepth
 301             swt = new StackWalkTest(2000);
 302             swt.runTest(StackWalkTest.class, "main", 1000, 10);
 303 
 304             // Long stack, maximum maxDepth
 305             swt = new StackWalkTest(Integer.MAX_VALUE);
 306             swt.runTest(StackWalkTest.class, "main", 1000, 10);
 307 
 308             //
 309             // Single batch
 310             //
 311             swt = new StackWalkTest(); // default maxDepth
 312             swt.runTest(StackWalkTest.class, "main", 6, 3);
 313 
 314             swt = new StackWalkTest(4); // maxDepth < stack
 315             swt.runTest(StackWalkTest.class, "main", 6, 3);
 316 
 317             swt = new StackWalkTest(2); // maxDepth < marker
 318             swt.runTest(StackWalkTest.class, "main", 6, 4);
 319 
 320             //
 321             // 2 batches
 322             //
 323             swt = new StackWalkTest(); // default maxDepth
 324             swt.runTest(StackWalkTest.class, "main", 24, 10);
 325             swt = new StackWalkTest(18); // maxDepth < stack
 326             swt.runTest(StackWalkTest.class, "main", 24, 10);
 327             swt = new StackWalkTest(8); // maxDepth < marker
 328             swt.runTest(StackWalkTest.class, "main", 24, 10);
 329 
 330             //
 331             // 3 batch
 332             //
 333             swt = new StackWalkTest(); // default maxDepth
 334             swt.runTest(StackWalkTest.class, "main", 60, 20);
 335             swt = new StackWalkTest(35); // maxDepth < stack
 336             swt.runTest(StackWalkTest.class, "main", 60, 20);
 337             swt = new StackWalkTest(8); // maxDepth < marker
 338             swt.runTest(StackWalkTest.class, "main", 60, 20);
 339 
 340             //
 341             // StackWalker.Options
 342             //
 343             swt = new StackWalkTest();
 344             swt.runTest(StackWalkTest.class, "main", 50, 10);
 345 
 346             swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE));
 347             swt.runTest(StackWalkTest.class, "main", 80, 40);
 348 
 349             swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE), 50);
 350             swt.runTest(StackWalkTest.class, "main", 1000, 524);
 351         }
 352     }
 353 }