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