From fcc0a083e4f47c8b3c1813525ff2a62fb32d909a Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 8 Feb 2017 16:33:52 +0000 Subject: [PATCH] 8173898: StackWalker.walk throws InternalError if called from a constructor invoked through reflection StackWalker is fixed to take into account constructor reflection frames. The specification of StackWalker.Option.SHOW_REFLECT_FRAMES is also clarified in this regard. Reviewed-by: bchristi, lancea, mchung, psandoz --- .../classes/java/lang/StackStreamFactory.java | 18 +- .../share/classes/java/lang/StackWalker.java | 26 +- jdk/test/java/lang/StackWalker/Basic.java | 62 +- .../lang/StackWalker/ReflectionFrames.java | 842 ++++++++++++++++++ 4 files changed, 930 insertions(+), 18 deletions(-) create mode 100644 jdk/test/java/lang/StackWalker/ReflectionFrames.java diff --git a/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java b/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java index 55976485d4f..7e64a6b543a 100644 --- a/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java +++ b/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java @@ -25,11 +25,13 @@ package java.lang; import jdk.internal.reflect.MethodAccessor; +import jdk.internal.reflect.ConstructorAccessor; import java.lang.StackWalker.Option; import java.lang.StackWalker.StackFrame; import java.lang.annotation.Native; import java.lang.reflect.Method; +import java.lang.reflect.Constructor; import java.util.HashSet; import java.util.NoSuchElementException; import java.util.Objects; @@ -922,7 +924,8 @@ final class StackStreamFactory { */ final void setBatch(int depth, int startIndex, int endIndex) { if (startIndex <= 0 || endIndex <= 0) - throw new IllegalArgumentException("startIndex=" + startIndex + " endIndex=" + endIndex); + throw new IllegalArgumentException("startIndex=" + startIndex + + " endIndex=" + endIndex); this.origin = startIndex; this.fence = endIndex; @@ -980,13 +983,18 @@ final class StackStreamFactory { private static boolean isReflectionFrame(Class c) { if (c.getName().startsWith("jdk.internal.reflect") && - !MethodAccessor.class.isAssignableFrom(c)) { - throw new InternalError("Not jdk.internal.reflect.MethodAccessor: " + c.toString()); + !MethodAccessor.class.isAssignableFrom(c) && + !ConstructorAccessor.class.isAssignableFrom(c)) { + throw new InternalError("Not jdk.internal.reflect.MethodAccessor" + + " or jdk.internal.reflect.ConstructorAccessor: " + + c.toString()); } // ## should filter all @Hidden frames? return c == Method.class || - MethodAccessor.class.isAssignableFrom(c) || - c.getName().startsWith("java.lang.invoke.LambdaForm"); + c == Constructor.class || + MethodAccessor.class.isAssignableFrom(c) || + ConstructorAccessor.class.isAssignableFrom(c) || + c.getName().startsWith("java.lang.invoke.LambdaForm"); } } diff --git a/jdk/src/java.base/share/classes/java/lang/StackWalker.java b/jdk/src/java.base/share/classes/java/lang/StackWalker.java index b5c43fd2d67..3f5ab6c39c1 100644 --- a/jdk/src/java.base/share/classes/java/lang/StackWalker.java +++ b/jdk/src/java.base/share/classes/java/lang/StackWalker.java @@ -29,6 +29,7 @@ import jdk.internal.reflect.CallerSensitive; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Stream; /** @@ -207,13 +208,23 @@ public final class StackWalker { /** * Shows all reflection frames. * - *

By default, reflection frames are hidden. This includes the - * {@link java.lang.reflect.Method#invoke} method - * and the reflection implementation classes. A {@code StackWalker} with - * this {@code SHOW_REFLECT_FRAMES} option will show all reflection frames. - * The {@link #SHOW_HIDDEN_FRAMES} option can also be used to show all + *

By default, reflection frames are hidden. A {@code StackWalker} + * configured with this {@code SHOW_REFLECT_FRAMES} option + * will show all reflection frames that + * include {@link java.lang.reflect.Method#invoke} and + * {@link java.lang.reflect.Constructor#newInstance(Object...)} + * and their reflection implementation classes. + * + *

The {@link #SHOW_HIDDEN_FRAMES} option can also be used to show all * reflection frames and it will also show other hidden frames that * are implementation-specific. + * + * @apiNote + * This option includes the stack frames representing the invocation of + * {@code Method} and {@code Constructor}. Any utility methods that + * are equivalent to calling {@code Method.invoke} or + * {@code Constructor.newInstance} such as {@code Class.newInstance} + * are not filtered or controlled by any stack walking option. */ SHOW_REFLECT_FRAMES, /** @@ -468,8 +479,9 @@ public final class StackWalker { * Gets the {@code Class} object of the caller invoking the method * that calls this {@code getCallerClass} method. * - *

Reflection frames, {@link java.lang.invoke.MethodHandle}, and - * hidden frames are filtered regardless of the + *

This method filters {@linkplain Option#SHOW_REFLECT_FRAMES reflection + * frames}, {@link java.lang.invoke.MethodHandle}, and + * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} regardless of the * {@link Option#SHOW_REFLECT_FRAMES SHOW_REFLECT_FRAMES} * and {@link Option#SHOW_HIDDEN_FRAMES SHOW_HIDDEN_FRAMES} options * this {@code StackWalker} has been configured with. diff --git a/jdk/test/java/lang/StackWalker/Basic.java b/jdk/test/java/lang/StackWalker/Basic.java index 37e51f08097..8dc7627a90d 100644 --- a/jdk/test/java/lang/StackWalker/Basic.java +++ b/jdk/test/java/lang/StackWalker/Basic.java @@ -23,18 +23,21 @@ /* * @test - * @bug 8140450 + * @bug 8140450 8173898 * @summary Basic test for the StackWalker::walk method * @run testng Basic */ import java.lang.StackWalker.StackFrame; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.lang.StackWalker.Option.*; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import static org.testng.Assert.*; public class Basic { private static boolean verbose = false; @@ -60,6 +63,17 @@ public class Basic { } } + @Test + public static void testWalkFromConstructor() throws Exception { + System.out.println("testWalkFromConstructor:"); + List found = ((ConstructorNewInstance)ConstructorNewInstance.class.getMethod("create") + .invoke(null)).collectedFrames(); + assertEquals(List.of(ConstructorNewInstance.class.getName()+"::", + ConstructorNewInstance.class.getName()+"::create", + Basic.class.getName()+"::testWalkFromConstructor"), + found); + } + private final int depth; Basic(int depth) { this.depth = depth; @@ -77,6 +91,47 @@ public class Basic { assertEquals(limit, frames.size()); } + static class ConstructorNewInstance { + static final StackWalker walker = + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + List testFramesOrReflectionFrames; + public ConstructorNewInstance() { + testFramesOrReflectionFrames = walker.walk(this::parse); + } + public List collectedFrames() { + return testFramesOrReflectionFrames; + } + public boolean accept(StackFrame f) { + // Frames whose class names don't contain "." + // are our own test frames. These are the ones + // we expect. + // Frames whose class names contain ".reflect." + // are reflection frames. None should be present, + // since they are supposed to be filtered by + // by StackWalker. If we find any, we want to fail. + if (!f.getClassName().contains(".") + || f.getClassName().contains(".reflect.")) { + System.out.println(" " + f); + return true; + } + // Filter out all other frames (in particular + // those from the test framework) in order to + // have predictable results. + return false; + } + public String frame(StackFrame f) { + return f.getClassName() + "::" + f.getMethodName(); + } + List parse(Stream s) { + return s.filter(this::accept) + .map(this::frame) + .collect(Collectors.toList()); + } + public static ConstructorNewInstance create() throws Exception { + return ConstructorNewInstance.class.getConstructor().newInstance(); + } + } + class StackBuilder { private final int stackDepth; private final int limit; @@ -131,9 +186,4 @@ public class Basic { } } - static void assertEquals(int x, int y) { - if (x != y) { - throw new RuntimeException(x + " != " + y); - } - } } diff --git a/jdk/test/java/lang/StackWalker/ReflectionFrames.java b/jdk/test/java/lang/StackWalker/ReflectionFrames.java new file mode 100644 index 00000000000..eacc55fe92b --- /dev/null +++ b/jdk/test/java/lang/StackWalker/ReflectionFrames.java @@ -0,0 +1,842 @@ +/* + * Copyright (c) 2017, 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. + */ + +/* + * @test + * @bug 8173898 + * @summary Basic test for checking filtering of reflection frames + * @run testng ReflectionFrames + */ + +import java.lang.StackWalker.StackFrame; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.util.EnumSet; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import static java.lang.StackWalker.Option.*; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class ReflectionFrames { + final static boolean verbose = false; + + /** + * This test invokes new StackInspector() directly from + * the caller StackInspector.Caller.create method. + * It checks that the caller is StackInspector.Caller. + * It also checks the expected frames collected + * by walking the stack from the default StackInspector() + * constructor. + * This is done twice, once using a default StackWalker + * that hides reflection frames, once using a StackWalker + * configured to show reflection frames. + */ + @Test + public static void testNewStackInspector() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes new StackInspector() directly. + // No reflection frame should appear. + System.out.println("testNewStackInspector: create"); + + StackInspector obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes new StackInspector() directly. + // No reflection frame should appear. + System.out.println("testNewStackInspector: reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::reflect", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // a MethodHandle. + // The create method invokes new StackInspector() directly. + // No reflection frame should appear. + System.out.println("testNewStackInspector: handle"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::handle", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes new StackInspector() directly. + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewStackInspector: create: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes new StackInspector() directly. + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewStackInspector: reflect: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + StackInspector.Caller.class.getName() + +"::reflect", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // MethodHandle. + // The create method invokes new StackInspector() directly. + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewStackInspector: handle: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.NEW)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + // MethodHandle::invoke remains hidden + StackInspector.Caller.class.getName() + +"::handle", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewStackInspector")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + } + + /** + * This test invokes Constructor.newInstance() from + * the caller StackInspector.Caller.create method. + * It checks that the caller is StackInspector.Caller. + * It also checks the expected frames collected + * by walking the stack from the default StackInspector() + * constructor. + * This is done twice, once using a default StackWalker + * that hides reflection frames, once using a StackWalker + * configured to show reflection frames. + */ + @Test + public static void testConstructor() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes Constructor.newInstance(). + // No reflection frame should appear. + System.out.println("testConstructor: create"); + + StackInspector obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes Constructor.newInstance(). + // No reflection frame should appear. + System.out.println("testConstructor: reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::reflect", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // MethodHandle. + // The create method invokes Constructor.newInstance(). + // No reflection frame should appear. + System.out.println("testConstructor: handle"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::handle", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertEquals(obj.filtered, 0); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes Constructor.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testConstructor: create: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes Constructor.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testConstructor: reflect: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + StackInspector.Caller.class.getName() + +"::reflect", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // MethodHandle. + // The create method invokes Constructor.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testConstructor: handle: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.CONSTRUCTOR)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + // MethodHandle::invoke remains hidden + StackInspector.Caller.class.getName() + +"::handle", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testConstructor")); + assertEquals(obj.cls, StackInspector.Caller.class); + assertNotEquals(obj.filtered, 0); + } + + /** + * This test invokes StackInspector.class.newInstance() from + * the caller StackInspector.Caller.create method. Because + * Class.newInstance() is not considered as a + * reflection frame, the the caller returned by + * getCallerClass() should appear to be java.lang.Class + * and not StackInspector.Caller. + * It also checks the expected frames collected + * by walking the stack from the default StackInspector() + * constructor. + * This is done twice, once using a default StackWalker + * that hides reflection frames, once using a StackWalker + * configured to show reflection frames. + */ + @Test + public static void testNewInstance() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes StackInspector.class.newInstance(). + // No reflection frame should appear, except + // Class::newInstance which is not considered as + // a reflection frame. + System.out.println("testNewInstance: create"); + + StackInspector obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + ReflectionFrames.class.getName() + +"::testNewInstance")); + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes StackInspector.class.newInstance(). + // No reflection frame should appear, except + // Class::newInstance which is not considered as + // a reflection frame. + System.out.println("testNewInstance: reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::reflect", + ReflectionFrames.class.getName() + +"::testNewInstance")); + + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // reflection. + // The create method invokes StackInspector.class.newInstance(). + // No reflection frame should appear, except + // Class::newInstance which is not considered as + // a reflection frame. + System.out.println("testNewInstance: handle"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + StackInspector.Caller.class.getName() + +"::handle", + ReflectionFrames.class.getName() + +"::testNewInstance")); + + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertEquals(obj.filtered, 0); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + // Calls the StackInspector.create method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The create method invokes StackInspector.class.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewInstance: create: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("create", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewInstance")); + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.reflect method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The reflect method invokes the create method through + // reflection. + // The create method invokes StackInspector.class.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewInstance: reflect: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("reflect", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + Method.class.getName() + +"::invoke", + StackInspector.Caller.class.getName() + +"::reflect", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewInstance")); + + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertNotEquals(obj.filtered, 0); + + // Calls the StackInspector.handle method through reflection + // and check the frames collected in the StackInspector + // default constructor. + // The handle method invokes the create method using + // MethodHandle. + // The create method invokes StackInspector.class.newInstance(). + // We should see all reflection frames, except the + // jdk.internal.reflect frames which we are filtering + // out in StackInspector::filter. + System.out.println("testNewInstance: handle: show reflect"); + + obj = ((StackInspector)StackInspector.Caller.class + .getMethod("handle", How.class) + .invoke(null, How.CLASS)); + assertEquals(obj.collectedFrames, + List.of(StackInspector.class.getName() + +"::", + Constructor.class.getName() + +"::newInstance", + Class.class.getName() + +"::newInstance", + StackInspector.Caller.class.getName() + +"::create", + // MethodHandle::invoke remains hidden + StackInspector.Caller.class.getName() + +"::handle", + Method.class.getName() + +"::invoke", + ReflectionFrames.class.getName() + +"::testNewInstance")); + + // Because Class.newInstance is not filtered, then the + // caller is Class.class + assertEquals(obj.cls, Class.class); + assertNotEquals(obj.filtered, 0); + } + + @Test + public static void testGetCaller() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + assertEquals(StackInspector.getCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("getCaller").invoke(null), + ReflectionFrames.class); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + assertEquals(StackInspector.getCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("getCaller").invoke(null), + ReflectionFrames.class); + } + + @Test + public static void testReflectCaller() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + assertEquals(StackInspector.reflectCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("reflectCaller").invoke(null), + ReflectionFrames.class); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + assertEquals(StackInspector.reflectCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("reflectCaller").invoke(null), + ReflectionFrames.class); + } + + @Test + public static void testSupplyCaller() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + assertEquals(StackInspector.supplyCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("supplyCaller").invoke(null), + ReflectionFrames.class); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + assertEquals(StackInspector.supplyCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("supplyCaller").invoke(null), + ReflectionFrames.class); + } + + @Test + public static void testHandleCaller() throws Exception { + // Sets the default walker which hides reflection + // frames. + StackInspector.walker.set(StackInspector.walkerHide); + + assertEquals(StackInspector.handleCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("handleCaller").invoke(null), + ReflectionFrames.class); + + // Sets a non-default walker configured to show + // reflection frames + StackInspector.walker.set(StackInspector.walkerShow); + + assertEquals(StackInspector.handleCaller(), ReflectionFrames.class); + assertEquals(StackInspector.class.getMethod("handleCaller").invoke(null), + ReflectionFrames.class); + } + + static enum How { NEW, CONSTRUCTOR, CLASS}; + + /** + * An object that collect stack frames by walking the stack + * (and calling getCallerClass()) from within its constructor. + * For the purpose of this test, StackInspector objects are + * always created from the nested StackInspector.Caller class, + * which should therefore appear as the caller of the + * StackInspector constructor. + */ + static class StackInspector { + static final StackWalker walkerHide = + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + static final StackWalker walkerShow = + StackWalker.getInstance(EnumSet.of( + StackWalker.Option.RETAIN_CLASS_REFERENCE, + StackWalker.Option.SHOW_REFLECT_FRAMES)); + final static ThreadLocal walker = new ThreadLocal<>() { + protected StackWalker initialValue() { + return walkerHide; + } + }; + + List collectedFrames; + Class cls = null; + boolean stop; + int filtered; + final boolean filterImplFrames; + + public StackInspector() { + stop = false; + // if reflection frames are not hidden, we want to + // filter implementation frames before collecting + // to avoid depending on internal details. + filterImplFrames = walker.get() == walkerShow; + collectedFrames = walker.get().walk(this::parse); + cls = walker.get().getCallerClass(); + } + + public List collectedFrames() { + return collectedFrames; + } + + // The takeWhile method arrange for stopping frame collection + // as soon as a frame from ReflectionFrames.class is reached. + // The first such frame encountered is still included in the + // collected frames, but collection stops right after. + // This makes it possible to filter out anything above the + // the test method frame, such as frames from the test + // framework. + public boolean takeWhile(StackFrame f) { + if (stop) return false; + if (verbose) System.out.println(" " + f); + stop = stop || f.getDeclaringClass() == ReflectionFrames.class; + return true; + } + + // filter out implementation frames to avoid depending + // on implementation details. If present, Class::newInstance, + // Method::invoke and Constructor::newInstance will + // still appear in the collected frames, which is + // sufficient for the purpose of the test. + // In the case where the StackWalker itself is supposed to + // filter the reflection frames, then this filter will always + // return true. This way, if such a reflection frame appears when + // it sjould have been filtered by StackWalker, it will make the + // test fail. + public boolean filter(StackFrame f) { + if (filterImplFrames && + f.getClassName().startsWith("jdk.internal.reflect.")) { + filtered++; + return false; + } + if (!verbose) System.out.println(" " + f); + return true; + } + + public String frame(StackFrame f) { + return f.getClassName() + "::" + f.getMethodName(); + } + + List parse(Stream s) { + return s.takeWhile(this::takeWhile) + .filter(this::filter) + .map(this::frame) + .collect(Collectors.toList()); + } + + /** + * The Caller class is used to create instances of + * StackInspector, either direcltly, or throug reflection. + */ + public static class Caller { + public static StackInspector create(How how) throws Exception { + switch(how) { + case NEW: return new StackInspector(); + case CONSTRUCTOR: return StackInspector.class + .getConstructor().newInstance(); + case CLASS: return StackInspector.class.newInstance(); + default: throw new AssertionError(String.valueOf(how)); + } + } + public static StackInspector reflect(How how) throws Exception { + return (StackInspector) Caller.class.getMethod("create", How.class) + .invoke(null, how); + } + public static StackInspector handle(How how) throws Exception { + Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = lookup.findStatic(Caller.class, "create", + MethodType.methodType(StackInspector.class, How.class)); + try { + return (StackInspector) mh.invoke(how); + } catch (Error | Exception x) { + throw x; + } catch(Throwable t) { + throw new AssertionError(t); + } + } + } + + public static Class getCaller() throws Exception { + return walker.get().getCallerClass(); + } + + public static Class reflectCaller() throws Exception { + return (Class)StackWalker.class.getMethod("getCallerClass") + .invoke(walker.get()); + } + + public static Class supplyCaller() throws Exception { + return ((Supplier>)StackInspector.walker.get()::getCallerClass).get(); + } + + public static Class handleCaller() throws Exception { + Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = lookup.findVirtual(StackWalker.class, "getCallerClass", + MethodType.methodType(Class.class)); + try { + return (Class) mh.invoke(walker.get()); + } catch (Error | Exception x) { + throw x; + } catch(Throwable t) { + throw new AssertionError(t); + } + } + } +}