From 111ecdbaf58e5c0b3a64e0eca8a291df295e71b0 Mon Sep 17 00:00:00 2001 From: Mandy Chung Date: Thu, 7 Sep 2023 21:37:40 +0000 Subject: [PATCH] 8268829: Provide an optimized way to walk the stack with Class object only 8210375: StackWalker::getCallerClass throws UnsupportedOperationException Reviewed-by: coleenp, dfuchs, bchristi --- make/data/hotspot-symbols/symbols-unix | 1 + src/hotspot/share/classfile/javaClasses.cpp | 140 +++++-- src/hotspot/share/classfile/javaClasses.hpp | 34 +- src/hotspot/share/classfile/vmClassMacros.hpp | 1 + src/hotspot/share/classfile/vmSymbols.hpp | 1 + src/hotspot/share/include/jvm.h | 10 +- src/hotspot/share/prims/jvm.cpp | 24 +- src/hotspot/share/prims/methodHandles.cpp | 2 + src/hotspot/share/prims/stackwalk.cpp | 42 +- src/hotspot/share/prims/stackwalk.hpp | 25 +- .../classes/java/lang/ClassFrameInfo.java | 135 +++++++ .../classes/java/lang/StackFrameInfo.java | 70 ++-- .../classes/java/lang/StackStreamFactory.java | 364 ++++++++---------- .../share/classes/java/lang/StackWalker.java | 261 ++++++------- .../classes/java/lang/invoke/MemberName.java | 5 - .../java/lang/invoke/MethodHandleImpl.java | 35 +- .../java/lang/invoke/MethodHandleNatives.java | 1 + .../java/lang/invoke/ResolvedMethodName.java | 35 ++ .../javax/crypto/JceSecurityManager.java | 4 +- .../internal/access/JavaLangInvokeAccess.java | 29 +- .../share/native/libjava/StackFrameInfo.c | 37 ++ .../share/native/libjava/StackStreamFactory.c | 12 +- .../runtime/LocalLong/LocalLongHelper.java | 13 +- .../CallerSensitiveMethod/Main.java | 5 +- .../csm/jdk/test/CallerSensitiveTest.java | 29 +- .../src/java.base/java/util/CSM.java | 29 +- .../lang/StackWalker/GetCallerClassTest.java | 9 +- .../jdk/java/lang/StackWalker/SanityTest.java | 115 +++--- .../lang/StackWalker/StackRecorderUtil.java | 8 +- .../lang/StackWalker/StackStreamTest.java | 21 +- .../java/lang/StackWalker/StackWalkTest.java | 1 + .../lang/StackWalker/VerifyStackTrace.java | 2 +- .../java/lang/StackWalkerHelper.java | 4 +- .../bench/java/lang/StackWalkBench.java | 100 ++++- 34 files changed, 982 insertions(+), 622 deletions(-) create mode 100644 src/java.base/share/classes/java/lang/ClassFrameInfo.java create mode 100644 src/java.base/share/classes/java/lang/invoke/ResolvedMethodName.java create mode 100644 src/java.base/share/native/libjava/StackFrameInfo.c diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index e6abc6c8d2c..d2be2cca7e8 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -57,6 +57,7 @@ JVM_DumpAllStacks JVM_DumpClassListToFile JVM_DumpDynamicArchive JVM_DumpThreads +JVM_ExpandStackFrameInfo JVM_FillInStackTrace JVM_FindClassFromCaller JVM_FindClassFromClass diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 95210a57b4d..0c016202b8e 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -2956,15 +2956,78 @@ void java_lang_StackTraceElement::decode(const methodHandle& method, int bci, } #endif // INCLUDE_JVMCI +// java_lang_ClassFrameInfo + +int java_lang_ClassFrameInfo::_classOrMemberName_offset; +int java_lang_ClassFrameInfo::_flags_offset; + +#define CLASSFRAMEINFO_FIELDS_DO(macro) \ + macro(_classOrMemberName_offset, k, "classOrMemberName", object_signature, false); \ + macro(_flags_offset, k, vmSymbols::flags_name(), int_signature, false) + +void java_lang_ClassFrameInfo::compute_offsets() { + InstanceKlass* k = vmClasses::ClassFrameInfo_klass(); + CLASSFRAMEINFO_FIELDS_DO(FIELD_COMPUTE_OFFSET); +} + +#if INCLUDE_CDS +void java_lang_ClassFrameInfo::serialize_offsets(SerializeClosure* f) { + CLASSFRAMEINFO_FIELDS_DO(FIELD_SERIALIZE_OFFSET); +} +#endif + +static int get_flags(const methodHandle& m) { + int flags = (jushort)( m->access_flags().as_short() & JVM_RECOGNIZED_METHOD_MODIFIERS ); + if (m->is_initializer()) { + flags |= java_lang_invoke_MemberName::MN_IS_CONSTRUCTOR; + } else { + flags |= java_lang_invoke_MemberName::MN_IS_METHOD; + } + if (m->caller_sensitive()) { + flags |= java_lang_invoke_MemberName::MN_CALLER_SENSITIVE; + } + if (m->is_hidden()) { + flags |= java_lang_invoke_MemberName::MN_HIDDEN_MEMBER; + } + assert((flags & 0xFF000000) == 0, "unexpected flags"); + return flags; +} + +oop java_lang_ClassFrameInfo::classOrMemberName(oop obj) { + return obj->obj_field(_classOrMemberName_offset); +} + +int java_lang_ClassFrameInfo::flags(oop obj) { + return obj->int_field(_flags_offset); +} + +void java_lang_ClassFrameInfo::init_class(Handle stackFrame, const methodHandle& m) { + stackFrame->obj_field_put(_classOrMemberName_offset, m->method_holder()->java_mirror()); + // flags is initialized when ClassFrameInfo object is constructed and retain the value + int flags = java_lang_ClassFrameInfo::flags(stackFrame()) | get_flags(m); + stackFrame->int_field_put(_flags_offset, flags); +} + +void java_lang_ClassFrameInfo::init_method(Handle stackFrame, const methodHandle& m, TRAPS) { + oop rmethod_name = java_lang_invoke_ResolvedMethodName::find_resolved_method(m, CHECK); + stackFrame->obj_field_put(_classOrMemberName_offset, rmethod_name); + // flags is initialized when ClassFrameInfo object is constructed and retain the value + int flags = java_lang_ClassFrameInfo::flags(stackFrame()) | get_flags(m); + stackFrame->int_field_put(_flags_offset, flags); +} + + // java_lang_StackFrameInfo -int java_lang_StackFrameInfo::_memberName_offset; +int java_lang_StackFrameInfo::_type_offset; +int java_lang_StackFrameInfo::_name_offset; int java_lang_StackFrameInfo::_bci_offset; int java_lang_StackFrameInfo::_version_offset; int java_lang_StackFrameInfo::_contScope_offset; #define STACKFRAMEINFO_FIELDS_DO(macro) \ - macro(_memberName_offset, k, "memberName", object_signature, false); \ + macro(_type_offset, k, "type", object_signature, false); \ + macro(_name_offset, k, "name", string_signature, false); \ macro(_bci_offset, k, "bci", int_signature, false); \ macro(_contScope_offset, k, "contScope", continuationscope_signature, false) @@ -2981,23 +3044,18 @@ void java_lang_StackFrameInfo::serialize_offsets(SerializeClosure* f) { } #endif -Method* java_lang_StackFrameInfo::get_method(Handle stackFrame, InstanceKlass* holder, TRAPS) { - HandleMark hm(THREAD); - Handle mname(THREAD, stackFrame->obj_field(_memberName_offset)); - Method* method = (Method*)java_lang_invoke_MemberName::vmtarget(mname()); - // we should expand MemberName::name when Throwable uses StackTrace - // MethodHandles::expand_MemberName(mname, MethodHandles::_suppress_defc|MethodHandles::_suppress_type, CHECK_NULL); +Method* java_lang_StackFrameInfo::get_method(oop obj) { + oop m = java_lang_ClassFrameInfo::classOrMemberName(obj); + Method* method = java_lang_invoke_ResolvedMethodName::vmtarget(m); return method; } void java_lang_StackFrameInfo::set_method_and_bci(Handle stackFrame, const methodHandle& method, int bci, oop cont, TRAPS) { // set Method* or mid/cpref HandleMark hm(THREAD); - Handle mname(THREAD, stackFrame->obj_field(_memberName_offset)); - Handle cont_h (THREAD, cont); - InstanceKlass* ik = method->method_holder(); - CallInfo info(method(), ik, CHECK); - MethodHandles::init_method_MemberName(mname, info); + Handle cont_h(THREAD, cont); + java_lang_ClassFrameInfo::init_method(stackFrame, method, CHECK); + // set bci java_lang_StackFrameInfo::set_bci(stackFrame(), bci); // method may be redefined; store the version @@ -3012,28 +3070,42 @@ void java_lang_StackFrameInfo::set_method_and_bci(Handle stackFrame, const metho void java_lang_StackFrameInfo::to_stack_trace_element(Handle stackFrame, Handle stack_trace_element, TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); - Handle mname(THREAD, stackFrame->obj_field(java_lang_StackFrameInfo::_memberName_offset)); - Klass* clazz = java_lang_Class::as_Klass(java_lang_invoke_MemberName::clazz(mname())); - InstanceKlass* holder = InstanceKlass::cast(clazz); - Method* method = java_lang_StackFrameInfo::get_method(stackFrame, holder, CHECK); + Method* method = java_lang_StackFrameInfo::get_method(stackFrame()); + InstanceKlass* holder = method->method_holder(); short version = stackFrame->short_field(_version_offset); int bci = stackFrame->int_field(_bci_offset); Symbol* name = method->name(); java_lang_StackTraceElement::fill_in(stack_trace_element, holder, methodHandle(THREAD, method), version, bci, name, CHECK); } -void java_lang_StackFrameInfo::set_version(oop element, short value) { - element->short_field_put(_version_offset, value); +oop java_lang_StackFrameInfo::type(oop obj) { + return obj->obj_field(_type_offset); } -void java_lang_StackFrameInfo::set_bci(oop element, int value) { +void java_lang_StackFrameInfo::set_type(oop obj, oop value) { + obj->obj_field_put(_type_offset, value); +} + +oop java_lang_StackFrameInfo::name(oop obj) { + return obj->obj_field(_name_offset); +} + +void java_lang_StackFrameInfo::set_name(oop obj, oop value) { + obj->obj_field_put(_name_offset, value); +} + +void java_lang_StackFrameInfo::set_version(oop obj, short value) { + obj->short_field_put(_version_offset, value); +} + +void java_lang_StackFrameInfo::set_bci(oop obj, int value) { assert(value >= 0 && value < max_jushort, "must be a valid bci value"); - element->int_field_put(_bci_offset, value); + obj->int_field_put(_bci_offset, value); } -void java_lang_StackFrameInfo::set_contScope(oop element, oop value) { - element->obj_field_put(_contScope_offset, value); +void java_lang_StackFrameInfo::set_contScope(oop obj, oop value) { + obj->obj_field_put(_contScope_offset, value); } int java_lang_LiveStackFrameInfo::_monitors_offset; @@ -3058,20 +3130,20 @@ void java_lang_LiveStackFrameInfo::serialize_offsets(SerializeClosure* f) { } #endif -void java_lang_LiveStackFrameInfo::set_monitors(oop element, oop value) { - element->obj_field_put(_monitors_offset, value); +void java_lang_LiveStackFrameInfo::set_monitors(oop obj, oop value) { + obj->obj_field_put(_monitors_offset, value); } -void java_lang_LiveStackFrameInfo::set_locals(oop element, oop value) { - element->obj_field_put(_locals_offset, value); +void java_lang_LiveStackFrameInfo::set_locals(oop obj, oop value) { + obj->obj_field_put(_locals_offset, value); } -void java_lang_LiveStackFrameInfo::set_operands(oop element, oop value) { - element->obj_field_put(_operands_offset, value); +void java_lang_LiveStackFrameInfo::set_operands(oop obj, oop value) { + obj->obj_field_put(_operands_offset, value); } -void java_lang_LiveStackFrameInfo::set_mode(oop element, int value) { - element->int_field_put(_mode_offset, value); +void java_lang_LiveStackFrameInfo::set_mode(oop obj, int value) { + obj->int_field_put(_mode_offset, value); } @@ -3947,14 +4019,19 @@ void java_lang_invoke_MemberName::serialize_offsets(SerializeClosure* f) { } #endif +#define RESOLVEDMETHOD_FIELDS_DO(macro) \ + macro(_vmholder_offset, k, "vmholder", class_signature, false) + void java_lang_invoke_ResolvedMethodName::compute_offsets() { InstanceKlass* k = vmClasses::ResolvedMethodName_klass(); assert(k != nullptr, "jdk mismatch"); + RESOLVEDMETHOD_FIELDS_DO(FIELD_COMPUTE_OFFSET); RESOLVEDMETHOD_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET); } #if INCLUDE_CDS void java_lang_invoke_ResolvedMethodName::serialize_offsets(SerializeClosure* f) { + RESOLVEDMETHOD_FIELDS_DO(FIELD_SERIALIZE_OFFSET); RESOLVEDMETHOD_INJECTED_FIELDS(INJECTED_FIELD_SERIALIZE_OFFSET); } #endif @@ -5217,6 +5294,7 @@ void java_lang_InternalError::serialize_offsets(SerializeClosure* f) { f(java_lang_reflect_Parameter) \ f(java_lang_Module) \ f(java_lang_StackTraceElement) \ + f(java_lang_ClassFrameInfo) \ f(java_lang_StackFrameInfo) \ f(java_lang_LiveStackFrameInfo) \ f(jdk_internal_vm_ContinuationScope) \ diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 44d74a20aa9..0d67e74562b 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -1199,7 +1199,6 @@ class jdk_internal_foreign_abi_CallConv: AllStatic { // (These are a private interface for Java code to query the class hierarchy.) #define RESOLVEDMETHOD_INJECTED_FIELDS(macro) \ - macro(java_lang_invoke_ResolvedMethodName, vmholder, object_signature, false) \ macro(java_lang_invoke_ResolvedMethodName, vmtarget, intptr_signature, false) class java_lang_invoke_ResolvedMethodName : AllStatic { @@ -1288,6 +1287,7 @@ class java_lang_invoke_MemberName: AllStatic { MN_IS_TYPE = 0x00080000, // nested type MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected MN_TRUSTED_FINAL = 0x00200000, // trusted final field + MN_HIDDEN_MEMBER = 0x00400000, // @Hidden annotation detected MN_REFERENCE_KIND_SHIFT = 24, // refKind MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, MN_NESTMATE_CLASS = 0x00000001, @@ -1591,6 +1591,26 @@ class Backtrace: AllStatic { friend class JavaClasses; }; +class java_lang_ClassFrameInfo: AllStatic { +private: + static int _classOrMemberName_offset; + static int _flags_offset; + +public: + static oop classOrMemberName(oop info); + static int flags(oop info); + + // Setters + static void init_class(Handle stackFrame, const methodHandle& m); + static void init_method(Handle stackFrame, const methodHandle& m, TRAPS); + + static void compute_offsets(); + static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN; + + // Debugging + friend class JavaClasses; +}; + // Interface to java.lang.StackFrameInfo objects #define STACKFRAMEINFO_INJECTED_FIELDS(macro) \ @@ -1598,16 +1618,22 @@ class Backtrace: AllStatic { class java_lang_StackFrameInfo: AllStatic { private: - static int _memberName_offset; + static int _type_offset; + static int _name_offset; static int _bci_offset; static int _version_offset; static int _contScope_offset; - static Method* get_method(Handle stackFrame, InstanceKlass* holder, TRAPS); - public: + // Getters + static oop name(oop info); + static oop type(oop info); + static Method* get_method(oop info); + // Setters static void set_method_and_bci(Handle stackFrame, const methodHandle& method, int bci, oop cont, TRAPS); + static void set_name(oop info, oop value); + static void set_type(oop info, oop value); static void set_bci(oop info, int value); static void set_version(oop info, short value); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 928add990c4..503b595074d 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -160,6 +160,7 @@ /* Stack Walking */ \ do_klass(StackWalker_klass, java_lang_StackWalker ) \ do_klass(AbstractStackWalker_klass, java_lang_StackStreamFactory_AbstractStackWalker ) \ + do_klass(ClassFrameInfo_klass, java_lang_ClassFrameInfo ) \ do_klass(StackFrameInfo_klass, java_lang_StackFrameInfo ) \ do_klass(LiveStackFrameInfo_klass, java_lang_LiveStackFrameInfo ) \ \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 705eaddadd8..1e5bbb60ca7 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -378,6 +378,7 @@ class SerializeClosure; /* Support for JVMCI */ \ JVMCI_VM_SYMBOLS_DO(template, do_alias) \ \ + template(java_lang_ClassFrameInfo, "java/lang/ClassFrameInfo") \ template(java_lang_StackWalker, "java/lang/StackWalker") \ template(java_lang_StackFrameInfo, "java/lang/StackFrameInfo") \ template(java_lang_LiveStackFrameInfo, "java/lang/LiveStackFrameInfo") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index a09ce6c6c92..b6099c2ebcc 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -244,19 +244,21 @@ JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable); * java.lang.StackWalker */ enum { - JVM_STACKWALK_FILL_CLASS_REFS_ONLY = 0x2, - JVM_STACKWALK_GET_CALLER_CLASS = 0x04, + JVM_STACKWALK_CLASS_INFO_ONLY = 0x2, JVM_STACKWALK_SHOW_HIDDEN_FRAMES = 0x20, JVM_STACKWALK_FILL_LIVE_STACK_FRAMES = 0x100 }; +JNIEXPORT void JNICALL +JVM_ExpandStackFrameInfo(JNIEnv *env, jobject obj); + JNIEXPORT jobject JNICALL -JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jlong mode, +JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jint mode, jint skip_frames, jobject contScope, jobject cont, jint frame_count, jint start_index, jobjectArray frames); JNIEXPORT jint JNICALL -JVM_MoreStackWalk(JNIEnv *env, jobject stackStream, jlong mode, jlong anchor, +JVM_MoreStackWalk(JNIEnv *env, jobject stackStream, jint mode, jlong anchor, jint frame_count, jint start_index, jobjectArray frames); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 685db6b939a..b18795f47f7 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -542,14 +542,28 @@ JVM_END JVM_ENTRY(void, JVM_InitStackTraceElement(JNIEnv* env, jobject element, jobject stackFrameInfo)) Handle stack_frame_info(THREAD, JNIHandles::resolve_non_null(stackFrameInfo)); Handle stack_trace_element(THREAD, JNIHandles::resolve_non_null(element)); - java_lang_StackFrameInfo::to_stack_trace_element(stack_frame_info, stack_trace_element, THREAD); + java_lang_StackFrameInfo::to_stack_trace_element(stack_frame_info, stack_trace_element, CHECK); JVM_END // java.lang.StackWalker ////////////////////////////////////////////////////// +JVM_ENTRY(void, JVM_ExpandStackFrameInfo(JNIEnv *env, jobject obj)) + Handle stack_frame_info(THREAD, JNIHandles::resolve_non_null(obj)); + bool have_name = (java_lang_StackFrameInfo::name(stack_frame_info()) != nullptr); + bool have_type = (java_lang_StackFrameInfo::type(stack_frame_info()) != nullptr); + Method* method = java_lang_StackFrameInfo::get_method(stack_frame_info()); + if (!have_name) { + oop name = StringTable::intern(method->name(), CHECK); + java_lang_StackFrameInfo::set_name(stack_frame_info(), name); + } + if (!have_type) { + Handle type = java_lang_String::create_from_symbol(method->signature(), CHECK); + java_lang_StackFrameInfo::set_type(stack_frame_info(), type()); + } +JVM_END -JVM_ENTRY(jobject, JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jlong mode, +JVM_ENTRY(jobject, JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jint mode, jint skip_frames, jobject contScope, jobject cont, jint frame_count, jint start_index, jobjectArray frames)) if (!thread->has_last_Java_frame()) { @@ -559,7 +573,7 @@ JVM_ENTRY(jobject, JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jlong mod Handle stackStream_h(THREAD, JNIHandles::resolve_non_null(stackStream)); Handle contScope_h(THREAD, JNIHandles::resolve(contScope)); Handle cont_h(THREAD, JNIHandles::resolve(cont)); - // frames array is a Class[] array when only getting caller reference, + // frames array is a ClassFrameInfo[] array when only getting caller reference, // and a StackFrameInfo[] array (or derivative) otherwise. It should never // be null. objArrayOop fa = objArrayOop(JNIHandles::resolve_non_null(frames)); @@ -576,10 +590,10 @@ JVM_ENTRY(jobject, JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jlong mod JVM_END -JVM_ENTRY(jint, JVM_MoreStackWalk(JNIEnv *env, jobject stackStream, jlong mode, jlong anchor, +JVM_ENTRY(jint, JVM_MoreStackWalk(JNIEnv *env, jobject stackStream, jint mode, jlong anchor, jint frame_count, jint start_index, jobjectArray frames)) - // frames array is a Class[] array when only getting caller reference, + // frames array is a ClassFrameInfo[] array when only getting caller reference, // and a StackFrameInfo[] array (or derivative) otherwise. It should never // be null. objArrayOop fa = objArrayOop(JNIHandles::resolve_non_null(frames)); diff --git a/src/hotspot/share/prims/methodHandles.cpp b/src/hotspot/share/prims/methodHandles.cpp index 0eafa811fdf..e1f1977b67e 100644 --- a/src/hotspot/share/prims/methodHandles.cpp +++ b/src/hotspot/share/prims/methodHandles.cpp @@ -137,6 +137,7 @@ enum { IS_TYPE = java_lang_invoke_MemberName::MN_IS_TYPE, CALLER_SENSITIVE = java_lang_invoke_MemberName::MN_CALLER_SENSITIVE, TRUSTED_FINAL = java_lang_invoke_MemberName::MN_TRUSTED_FINAL, + HIDDEN_MEMBER = java_lang_invoke_MemberName::MN_HIDDEN_MEMBER, REFERENCE_KIND_SHIFT = java_lang_invoke_MemberName::MN_REFERENCE_KIND_SHIFT, REFERENCE_KIND_MASK = java_lang_invoke_MemberName::MN_REFERENCE_KIND_MASK, LM_UNCONDITIONAL = java_lang_invoke_MemberName::MN_UNCONDITIONAL_MODE, @@ -1003,6 +1004,7 @@ void MethodHandles::trace_method_handle_interpreter_entry(MacroAssembler* _masm, template(java_lang_invoke_MemberName,MN_IS_TYPE) \ template(java_lang_invoke_MemberName,MN_CALLER_SENSITIVE) \ template(java_lang_invoke_MemberName,MN_TRUSTED_FINAL) \ + template(java_lang_invoke_MemberName,MN_HIDDEN_MEMBER) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_SHIFT) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_MASK) \ template(java_lang_invoke_MemberName,MN_NESTMATE_CLASS) \ diff --git a/src/hotspot/share/prims/stackwalk.cpp b/src/hotspot/share/prims/stackwalk.cpp index 9e8a86c516c..619f64085a0 100644 --- a/src/hotspot/share/prims/stackwalk.cpp +++ b/src/hotspot/share/prims/stackwalk.cpp @@ -80,7 +80,7 @@ void BaseFrameStream::set_continuation(Handle cont) { _continuation.replace(cont()); } -JavaFrameStream::JavaFrameStream(JavaThread* thread, jlong mode, Handle cont_scope, Handle cont) +JavaFrameStream::JavaFrameStream(JavaThread* thread, jint mode, Handle cont_scope, Handle cont) : BaseFrameStream(thread, cont), _vfst(cont.is_null() ? vframeStream(thread, cont_scope) @@ -154,15 +154,15 @@ BaseFrameStream* BaseFrameStream::from_current(JavaThread* thread, jlong magic, // BaseFrameStream stream of frames // max_nframes Maximum number of frames to be filled. // start_index Start index to the user-supplied buffers. -// frames_array Buffer to store Class or StackFrame in, starting at start_index. -// frames array is a Class[] array when only getting caller +// frames_array Buffer to store stack frame information in, starting at start_index. +// frames array is a ClassFrameInfo[] array when only getting caller // reference, and a StackFrameInfo[] array (or derivative) // otherwise. It should never be null. // end_index End index to the user-supplied buffers with unpacked frames. // // Returns the number of frames whose information was transferred into the buffers. // -int StackWalk::fill_in_frames(jlong mode, BaseFrameStream& stream, +int StackWalk::fill_in_frames(jint mode, BaseFrameStream& stream, int max_nframes, int start_index, objArrayHandle frames_array, int& end_index, TRAPS) { @@ -186,16 +186,17 @@ int StackWalk::fill_in_frames(jlong mode, BaseFrameStream& stream, // skip hidden frames for default StackWalker option (i.e. SHOW_HIDDEN_FRAMES // not set) and when StackWalker::getCallerClass is called - if (!ShowHiddenFrames && (skip_hidden_frames(mode) || get_caller_class(mode))) { + LogTarget(Debug, stackwalk) lt; + if (!ShowHiddenFrames && skip_hidden_frames(mode)) { if (method->is_hidden()) { - LogTarget(Debug, stackwalk) lt; if (lt.is_enabled()) { ResourceMark rm(THREAD); LogStream ls(lt); - ls.print(" hidden method: "); + ls.print(" skip hidden method: "); method->print_short_name(&ls); ls.cr(); } + // We end a batch on continuation bottom to let the Java side skip top frames of the next one if (stream.continuation() != nullptr && method->intrinsic_id() == vmIntrinsics::_Continuation_enter) break; continue; @@ -203,7 +204,6 @@ int StackWalk::fill_in_frames(jlong mode, BaseFrameStream& stream, } int index = end_index++; - LogTarget(Debug, stackwalk) lt; if (lt.is_enabled()) { ResourceMark rm(THREAD); LogStream ls(lt); @@ -212,13 +212,6 @@ int StackWalk::fill_in_frames(jlong mode, BaseFrameStream& stream, ls.print_cr(" bci=%d", stream.bci()); } - if (!need_method_info(mode) && get_caller_class(mode) && - index == start_index && method->caller_sensitive()) { - ResourceMark rm(THREAD); - THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), - err_msg("StackWalker::getCallerClass called from @CallerSensitive '%s' method", - method->external_name())); - } // fill in StackFrameInfo and initialize MemberName stream.fill_frame(index, frames_array, methodHandle(THREAD, method), CHECK_0); @@ -256,7 +249,9 @@ void JavaFrameStream::fill_frame(int index, objArrayHandle frames_array, Handle stackFrame(THREAD, frames_array->obj_at(index)); fill_stackframe(stackFrame, method, CHECK); } else { - frames_array->obj_at_put(index, method->method_holder()->java_mirror()); + HandleMark hm(THREAD); + Handle stackFrame(THREAD, frames_array->obj_at(index)); + java_lang_ClassFrameInfo::init_class(stackFrame, method); } } @@ -356,7 +351,7 @@ objArrayHandle LiveFrameStream::monitors_to_object_array(GrowableArray[] array when only getting caller +// frames_array Buffer to store stack frame info in, starting at start_index. +// frames array is a ClassFrameInfo[] array when only getting caller // reference, and a StackFrameInfo[] array (or derivative) // otherwise. It should never be null. // // Returns Object returned from AbstractStackWalker::doStackWalk call. // -oop StackWalk::walk(Handle stackStream, jlong mode, int skip_frames, Handle cont_scope, Handle cont, +oop StackWalk::walk(Handle stackStream, jint mode, int skip_frames, Handle cont_scope, Handle cont, int frame_count, int start_index, objArrayHandle frames_array, TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); // needed to store a continuation in the RegisterMap JavaThread* jt = THREAD; - log_debug(stackwalk)("Start walking: mode " JLONG_FORMAT " skip %d frames batch size %d", mode, skip_frames, frame_count); + log_debug(stackwalk)("Start walking: mode " INT32_FORMAT_X " skip %d frames batch size %d", mode, skip_frames, frame_count); LogTarget(Debug, stackwalk) lt; if (lt.is_enabled()) { ResourceMark rm(THREAD); @@ -437,7 +432,6 @@ oop StackWalk::walk(Handle stackStream, jlong mode, int skip_frames, Handle cont // Setup traversal onto my stack. if (live_frame_info(mode)) { - assert(use_frames_array(mode), "Bad mode for get live frame"); RegisterMap regMap = cont.is_null() ? RegisterMap(jt, RegisterMap::UpdateMap::include, RegisterMap::ProcessFrames::include, @@ -454,7 +448,7 @@ oop StackWalk::walk(Handle stackStream, jlong mode, int skip_frames, Handle cont } oop StackWalk::fetchFirstBatch(BaseFrameStream& stream, Handle stackStream, - jlong mode, int skip_frames, int frame_count, + jint mode, int skip_frames, int frame_count, int start_index, objArrayHandle frames_array, TRAPS) { methodHandle m_doStackWalk(THREAD, Universe::do_stack_walk_method()); @@ -547,7 +541,7 @@ oop StackWalk::fetchFirstBatch(BaseFrameStream& stream, Handle stackStream, // // Returns the end index of frame filled in the buffer. // -jint StackWalk::fetchNextBatch(Handle stackStream, jlong mode, jlong magic, +jint StackWalk::fetchNextBatch(Handle stackStream, jint mode, jlong magic, int frame_count, int start_index, objArrayHandle frames_array, TRAPS) diff --git a/src/hotspot/share/prims/stackwalk.hpp b/src/hotspot/share/prims/stackwalk.hpp index 3e85491f53e..f918c4427a6 100644 --- a/src/hotspot/share/prims/stackwalk.hpp +++ b/src/hotspot/share/prims/stackwalk.hpp @@ -90,7 +90,7 @@ private: bool _need_method_info; public: - JavaFrameStream(JavaThread* thread, jlong mode, Handle cont_scope, Handle cont); + JavaFrameStream(JavaThread* thread, jint mode, Handle cont_scope, Handle cont); const RegisterMap* reg_map() override { return _vfst.reg_map(); }; @@ -142,37 +142,32 @@ public: class StackWalk : public AllStatic { private: - static int fill_in_frames(jlong mode, BaseFrameStream& stream, + static int fill_in_frames(jint mode, BaseFrameStream& stream, int max_nframes, int start_index, objArrayHandle frames_array, int& end_index, TRAPS); - static inline bool get_caller_class(jlong mode) { - return (mode & JVM_STACKWALK_GET_CALLER_CLASS) != 0; - } - static inline bool skip_hidden_frames(jlong mode) { + static inline bool skip_hidden_frames(jint mode) { return (mode & JVM_STACKWALK_SHOW_HIDDEN_FRAMES) == 0; } - static inline bool live_frame_info(jlong mode) { + static inline bool live_frame_info(jint mode) { return (mode & JVM_STACKWALK_FILL_LIVE_STACK_FRAMES) != 0; } public: - static inline bool need_method_info(jlong mode) { - return (mode & JVM_STACKWALK_FILL_CLASS_REFS_ONLY) == 0; + static inline bool need_method_info(jint mode) { + return (mode & JVM_STACKWALK_CLASS_INFO_ONLY) == 0; } - static inline bool use_frames_array(jlong mode) { - return (mode & JVM_STACKWALK_FILL_CLASS_REFS_ONLY) == 0; - } - static oop walk(Handle stackStream, jlong mode, int skip_frames, Handle cont_scope, Handle cont, + + static oop walk(Handle stackStream, jint mode, int skip_frames, Handle cont_scope, Handle cont, int frame_count, int start_index, objArrayHandle frames_array, TRAPS); static oop fetchFirstBatch(BaseFrameStream& stream, Handle stackStream, - jlong mode, int skip_frames, int frame_count, + jint mode, int skip_frames, int frame_count, int start_index, objArrayHandle frames_array, TRAPS); - static jint fetchNextBatch(Handle stackStream, jlong mode, jlong magic, + static jint fetchNextBatch(Handle stackStream, jint mode, jlong magic, int frame_count, int start_index, objArrayHandle frames_array, TRAPS); diff --git a/src/java.base/share/classes/java/lang/ClassFrameInfo.java b/src/java.base/share/classes/java/lang/ClassFrameInfo.java new file mode 100644 index 00000000000..1c0f6011a75 --- /dev/null +++ b/src/java.base/share/classes/java/lang/ClassFrameInfo.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package java.lang; + +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import java.lang.StackWalker.StackFrame; + +/** + * ClassFrameInfo is an implementation of StackFrame that contains only + * the class name and declaring class. + * + * Methods that access the method information such as method name, + * will throw UnsupportedOperationException. + * + * @see StackWalker.Option#DROP_METHOD_INFO + */ +class ClassFrameInfo implements StackFrame { + static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + + Object classOrMemberName; // Class or ResolvedMemberName initialized by VM + int flags; // updated by VM to set hidden and caller-sensitive bits + + /* + * Construct an empty ClassFrameInfo object that will be filled by the VM + * during stack walking. + * + * @see StackStreamFactory.AbstractStackWalker#callStackWalk + * @see StackStreamFactory.AbstractStackWalker#fetchStackFrames + */ + ClassFrameInfo(StackWalker walker) { + this.flags = walker.retainClassRef ? RETAIN_CLASS_REF_BIT : 0; + } + + // package-private called by StackStreamFactory to skip + // the capability check + Class declaringClass() { + return (Class) classOrMemberName; + } + + boolean isCallerSensitive() { + return JLIA.isCallerSensitive(flags & MEMBER_INFO_FLAGS); + } + + boolean isHidden() { + return JLIA.isHiddenMember(flags & MEMBER_INFO_FLAGS); + } + + // ----- implementation of StackFrame methods + + @Override + public String getClassName() { + return declaringClass().getName(); + } + + @Override + public Class getDeclaringClass() { + ensureRetainClassRefEnabled(); + return declaringClass(); + } + + @Override + public String getMethodName() { + throw new UnsupportedOperationException(); + } + + @Override + public int getByteCodeIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public String getFileName() { + throw new UnsupportedOperationException(); + } + + @Override + public int getLineNumber() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNativeMethod() { + throw new UnsupportedOperationException(); + } + + @Override + public StackTraceElement toStackTraceElement() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + String tags = isHidden() ? " hidden" : ""; + if (isCallerSensitive()) { + tags += " caller sensitive"; + } + return declaringClass().getName() + " " + tags; + } + + private static final int MEMBER_INFO_FLAGS = 0x00FFFFFF; + private static final int RETAIN_CLASS_REF_BIT = 0x08000000; // retainClassRef + + boolean retainClassRef() { + return (flags & RETAIN_CLASS_REF_BIT) == RETAIN_CLASS_REF_BIT; + } + + void ensureRetainClassRefEnabled() { + if (!retainClassRef()) { + throw new UnsupportedOperationException("No access to RETAIN_CLASS_REFERENCE"); + } + } +} diff --git a/src/java.base/share/classes/java/lang/StackFrameInfo.java b/src/java.base/share/classes/java/lang/StackFrameInfo.java index 53ad7491e8c..0e935592fbe 100644 --- a/src/java.base/share/classes/java/lang/StackFrameInfo.java +++ b/src/java.base/share/classes/java/lang/StackFrameInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -24,20 +24,20 @@ */ package java.lang; -import jdk.internal.access.JavaLangInvokeAccess; -import jdk.internal.access.SharedSecrets; import jdk.internal.vm.ContinuationScope; -import java.lang.StackWalker.StackFrame; import java.lang.invoke.MethodType; +import java.lang.reflect.Modifier; -class StackFrameInfo implements StackFrame { - private static final JavaLangInvokeAccess JLIA = - SharedSecrets.getJavaLangInvokeAccess(); - - private final boolean retainClassRef; - private Object memberName; // MemberName initialized by VM - private int bci; // initialized by VM to >= 0 +/** + * StackFrameInfo is an implementation of StackFrame that contains the + * class and method information. This is used by stack walker configured + * without StackWalker.Option#DROP_METHOD_INFO. + */ +class StackFrameInfo extends ClassFrameInfo { + private String name; + private Object type; // String or MethodType + private int bci; // set by VM to >= 0 private ContinuationScope contScope; private volatile StackTraceElement ste; @@ -49,14 +49,13 @@ class StackFrameInfo implements StackFrame { * @see StackStreamFactory.AbstractStackWalker#fetchStackFrames */ StackFrameInfo(StackWalker walker) { - this.retainClassRef = walker.retainClassRef; - this.memberName = JLIA.newMemberName(); + super(walker); } // package-private called by StackStreamFactory to skip // the capability check Class declaringClass() { - return JLIA.getDeclaringClass(memberName); + return JLIA.getDeclaringClass(classOrMemberName); } // ----- implementation of StackFrame methods @@ -66,26 +65,43 @@ class StackFrameInfo implements StackFrame { return declaringClass().getName(); } - @Override - public Class getDeclaringClass() { - ensureRetainClassRefEnabled(); - return declaringClass(); - } - @Override public String getMethodName() { - return JLIA.getName(memberName); + if (name == null) { + expandStackFrameInfo(); + assert name != null; + } + return name; } @Override public MethodType getMethodType() { ensureRetainClassRefEnabled(); - return JLIA.getMethodType(memberName); + + if (type == null) { + expandStackFrameInfo(); + assert type != null; + } + + if (type instanceof MethodType mt) { + return mt; + } + + // type is not a MethodType yet. Convert it thread-safely. + synchronized (this) { + if (type instanceof String sig) { + type = JLIA.getMethodType(sig, declaringClass().getClassLoader()); + } + } + return (MethodType)type; } + // expand the name and type field of StackFrameInfo + private native void expandStackFrameInfo(); + @Override public String getDescriptor() { - return JLIA.getMethodDescriptor(memberName); + return getMethodType().descriptorString(); } @Override @@ -114,7 +130,7 @@ class StackFrameInfo implements StackFrame { @Override public boolean isNativeMethod() { - return JLIA.isNative(memberName); + return Modifier.isNative(flags); } private String getContinuationScopeName() { @@ -139,10 +155,4 @@ class StackFrameInfo implements StackFrame { } return s; } - - private void ensureRetainClassRefEnabled() { - if (!retainClassRef) { - throw new UnsupportedOperationException("No access to RETAIN_CLASS_REFERENCE"); - } - } } diff --git a/src/java.base/share/classes/java/lang/StackStreamFactory.java b/src/java.base/share/classes/java/lang/StackStreamFactory.java index 8d7b77ef5ea..67ca9e58ab9 100644 --- a/src/java.base/share/classes/java/lang/StackStreamFactory.java +++ b/src/java.base/share/classes/java/lang/StackStreamFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -30,8 +30,10 @@ import java.lang.StackWalker.Option; import java.lang.StackWalker.StackFrame; import java.lang.annotation.Native; +import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Constructor; +import java.util.Arrays; import java.util.HashSet; import java.util.NoSuchElementException; import java.util.Objects; @@ -74,10 +76,10 @@ final class StackStreamFactory { // These flags must match the values maintained in the VM @Native private static final int DEFAULT_MODE = 0x0; - @Native private static final int FILL_CLASS_REFS_ONLY = 0x2; - @Native private static final int GET_CALLER_CLASS = 0x4; + @Native private static final int CLASS_INFO_ONLY = 0x2; @Native private static final int SHOW_HIDDEN_FRAMES = 0x20; // LambdaForms are hidden by the VM @Native private static final int FILL_LIVE_STACK_FRAMES = 0x100; + /* * For Throwable to use StackWalker, set useNewThrowable to true. * Performance work and extensive testing is needed to replace the @@ -89,10 +91,11 @@ final class StackStreamFactory { static StackFrameTraverser makeStackTraverser(StackWalker walker, Function, ? extends T> function) { - if (walker.hasLocalsOperandsOption()) + if (walker.hasLocalsOperandsOption()) { return new LiveStackInfoTraverser<>(walker, function); - else + } else { return new StackFrameTraverser<>(walker, function); + } } /** @@ -108,6 +111,17 @@ final class StackStreamFactory { CLOSED; // the stream is closed when the stack walking is done } + private static int toStackWalkMode(StackWalker walker, int mode) { + int newMode = mode; + if (walker.hasOption(Option.DROP_METHOD_INFO)) + newMode |= CLASS_INFO_ONLY; + if (walker.hasOption(Option.SHOW_HIDDEN_FRAMES)) + newMode |= SHOW_HIDDEN_FRAMES; + if (walker.hasLocalsOperandsOption()) + newMode |= FILL_LIVE_STACK_FRAMES; + return newMode; + } + /** * Subclass of AbstractStackWalker implements a specific stack walking logic. * It needs to set up the frame buffer and stack walking mode. @@ -124,7 +138,7 @@ final class StackStreamFactory { protected final StackWalker walker; protected final Thread thread; protected final int maxDepth; - protected final long mode; + protected final int mode; protected int depth; // traversed stack depth protected FrameBuffer frameBuffer; protected long anchor; @@ -137,7 +151,7 @@ final class StackStreamFactory { } protected AbstractStackWalker(StackWalker walker, int mode, int maxDepth) { this.thread = Thread.currentThread(); - this.mode = toStackWalkMode(walker, mode); + this.mode = mode; this.walker = walker; this.maxDepth = maxDepth; this.depth = 0; @@ -151,16 +165,6 @@ final class StackStreamFactory { } } - private int toStackWalkMode(StackWalker walker, int mode) { - int newMode = mode; - if (walker.hasOption(Option.SHOW_HIDDEN_FRAMES) && - (mode & FILL_CLASS_REFS_ONLY) != FILL_CLASS_REFS_ONLY) - newMode |= SHOW_HIDDEN_FRAMES; - if (walker.hasLocalsOperandsOption()) - newMode |= FILL_LIVE_STACK_FRAMES; - return newMode; - } - /** * A callback method to consume the stack frames. This method is invoked * once stack walking begins (i.e. it is only invoked when walkFrames is called). @@ -450,11 +454,11 @@ final class StackStreamFactory { * @param continuation the continuation to walk, or {@code null} if walking a thread. * @param batchSize the batch size, max. number of elements to be filled in the frame buffers. * @param startIndex start index of the frame buffers to be filled. - * @param frames Either a Class array, if mode is {@link #FILL_CLASS_REFS_ONLY} + * @param frames Either a {@link ClassFrameInfo} array, if mode is {@link #CLASS_INFO_ONLY} * or a {@link StackFrameInfo} (or derivative) array otherwise. * @return Result of AbstractStackWalker::doStackWalk */ - private native R callStackWalk(long mode, int skipframes, + private native R callStackWalk(int mode, int skipframes, ContinuationScope contScope, Continuation continuation, int batchSize, int startIndex, T[] frames); @@ -466,12 +470,12 @@ final class StackStreamFactory { * @param anchor * @param batchSize the batch size, max. number of elements to be filled in the frame buffers. * @param startIndex start index of the frame buffers to be filled. - * @param frames Either a Class array, if mode is {@link #FILL_CLASS_REFS_ONLY} + * @param frames Either a {@link ClassFrameInfo} array, if mode is {@link #CLASS_INFO_ONLY} * or a {@link StackFrameInfo} (or derivative) array otherwise. * * @return the end index to the frame buffers */ - private native int fetchStackFrames(long mode, long anchor, + private native int fetchStackFrames(int mode, long anchor, int batchSize, int startIndex, T[] frames); @@ -483,7 +487,7 @@ final class StackStreamFactory { * * This class implements Spliterator::forEachRemaining and Spliterator::tryAdvance. */ - static class StackFrameTraverser extends AbstractStackWalker + static class StackFrameTraverser extends AbstractStackWalker implements Spliterator { static { @@ -491,70 +495,11 @@ final class StackStreamFactory { } private static final int CHARACTERISTICS = Spliterator.ORDERED | Spliterator.IMMUTABLE; - final class StackFrameBuffer extends FrameBuffer { - private StackFrameInfo[] stackFrames; - StackFrameBuffer(int initialBatchSize) { - super(initialBatchSize); - - this.stackFrames = new StackFrameInfo[initialBatchSize]; - for (int i = START_POS; i < initialBatchSize; i++) { - stackFrames[i] = new StackFrameInfo(walker); - } - } - - @Override - StackFrameInfo[] frames() { - return stackFrames; - } - - @Override - void resize(int startIndex, int elements) { - if (!isActive()) - throw new IllegalStateException("inactive frame buffer can't be resized"); - - assert startIndex == START_POS : - "bad start index " + startIndex + " expected " + START_POS; - - int size = startIndex+elements; - if (stackFrames.length < size) { - StackFrameInfo[] newFrames = new StackFrameInfo[size]; - // copy initial magic... - System.arraycopy(stackFrames, 0, newFrames, 0, startIndex); - stackFrames = newFrames; - } - for (int i = startIndex; i < size; i++) { - stackFrames[i] = new StackFrameInfo(walker); - } - currentBatchSize = size; - } - - @Override - StackFrameInfo nextStackFrame() { - if (isEmpty()) { - throw new NoSuchElementException("origin=" + origin + " fence=" + fence); - } - - StackFrameInfo frame = stackFrames[origin]; - origin++; - return frame; - } - - @Override - final Class at(int index) { - return stackFrames[index].declaringClass(); - } - } - final Function, ? extends T> function; // callback StackFrameTraverser(StackWalker walker, Function, ? extends T> function) { - this(walker, function, DEFAULT_MODE); - } - StackFrameTraverser(StackWalker walker, - Function, ? extends T> function, - int mode) { - super(walker, mode); + super(walker, toStackWalkMode(walker, DEFAULT_MODE)); this.function = function; } @@ -567,7 +512,7 @@ final class StackStreamFactory { return null; } - StackFrameInfo frame = frameBuffer.nextStackFrame(); + StackFrame frame = frameBuffer.nextStackFrame(); depth++; return frame; } @@ -584,7 +529,9 @@ final class StackStreamFactory { @Override protected void initFrameBuffer() { - this.frameBuffer = new StackFrameBuffer(getNextBatchSize()); + this.frameBuffer = walker.hasOption(Option.DROP_METHOD_INFO) + ? new ClassFrameBuffer(walker, getNextBatchSize()) + : new StackFrameBuffer<>(StackFrameInfo.class, walker, getNextBatchSize()); } @Override @@ -651,11 +598,108 @@ final class StackStreamFactory { } } + static class StackFrameBuffer extends FrameBuffer { + final StackWalker walker; + private final Class type; + private final Constructor ctor; + private T[] stackFrames; + StackFrameBuffer(Class type, StackWalker walker, int initialBatchSize) { + super(initialBatchSize); + this.walker = walker; + this.type = type; + try { + this.ctor = type.getDeclaredConstructor(StackWalker.class); + } catch (NoSuchMethodException e) { + throw new InternalError(e); + } + this.stackFrames = fill(allocateArray(initialBatchSize), START_POS, initialBatchSize); + } + + @Override + T[] frames() { + return stackFrames; + } + + @SuppressWarnings("unchecked") + T[] allocateArray(int size) { + return (T[])Array.newInstance(type, size); + } + + T[] fill(T[] array, int startIndex, int size) { + try { + for (int i = startIndex; i < size; i++) { + array[i] = ctor.newInstance(walker); + } + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + return array; + } + + @Override + void resize(int startIndex, int elements) { + if (!isActive()) + throw new IllegalStateException("inactive frame buffer can't be resized"); + + assert startIndex == START_POS : + "bad start index " + startIndex + " expected " + START_POS; + + int size = startIndex+elements; + if (stackFrames.length < size) { + T[] newFrames = allocateArray(size); + // copy initial magic... + System.arraycopy(stackFrames, 0, newFrames, 0, startIndex); + stackFrames = newFrames; + } + fill(stackFrames, startIndex, size); + currentBatchSize = size; + } + + @Override + T nextStackFrame() { + if (isEmpty()) { + throw new NoSuchElementException("origin=" + origin + " fence=" + fence); + } + + T frame = stackFrames[origin]; + origin++; + return frame; + } + + @Override + final Class at(int index) { + return stackFrames[index].declaringClass(); + } + } + + /* + * Buffer for ClassFrameInfo. It allocates ClassFrameInfo via bytecode + * invocation instead of via core reflection to minimize the overhead. + */ + static class ClassFrameBuffer extends StackFrameBuffer { + ClassFrameBuffer(StackWalker walker, int initialBatchSize) { + super(ClassFrameInfo.class, walker, initialBatchSize); + } + + @Override + ClassFrameInfo[] allocateArray(int size) { + return new ClassFrameInfo[size]; + } + + @Override + ClassFrameInfo[] fill(ClassFrameInfo[] array, int startIndex, int size) { + for (int i = startIndex; i < size; i++) { + array[i] = new ClassFrameInfo(walker); + } + return array; + } + } + /* * CallerClassFinder is specialized to return Class for each stack frame. * StackFrame is not requested. */ - static final class CallerClassFinder extends AbstractStackWalker> { + static final class CallerClassFinder extends AbstractStackWalker { static { stackWalkImplClasses.add(CallerClassFinder.class); } @@ -663,52 +707,7 @@ final class StackStreamFactory { private Class caller; CallerClassFinder(StackWalker walker) { - super(walker, FILL_CLASS_REFS_ONLY|GET_CALLER_CLASS); - } - - static final class ClassBuffer extends FrameBuffer> { - Class[] classes; // caller class for fast path - ClassBuffer(int batchSize) { - super(batchSize); - classes = new Class[batchSize]; - } - - @Override - Class[] frames() { return classes;} - - @Override - final Class at(int index) { return classes[index];} - - // ------ subclass may override the following methods ------- - /** - * Resizes the buffers for VM to fill in the next batch of stack frames. - * The next batch will start at the given startIndex with the maximum number - * of elements. - * - *

