--- /dev/null 2015-11-09 17:15:06.000000000 -0800 +++ new/jdk/test/java/lang/StackWalker/StackWalkTest.java 2015-11-09 17:15:06.000000000 -0800 @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import static java.lang.StackWalker.Option.*; +import java.lang.StackWalker.StackFrame; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; + +import jdk.testlibrary.RandomFactory; + +/** + * @test + * @bug 8140450 + * @summary Stack Walk Test (use -Dseed=X to set PRNG seed) + * @library /lib/testlibrary + * @build jdk.testlibrary.* + * @compile StackRecorderUtil.java + * @run main/othervm StackWalkTest + * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest + * @run main/othervm StackWalkTest -random + * @run main/othervm/java.security.policy=stackwalktest.policy StackWalkTest -random + * @author danielfuchs, bchristi + * @key randomness + */ +public class StackWalkTest { + // TODO: test rest of StackWalker options (SHOW_*_FRAMES) + // TODO: record unexpected classes instead of just dying + // TODO: add -verbose flag + // TODO: add real checking to Lambda inner-walk + + private final static boolean verbose = false; + private final static int randomIters = 200; + private final static int DEFAULT_MAX_STACK_FRAMES = 1024; //StackWalker.MAX_STACK_FRAMES + private final static int MAX_RANDOM_DEPTH = 2000; + private final static boolean CREATE_STACK_TRACE_ELEMENTS = true; + + private static boolean random = false; + + static final Set infrastructureClasses = new TreeSet<>(Arrays.asList( + "sun.reflect.NativeMethodAccessorImpl", + "sun.reflect.DelegatingMethodAccessorImpl", + "java.lang.reflect.Method", + "com.sun.javatest.regtest.MainWrapper$MainThread", + "com.sun.javatest.regtest.agent.MainWrapper$MainThread", + "java.lang.Thread" + )); + static final List> streamPipelines = Arrays.asList( + classForName("java.util.stream.AbstractPipeline"), + classForName("java.util.stream.TerminalOp") + ); + static Class classForName(String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e){ + throw new RuntimeException(e); + } + } + + private static boolean isStreamPipeline(Class clazz) { + for (Class c : streamPipelines) { + if (c.isAssignableFrom(clazz)) { + return true; + } + } + return false; + } + + StackRecorderUtil recorder; + int count = 0; + boolean didWalk = false; + + final int maxDepth; + final Set swOptions; + + public StackWalkTest() { + this(DEFAULT_MAX_STACK_FRAMES, null); + } + + public StackWalkTest(Set swOptions) { + this(DEFAULT_MAX_STACK_FRAMES, swOptions); + } + + public StackWalkTest(int maxDepth) { + this(maxDepth, null); + } + + public StackWalkTest(int maxDepth, Set swOptions) { + this.maxDepth = maxDepth; + this.swOptions = swOptions != null + ? swOptions + : EnumSet.noneOf(StackWalker.Option.class); + } + + private StackWalker createStackWalker() { + // test the other ctors, if applicable + if (this.maxDepth == DEFAULT_MAX_STACK_FRAMES) { + if (swOptions.isEmpty()) { + return StackWalker.create(); + } else { + return StackWalker.create(swOptions); + } + } + return new StackWalker(maxDepth, swOptions); + } + public void consume(StackFrame sf) { + if (count == 0 && swOptions.contains(StackWalker.Option.RETAIN_CLASS_REFERENCE) + && isStreamPipeline(sf.getDeclaringClass())) { + return; + } + if (verbose) { + System.out.println("\t" + sf.getClassName() + "." + sf.getMethodName()); + } + if (count >= recorder.frameCount()) { + // We've gone past main()... + if (infrastructureClasses.contains(sf.getClassName())) { + // safe to ignore + return; + } + } + try { + recorder.compareFrame(count, sf); + } catch (IndexOutOfBoundsException e) { + // Extra non-infra frame in stream + throw new RuntimeException("extra non-infra stack frame at count " + + count + ": <" + sf + ">", e); + } + count++; + + // Maybe we should had some kind of checking inside that lambda + // too. For the moment we should be satisfied if it doesn't throw + // any exception and doesn't make the outer walk fail... + + // Use SW w/ limited stack depth, as this can take a long time for larger stack depths - + // for each frame, it walks to whole stack again... + StackWalker sw = StackWalker.create(swOptions); + sw.walk(s -> { s.limit(128).forEach(f -> { + // TODO need to check the correctness of StackFrame & StackTraceElement here + f.getMethodName(); + f.getClassName(); + f.getFileName(); + f.getLineNumber(); + if (CREATE_STACK_TRACE_ELEMENTS) { + StackTraceElement st = f.toStackTraceElement(); + st.getClassName(); + st.getMethodName(); + st.getFileName(); + st.getLineNumber(); + } + }); return null;}); + } + + public class Call { + public void walk(int total, int markAt) { + recorder.add(Call.class, "walk", "StackWalkTest.java"); + long swFrameCount = createStackWalker().walk(s -> s.count()); + + if (verbose) { + System.out.println("Call.walk() total=" + total + ", markAt=" + + markAt + ", maxDepth="+maxDepth); + System.out.println("recorder frames:"); + for (StackRecorderUtil.TestFrame f : recorder) { + System.out.println("\t" + f.declaringClass + "." + f.methodName); + } + System.out.println("\nStackWalker recorded " + swFrameCount + " frames"); + System.out.flush(); + } + if (swFrameCount > maxDepth) { + throw new RuntimeException("StackWalker maxDepth is " +maxDepth + + ", but stream has " + swFrameCount + " frames"); + } + long recFrameCount = (long)recorder.frameCount(); + if (recFrameCount <= maxDepth && swFrameCount < recFrameCount) { + throw new RuntimeException("StackWalker recorded fewer frames (" + + (swFrameCount) + ") than expected (" + + recorder.frameCount() + "v)"); + } + if (verbose) { + System.out.println("StackWalker frames:"); + } + createStackWalker().forEach(StackWalkTest.this::consume); + didWalk = true; + } + public void call(int total, int current, int markAt) { + recorder.add(Call.class, "call", "StackWalkTest.java"); + if (current < total) { + testCall.call(total, current+1, markAt); + } else { + walk(total, markAt); + } + } + } + + public class Marker extends Call { + @Override + public void call(int total, int current, int markAt) { + recorder.add(Marker.class, "call", "StackWalkTest.java"); + if (current < total) { + testCall.call(total, current+1, markAt); + } else { + walk(total, markAt); + } + } + } + private Call markerCall = new Marker(); + + public class Test extends Call { + @Override + public void call(int total, int current, int markAt) { + recorder.add(Test.class, "call", "StackWalkTest.java"); + if (current < total) { + int nexti = current + 1; + if (nexti==markAt) { + markerCall.call(total, nexti, markAt); + } else { + testCall.call2(total, nexti, markAt); + } + } else { + walk(total, markAt); + } + } + public void call2(int total, int current, int markAt) { + recorder.add(Test.class, "call2", "StackWalkTest.java"); + if (current < total) { + int nexti = current + 1; + if (nexti==markAt) { + markerCall.call(total, nexti, markAt); + } else { + test2Call.call(total, nexti, markAt); + } + } else { + walk(total, markAt); + } + } + } + private Test testCall = new Test(); + + /** Inherits call() from Call */ + public class Test2 extends Call {} + private Test2 test2Call = new Test2(); + + public void runTest(Class callerClass, String callerMethod, int stackDepth, + int markAt) { + if (didWalk) { + throw new IllegalStateException("StackWalkTest already used"); + } + assert markAt <= stackDepth : "markAt(" + markAt + ") > stackDepth(" + + stackDepth + ")"; + System.out.print("runTest(" + swOptions + + "), maxDepth=" + maxDepth); + + recorder = new StackRecorderUtil(swOptions); + recorder.add(callerClass, callerMethod, "StackWalkTest.java"); + recorder.add(StackWalkTest.class, "runTest", "StackWalkTest.java"); + + Test test1 = new Test(); + test1.call(stackDepth, 0, markAt); + + System.out.println(" finished"); + if (!didWalk) { + throw new IllegalStateException("Test wasn't actually performed"); + } + } + + public static void main(String[] args) { + for(String arg : args) { + if ("-random".equals(arg)) { + random = true; + } + } + if (random) { + Random rng = RandomFactory.getRandom(); + for (int iters = 0; iters < randomIters; iters++) { + Set opts = new HashSet<>(); + if (rng.nextBoolean()) { + opts.add(RETAIN_CLASS_REFERENCE); + } + + int depth = 1 + rng.nextInt(MAX_RANDOM_DEPTH); + + StackWalkTest swt; + if (rng.nextBoolean() && depth > 1) { + swt = new StackWalkTest(1+rng.nextInt(depth-1), opts); + } else { + swt = new StackWalkTest(opts); + } + + int markAt = rng.nextInt(depth+1); + System.out.print(depth + "@" + markAt + " "); + System.out.flush(); + swt.runTest(StackWalkTest.class, "main", depth, markAt); + } + } else { + // Long stack, default maxDepth + StackWalkTest swt; + swt = new StackWalkTest(); + swt.runTest(StackWalkTest.class, "main", 2000, 10); + + // Long stack, matching maxDepth + swt = new StackWalkTest(2000); + swt.runTest(StackWalkTest.class, "main", 2000, 10); + + // Long stack, maximum maxDepth + swt = new StackWalkTest(Integer.MAX_VALUE); + swt.runTest(StackWalkTest.class, "main", 2000, 10); + + // + // Single batch + // + swt = new StackWalkTest(); // default maxDepth + swt.runTest(StackWalkTest.class, "main", 6, 3); + + swt = new StackWalkTest(4); // maxDepth < stack + swt.runTest(StackWalkTest.class, "main", 6, 3); + + swt = new StackWalkTest(2); // maxDepth < marker + swt.runTest(StackWalkTest.class, "main", 6, 4); + + // + // 2 batches + // + swt = new StackWalkTest(); // default maxDepth + swt.runTest(StackWalkTest.class, "main", 24, 10); + swt = new StackWalkTest(18); // maxDepth < stack + swt.runTest(StackWalkTest.class, "main", 24, 10); + swt = new StackWalkTest(8); // maxDepth < marker + swt.runTest(StackWalkTest.class, "main", 24, 10); + + // + // 3 batch + // + swt = new StackWalkTest(); // default maxDepth + swt.runTest(StackWalkTest.class, "main", 60, 20); + swt = new StackWalkTest(35); // maxDepth < stack + swt.runTest(StackWalkTest.class, "main", 60, 20); + swt = new StackWalkTest(8); // maxDepth < marker + swt.runTest(StackWalkTest.class, "main", 60, 20); + + // + // StackWalker.Options + // + swt = new StackWalkTest(); + swt.runTest(StackWalkTest.class, "main", 50, 10); + + swt = new StackWalkTest(EnumSet.of(RETAIN_CLASS_REFERENCE)); + swt.runTest(StackWalkTest.class, "main", 80, 40); + + swt = new StackWalkTest(Integer.MAX_VALUE, EnumSet.of(RETAIN_CLASS_REFERENCE)); + swt.runTest(StackWalkTest.class, "main", 2000, 1048); + } + } +}