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