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);
+ }
+ }
+ }
+}