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