Subclass may override this method to manage the allocated buffers. - * - * @param startIndex the start index for the first frame of the next batch to fill in. - * @param elements the number of elements for the next batch to fill in. - * - */ - @Override - void resize(int startIndex, int elements) { - if (!isActive()) - throw new IllegalStateException("inactive frame buffer can't be resized"); - - assert startIndex == START_POS : - "bad start index " + startIndex + " expected " + START_POS; - - int size = startIndex+elements; - if (classes.length < size) { - // copy the elements in classes array to the newly allocated one. - // classes[0] is a Thread object - Class[] prev = classes; - classes = new Class[size]; - System.arraycopy(prev, 0, classes, 0, startIndex); - } - currentBatchSize = size; - } + super(walker, toStackWalkMode(walker, CLASS_INFO_ONLY)); } Class findCaller() { @@ -720,25 +719,37 @@ final class StackStreamFactory { protected Integer consumeFrames() { checkState(OPEN); int n = 0; - Class[] frames = new Class[2]; - // skip the API calling this getCallerClass method - // 0: StackWalker::getCallerClass - // 1: caller-sensitive method - // 2: caller class - while (n < 2 && (caller = nextFrame()) != null) { - if (isMethodHandleFrame(caller)) { continue; } - if (isReflectionFrame(caller)) { continue; } - frames[n++] = caller; + ClassFrameInfo curFrame = null; + // StackWalker::getCallerClass method + // 0: caller-sensitive method + // 1: caller class + ClassFrameInfo[] frames = new ClassFrameInfo[2]; + while (n < 2 && hasNext() && (curFrame = frameBuffer.nextStackFrame()) != null) { + caller = curFrame.declaringClass(); + if (curFrame.isHidden() || isReflectionFrame(caller) || isMethodHandleFrame(caller)) { + if (isDebug) + System.err.println(" skip: frame " + frameBuffer.getIndex() + " " + curFrame); + continue; + } + frames[n++] = curFrame; + } + if (isDebug) { + System.err.println("0: " + frames[0]); + System.err.println("1: " + frames[1]); } if (frames[1] == null) { - throw new IllegalCallerException("no caller frame"); + throw new IllegalCallerException("no caller frame: " + Arrays.toString(frames)); + } + if (frames[0].isCallerSensitive()) { + throw new UnsupportedOperationException("StackWalker::getCallerClass called from @CallerSensitive " + + Arrays.toString(frames)); } return n; } @Override protected void initFrameBuffer() { - this.frameBuffer = new ClassBuffer(getNextBatchSize()); + this.frameBuffer = new ClassFrameBuffer(walker, getNextBatchSize()); } @Override @@ -756,70 +767,15 @@ final class StackStreamFactory { static { stackWalkImplClasses.add(LiveStackInfoTraverser.class); } - // VM will fill in all method info and live stack info directly in StackFrameInfo - final class LiveStackFrameBuffer extends FrameBuffer { - private LiveStackFrameInfo[] stackFrames; - LiveStackFrameBuffer(int initialBatchSize) { - super(initialBatchSize); - this.stackFrames = new LiveStackFrameInfo[initialBatchSize]; - for (int i = START_POS; i < initialBatchSize; i++) { - stackFrames[i] = new LiveStackFrameInfo(walker); - } - } - - @Override - LiveStackFrameInfo[] frames() { - return stackFrames; - } - - @Override - void resize(int startIndex, int elements) { - if (!isActive()) { - throw new IllegalStateException("inactive frame buffer can't be resized"); - } - assert startIndex == START_POS : - "bad start index " + startIndex + " expected " + START_POS; - - int size = startIndex + elements; - if (stackFrames.length < size) { - LiveStackFrameInfo[] newFrames = new LiveStackFrameInfo[size]; - // copy initial magic... - System.arraycopy(stackFrames, 0, newFrames, 0, startIndex); - stackFrames = newFrames; - } - - for (int i = startIndex(); i < size; i++) { - stackFrames[i] = new LiveStackFrameInfo(walker); - } - - currentBatchSize = size; - } - - @Override - LiveStackFrameInfo nextStackFrame() { - if (isEmpty()) { - throw new NoSuchElementException("origin=" + origin + " fence=" + fence); - } - - LiveStackFrameInfo frame = stackFrames[origin]; - origin++; - return frame; - } - - @Override - final Class at(int index) { - return stackFrames[index].declaringClass(); - } - } LiveStackInfoTraverser(StackWalker walker, Function, ? extends T> function) { - super(walker, function, DEFAULT_MODE); + super(walker, function); } @Override protected void initFrameBuffer() { - this.frameBuffer = new LiveStackFrameBuffer(getNextBatchSize()); + this.frameBuffer = new StackFrameBuffer<>(LiveStackFrameInfo.class, walker, getNextBatchSize()); } } @@ -851,7 +807,7 @@ final class StackStreamFactory { * when walking the stack. * * May be an array of {@code Class} if the {@code AbstractStackWalker} - * mode is {@link #FILL_CLASS_REFS_ONLY}, or an array of + * mode is {@link #CLASS_INFO_ONLY}, or an array of * {@link StackFrameInfo} (or derivative) array otherwise. * * @return An array of frames that may be used to store frame objects @@ -1021,22 +977,20 @@ final class StackStreamFactory { private static boolean filterStackWalkImpl(Class c) { return stackWalkImplClasses.contains(c) || - c.getName().startsWith("java.util.stream."); + c.getPackageName().equals("java.util.stream"); } // MethodHandle frames are not hidden and CallerClassFinder has // to filter them out private static boolean isMethodHandleFrame(Class c) { - return c.getName().startsWith("java.lang.invoke."); + return c.getPackageName().equals("java.lang.invoke"); } private static boolean isReflectionFrame(Class c) { - // ## should filter all @Hidden frames? return c == Method.class || c == Constructor.class || MethodAccessor.class.isAssignableFrom(c) || - ConstructorAccessor.class.isAssignableFrom(c) || - c.getName().startsWith("java.lang.invoke.LambdaForm"); + ConstructorAccessor.class.isAssignableFrom(c); } } diff --git a/src/java.base/share/classes/java/lang/StackWalker.java b/src/java.base/share/classes/java/lang/StackWalker.java index 9b31de07f2f..aab9c6c28d8 100644 --- a/src/java.base/share/classes/java/lang/StackWalker.java +++ b/src/java.base/share/classes/java/lang/StackWalker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -49,13 +49,15 @@ import jdk.internal.vm.ContinuationScope; * If an attempt is made to reuse the closed stream, * {@code IllegalStateException} will be thrown. * - *

The {@linkplain Option stack walking options} of a - * {@code StackWalker} determines the information of - * {@link StackFrame StackFrame} objects to be returned. - * By default, stack frames of the reflection API and implementation - * classes are {@linkplain Option#SHOW_HIDDEN_FRAMES hidden} - * and {@code StackFrame}s have the class name and method name - * available but not the {@link StackFrame#getDeclaringClass() Class reference}. + *

{@linkplain Option Stack walker options} configure the stack frame + * information obtained by a {@code StackWalker}. + * By default, the class name and method information are collected but + * not the {@link StackFrame#getDeclaringClass() Class reference}. + * The method information can be dropped via the {@link Option#DROP_METHOD_INFO + * DROP_METHOD_INFO} option. The {@code Class} object can be retained for + * access via the {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option. + * Stack frames of the reflection API and implementation classes are + * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden} by default. * *

{@code StackWalker} is thread-safe. Multiple threads can share * a single {@code StackWalker} object to traverse its own stack. @@ -66,20 +68,19 @@ import jdk.internal.vm.ContinuationScope; * @apiNote * Examples * - *

1. To find the first caller filtering a known list of implementation class: - *

{@code
- *     StackWalker walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
+ * 

1. To find the first caller filtering out a known list of implementation class: + * {@snippet lang="java" : + * StackWalker walker = StackWalker.getInstance(Set.of(Option.DROP_METHOD_INFO, Option.RETAIN_CLASS_REFERENCE)); * Optional> callerClass = walker.walk(s -> - * s.map(StackFrame::getDeclaringClass) - * .filter(interestingClasses::contains) - * .findFirst()); - * }

+ * s.map(StackFrame::getDeclaringClass) + * .filter(Predicate.not(implClasses::contains)) + * .findFirst()); + * } * *

2. To snapshot the top 10 stack frames of the current thread, - *

{@code
- *     List stack = StackWalker.getInstance().walk(s ->
- *         s.limit(10).collect(Collectors.toList()));
- * }
+ * {@snippet lang="java" : + * List stack = StackWalker.getInstance().walk(s -> s.limit(10).toList()); + * } * * Unless otherwise noted, passing a {@code null} argument to a * constructor or method in this {@code StackWalker} class @@ -93,41 +94,44 @@ public final class StackWalker { * A {@code StackFrame} object represents a method invocation returned by * {@link StackWalker}. * - *

The {@link #getDeclaringClass()} method may be unsupported as determined - * by the {@linkplain Option stack walking options} of a {@linkplain - * StackWalker stack walker}. + *

{@linkplain Option Stack walker options} configure the stack + * frame information obtained by a {@code StackWalker}. + * If the stack walker is configured with {@link Option#DROP_METHOD_INFO + * DROP_METHOD_INFO} option, method information such as + * the {@linkplain StackFrame#getMethodName() method name}, + * the {@linkplain StackFrame#getLineNumber() line number}, + * the {@linkplain StackFrame#getByteCodeIndex() bytecode index}, etc + * will be dropped. + * If the stack walker is configured with {@link Option#RETAIN_CLASS_REFERENCE + * RETAIN_CLASS_REFERENCE} option, the {@link #getDeclaringClass() Class} object + * will be retained for access. * * @since 9 - * @jvms 2.6 + * @jvms 2.6 Frames */ public interface StackFrame { /** - * Gets the binary name - * of the declaring class of the method represented by this stack frame. - * - * @return the binary name of the declaring class of the method - * represented by this stack frame + * {@return the {@linkplain ClassLoader##binary-name binary name} + * of the declaring class of the method represented by this stack frame} * * @jls 13.1 The Form of a Binary */ public String getClassName(); /** - * Gets the name of the method represented by this stack frame. - * @return the name of the method represented by this stack frame + * {@return the name of the method represented by this stack frame} + * + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option */ public String getMethodName(); /** - * Gets the declaring {@code Class} for the method represented by - * this stack frame. + * {@return the declaring {@code Class} for the method represented by + * this stack frame} * - * @return the declaring {@code Class} of the method represented by - * this stack frame - * - * @throws UnsupportedOperationException if this {@code StackWalker} - * is not configured with {@link Option#RETAIN_CLASS_REFERENCE - * Option.RETAIN_CLASS_REFERENCE}. + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * without {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option */ public Class getDeclaringClass(); @@ -138,11 +142,11 @@ public final class StackWalker { * @implSpec * The default implementation throws {@code UnsupportedOperationException}. * - * @return the {@code MethodType} for this stack frame + * @return the {@code MethodType} of the method represented by this stack frame * - * @throws UnsupportedOperationException if this {@code StackWalker} - * is not configured with {@link Option#RETAIN_CLASS_REFERENCE - * Option.RETAIN_CLASS_REFERENCE}. + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option or + * without {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option * * @since 10 */ @@ -161,6 +165,9 @@ public final class StackWalker { * @return the descriptor of the method represented by * this stack frame * + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option + * * @see MethodType#fromMethodDescriptorString(String, ClassLoader) * @see MethodType#toMethodDescriptorString() * @jvms 4.3.3 Method Descriptor @@ -182,6 +189,9 @@ public final class StackWalker { * containing the execution point represented by this stack frame, * or a negative number if the method is native. * + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option + * * @jvms 4.7.3 The {@code Code} Attribute */ public int getByteCodeIndex(); @@ -198,6 +208,9 @@ public final class StackWalker { * represented by this stack frame, or {@code null} if * this information is unavailable. * + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option + * * @jvms 4.7.10 The {@code SourceFile} Attribute */ public String getFileName(); @@ -213,23 +226,27 @@ public final class StackWalker { * point represented by this stack frame, or a negative number if * this information is unavailable. * + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option + * * @jvms 4.7.12 The {@code LineNumberTable} Attribute */ public int getLineNumber(); /** - * Returns {@code true} if the method containing the execution point - * represented by this stack frame is a native method. + * {@return {@code true} if the method containing the execution point + * represented by this stack frame is a native method} * - * @return {@code true} if the method containing the execution point - * represented by this stack frame is a native method. + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option */ public boolean isNativeMethod(); /** - * Gets a {@code StackTraceElement} for this stack frame. + * {@return {@code StackTraceElement} for this stack frame} * - * @return {@code StackTraceElement} for this stack frame. + * @throws UnsupportedOperationException if the {@code StackWalker} is configured + * with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option */ public StackTraceElement toStackTraceElement(); } @@ -250,6 +267,21 @@ public final class StackWalker { * {@link StackFrame#getDeclaringClass() StackFrame.getDeclaringClass()}. */ RETAIN_CLASS_REFERENCE, + /** + * Drops the method information from {@code StackFrame}s + * walked by this {@code StackWalker}. + * + *

A {@code StackWalker} configured with this option will drop + * the {@linkplain StackFrame#getMethodName() method name}, + * the {@linkplain StackFrame#getMethodType() method type}, + * the {@linkplain StackFrame#getLineNumber() line number}, + * the {@linkplain StackFrame#getByteCodeIndex() bytecode index}, + * the {@linkplain StackFrame#getFileName() source file name} and + * {@linkplain StackFrame#isNativeMethod() native method or not}. + * + * @since 22 + */ + DROP_METHOD_INFO, /** * Shows all reflection frames. * @@ -319,28 +351,9 @@ public final class StackWalker { return DEFAULT_WALKER; } - /** - * Returns a {@code StackWalker} instance. - * - *

This {@code StackWalker} is configured to skip all - * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} and - * no {@linkplain Option#RETAIN_CLASS_REFERENCE class reference} is retained. - * - * @param contScope the continuation scope up to which (inclusive) to walk the stack - * - * @return a {@code StackWalker} configured to skip all - * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} and - * no {@linkplain Option#RETAIN_CLASS_REFERENCE class reference} is retained. - * - */ - static StackWalker getInstance(ContinuationScope contScope) { - return getInstance(EnumSet.noneOf(Option.class), contScope); - } - /** * Returns a {@code StackWalker} instance with the given option specifying * the stack frame information it can access. - * *

* If a security manager is present and the given {@code option} is * {@link Option#RETAIN_CLASS_REFERENCE Option.RETAIN_CLASS_REFERENCE}, @@ -359,33 +372,13 @@ public final class StackWalker { } /** - * Returns a {@code StackWalker} instance with the given option specifying + * Returns a {@code StackWalker} instance with the given {@code options} specifying * the stack frame information it can access. * *

- * If a security manager is present and the given {@code option} is - * {@link Option#RETAIN_CLASS_REFERENCE Option.RETAIN_CLASS_REFERENCE}, - * it calls its {@link SecurityManager#checkPermission checkPermission} - * method for {@code RuntimePermission("getStackWalkerWithClassReference")}. - * - * @param option {@link Option stack walking option} - * @param contScope the continuation scope up to which (inclusive) to walk the stack - * - * @return a {@code StackWalker} configured with the given option - * - * @throws SecurityException if a security manager exists and its - * {@code checkPermission} method denies access. - */ - static StackWalker getInstance(Option option, ContinuationScope contScope) { - return getInstance(EnumSet.of(Objects.requireNonNull(option)), contScope); - } - - /** - * Returns a {@code StackWalker} instance with the given {@code options} specifying - * the stack frame information it can access. If the given {@code options} - * is empty, this {@code StackWalker} is configured to skip all - * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} and no - * {@linkplain Option#RETAIN_CLASS_REFERENCE class reference} is retained. + * If the given {@code options} is empty, this {@code StackWalker} is + * configured to skip all {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} + * and no {@linkplain Option#RETAIN_CLASS_REFERENCE class reference} is retained. * *

* If a security manager is present and the given {@code options} contains @@ -393,7 +386,7 @@ public final class StackWalker { * it calls its {@link SecurityManager#checkPermission checkPermission} * method for {@code RuntimePermission("getStackWalkerWithClassReference")}. * - * @param options {@link Option stack walking option} + * @param options {@link Option stack walking options} * * @return a {@code StackWalker} configured with the given options * @@ -401,46 +394,23 @@ public final class StackWalker { * {@code checkPermission} method denies access. */ public static StackWalker getInstance(Set

- * If a security manager is present and the given {@code options} contains - * {@link Option#RETAIN_CLASS_REFERENCE Option.RETAIN_CLASS_REFERENCE}, - * it calls its {@link SecurityManager#checkPermission checkPermission} - * method for {@code RuntimePermission("getStackWalkerWithClassReference")}. - * - * @param options {@link Option stack walking option} - * @param contScope the continuation scope up to which (inclusive) to walk the stack - * - * @return a {@code StackWalker} configured with the given options - * - * @throws SecurityException if a security manager exists and its - * {@code checkPermission} method denies access. - */ - static StackWalker getInstance(Set

+ * If the given {@code options} is empty, this {@code StackWalker} is + * configured to skip all {@linkplain Option#SHOW_HIDDEN_FRAMES hidden frames} + * and no {@linkplain Option#RETAIN_CLASS_REFERENCE class reference} is retained. * *

* If a security manager is present and the given {@code options} contains @@ -475,24 +445,9 @@ public final class StackWalker { private StackWalker(EnumSet

- *
{@code
+     * {@snippet lang="java" :
      * List frames = StackWalker.getInstance().walk(s ->
-     *     s.dropWhile(f -> f.getClassName().startsWith("com.foo."))
-     *      .limit(10)
-     *      .collect(Collectors.toList()));
-     * }
+ * s.dropWhile(f -> f.getClassName().startsWith("com.foo.")) + * .limit(10) + * .toList()); + * } * *

This method takes a {@code Function} accepting a {@code Stream}, * rather than returning a {@code Stream} and allowing the @@ -638,9 +592,10 @@ public final class StackWalker { * the class loader to load the resource bundle. The caller class * in this example is {@code MyTool}. * - *

{@code
+     * {@snippet lang="java" :
      * class Util {
-     *     private final StackWalker walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
+     *     private final StackWalker walker =
+     *         StackWalker.getInstance(Set.of(Option.DROP_METHOD_INFO, Option.RETAIN_CLASS_REFERENCE));
      *     public ResourceBundle getResourceBundle(String bundleName) {
      *         Class caller = walker.getCallerClass();
      *         return ResourceBundle.getBundle(bundleName, Locale.getDefault(), caller.getClassLoader());
@@ -653,18 +608,18 @@ public final class StackWalker {
      *         ResourceBundle rb = util.getResourceBundle("mybundle");
      *     }
      * }
-     * }
+ * } * * An equivalent way to find the caller class using the * {@link StackWalker#walk walk} method is as follows * (filtering the reflection frames, {@code MethodHandle} and hidden frames * not shown below): - *
{@code
+     * {@snippet lang="java" :
      *     Optional> caller = walker.walk(s ->
      *         s.map(StackFrame::getDeclaringClass)
      *          .skip(2)
      *          .findFirst());
-     * }
+ * } * * When the {@code getCallerClass} method is called from a method that * is the bottom most frame on the stack, @@ -700,10 +655,12 @@ public final class StackWalker { return newInstance(options, extendedOption, null); } + static StackWalker newInstance(Set