This commit is contained in:
Jesper Wilhelmsson 2022-07-06 21:01:10 +00:00
commit 2a6ec88cd0
24 changed files with 833 additions and 192 deletions

View File

@ -230,30 +230,41 @@ frame frame::sender_for_interpreter_frame(RegisterMap *map) const {
return frame(sender_sp(), sender_pc(), (intptr_t*)(ijava_state()->sender_sp));
}
intptr_t* frame::compiled_sender_sp(CodeBlob* cb) const {
return sender_sp();
}
address* frame::compiled_sender_pc_addr(CodeBlob* cb) const {
return sender_pc_addr();
}
void frame::patch_pc(Thread* thread, address pc) {
assert(_cb == CodeCache::find_blob(pc), "unexpected pc");
address* pc_addr = (address*)&(own_abi()->return_pc);
if (TracePcPatching) {
tty->print_cr("patch_pc at address " PTR_FORMAT " [" PTR_FORMAT " -> " PTR_FORMAT "] ",
p2i(&((address*) _sp)[-1]), p2i(((address*) _sp)[-1]), p2i(pc));
}
assert(!Continuation::is_return_barrier_entry(*pc_addr), "return barrier");
assert(_pc == *pc_addr || pc == *pc_addr || 0 == *pc_addr,
"must be (pc: " INTPTR_FORMAT " _pc: " INTPTR_FORMAT " pc_addr: " INTPTR_FORMAT
" *pc_addr: " INTPTR_FORMAT " sp: " INTPTR_FORMAT ")",
p2i(pc), p2i(_pc), p2i(pc_addr), p2i(*pc_addr), p2i(sp()));
DEBUG_ONLY(address old_pc = _pc;)
own_abi()->return_pc = (uint64_t)pc;
_pc = pc; // must be set before call to get_deopt_original_pc
address original_pc = CompiledMethod::get_deopt_original_pc(this);
if (original_pc != NULL) {
assert(original_pc == _pc, "expected original to be stored before patching");
// assert(original_pc == _pc, "expected original to be stored before patching");
_deopt_state = is_deoptimized;
// Leave _pc as is.
_pc = original_pc;
} else {
_deopt_state = not_deoptimized;
_pc = pc;
}
assert(!is_compiled_frame() || !_cb->as_compiled_method()->is_deopt_entry(_pc), "must be");
#ifdef ASSERT
{
frame f(this->sp(), pc, this->unextended_sp());
assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(),
"must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d "
"f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")",
f.is_deoptimized_frame(), this->is_deoptimized_frame(), p2i(f.pc()), p2i(this->pc()), p2i(f.raw_pc()), p2i(this->raw_pc()));
}
#endif
}
bool frame::is_interpreted_frame_valid(JavaThread* thread) const {

View File

@ -460,15 +460,15 @@
private:
inline void find_codeblob_and_set_pc_and_deopt_state(address pc);
// Initialize frame members (_pc and _sp must be given)
inline void setup();
const ImmutableOopMap* get_oop_map() const;
// Constructors
public:
// To be used, if sp was not extended to match callee's calling convention.
inline frame(intptr_t* sp, address pc);
inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp);
inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr);
// Access frame via stack pointer.
inline intptr_t* sp_addr_at(int index) const { return &sp()[index]; }
@ -479,10 +479,6 @@
inline z_abi_160* callers_abi() const { return (z_abi_160*) fp(); }
private:
intptr_t* compiled_sender_sp(CodeBlob* cb) const;
address* compiled_sender_pc_addr(CodeBlob* cb) const;
address* sender_pc_addr(void) const;
public:

View File

@ -28,71 +28,71 @@
#include "code/codeCache.hpp"
#include "code/vmreg.inline.hpp"
#include "runtime/sharedRuntime.hpp"
#include "utilities/align.hpp"
// Inline functions for z/Architecture frames:
inline void frame::find_codeblob_and_set_pc_and_deopt_state(address pc) {
assert(pc != NULL, "precondition: must have PC");
_cb = CodeCache::find_blob(pc);
_pc = pc; // Must be set for get_deopt_original_pc().
_fp = (intptr_t *) own_abi()->callers_sp;
address original_pc = CompiledMethod::get_deopt_original_pc(this);
if (original_pc != NULL) {
_pc = original_pc;
_deopt_state = is_deoptimized;
} else {
_deopt_state = not_deoptimized;
// Initialize frame members (_sp must be given)
inline void frame::setup() {
if (_pc == nullptr) {
_pc = (address)own_abi()->return_pc;
assert(_pc != nullptr, "must have PC");
}
assert(((uint64_t)_sp & 0x7) == 0, "SP must be 8-byte aligned");
if (_cb == nullptr) {
_cb = CodeCache::find_blob(_pc);
}
if (_fp == nullptr) {
_fp = (intptr_t*)own_abi()->callers_sp;
}
if (_unextended_sp == nullptr) {
_unextended_sp = _sp;
}
// When thawing continuation frames the _unextended_sp passed to the constructor is not aligend
assert(_on_heap || (is_aligned(_sp, alignment_in_bytes) && is_aligned(_fp, alignment_in_bytes)),
"invalid alignment sp:" PTR_FORMAT " unextended_sp:" PTR_FORMAT " fp:" PTR_FORMAT, p2i(_sp), p2i(_unextended_sp), p2i(_fp));
address original_pc = CompiledMethod::get_deopt_original_pc(this);
if (original_pc != nullptr) {
_pc = original_pc;
_deopt_state = is_deoptimized;
assert(_cb == nullptr || _cb->as_compiled_method()->insts_contains_inclusive(_pc),
"original PC must be in the main code section of the the compiled method (or must be immediately following it)");
} else {
if (_cb == SharedRuntime::deopt_blob()) {
_deopt_state = is_deoptimized;
} else {
_deopt_state = not_deoptimized;
}
}
// assert(_on_heap || is_aligned(_sp, frame::frame_alignment), "SP must be 8-byte aligned");
}
// Constructors
// Initialize all fields, _unextended_sp will be adjusted in find_codeblob_and_set_pc_and_deopt_state.
inline frame::frame() : _sp(NULL), _pc(NULL), _cb(NULL), _deopt_state(unknown), _on_heap(false),
#ifdef ASSERT
_frame_index(-1),
#endif
_unextended_sp(NULL), _fp(NULL) {}
// Initialize all fields
inline frame::frame() : _sp(nullptr), _pc(nullptr), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(nullptr) {}
inline frame::frame(intptr_t* sp) : _sp(sp), _on_heap(false),
#ifdef ASSERT
_frame_index(-1),
#endif
_unextended_sp(sp) {
find_codeblob_and_set_pc_and_deopt_state((address)own_abi()->return_pc);
inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* fp, CodeBlob* cb)
: _sp(sp), _pc(pc), _cb(cb), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) {
setup();
}
inline frame::frame(intptr_t* sp, address pc) : _sp(sp), _on_heap(false),
#ifdef ASSERT
_frame_index(-1),
#endif
_unextended_sp(sp) {
find_codeblob_and_set_pc_and_deopt_state(pc); // Also sets _fp and adjusts _unextended_sp.
}
inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp) : _sp(sp), _on_heap(false),
#ifdef ASSERT
_frame_index(-1),
#endif
_unextended_sp(unextended_sp) {
find_codeblob_and_set_pc_and_deopt_state(pc); // Also sets _fp and adjusts _unextended_sp.
}
inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {}
// Generic constructor. Used by pns() in debug.cpp only
#ifndef PRODUCT
inline frame::frame(void* sp, void* pc, void* unextended_sp) :
_sp((intptr_t*)sp), _pc(NULL), _cb(NULL), _on_heap(false),
#ifdef ASSERT
_frame_index(-1),
#endif
_unextended_sp((intptr_t*)unextended_sp) {
find_codeblob_and_set_pc_and_deopt_state((address)pc); // Also sets _fp and adjusts _unextended_sp.
inline frame::frame(void* sp, void* pc, void* unextended_sp)
: _sp((intptr_t*)sp), _pc((address)pc), _cb(nullptr), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp((intptr_t*)unextended_sp) {
setup();
}
#endif
@ -304,7 +304,16 @@ inline intptr_t* frame::real_fp() const {
}
inline const ImmutableOopMap* frame::get_oop_map() const {
Unimplemented();
if (_cb == NULL) return NULL;
if (_cb->oop_maps() != NULL) {
NativePostCallNop* nop = nativePostCallNop_at(_pc);
if (nop != NULL && nop->displacement() != 0) {
int slot = ((nop->displacement() >> 24) & 0xff);
return _cb->oop_map_for_slot(slot, _pc);
}
const ImmutableOopMap* oop_map = OopMapSet::find_map(this);
return oop_map;
}
return NULL;
}
@ -355,7 +364,7 @@ inline frame frame::sender(RegisterMap* map) const {
return sender_for_interpreter_frame(map);
}
assert(_cb == CodeCache::find_blob(pc()),"Must be the same");
if (_cb != NULL) return sender_for_compiled_frame(map);
if (_cb != nullptr) return sender_for_compiled_frame(map);
// Must be native-compiled frame, i.e. the marshaling code for native
// methods that exists in the core system.
@ -363,24 +372,21 @@ inline frame frame::sender(RegisterMap* map) const {
}
inline frame frame::sender_for_compiled_frame(RegisterMap *map) const {
assert(map != NULL, "map must be set");
// Frame owned by compiler.
assert(map != nullptr, "map must be set");
address pc = *compiled_sender_pc_addr(_cb);
frame caller(compiled_sender_sp(_cb), pc);
intptr_t* sender_sp = this->sender_sp();
address sender_pc = this->sender_pc();
// Now adjust the map.
// Get the rest.
if (map->update_map()) {
// Tell GC to use argument oopmaps for some runtime stubs that need it.
map->set_include_argument_oops(_cb->caller_must_gc_arguments(map->thread()));
if (_cb->oop_maps() != NULL) {
if (_cb->oop_maps() != nullptr) {
OopMapSet::update_register_map(this, map);
}
}
return caller;
return frame(sender_sp, sender_pc);
}
template <typename RegisterMapT>

View File

@ -657,13 +657,13 @@ class NativeGeneralJump: public NativeInstruction {
class NativePostCallNop: public NativeInstruction {
public:
bool check() const { Unimplemented(); return false; }
int displacement() const { Unimplemented(); return 0; }
int displacement() const { return 0; }
void patch(jint diff) { Unimplemented(); }
void make_deopt() { Unimplemented(); }
};
inline NativePostCallNop* nativePostCallNop_at(address address) {
Unimplemented();
// Unimplemented();
return NULL;
}
@ -675,7 +675,7 @@ public:
void verify() { Unimplemented(); }
static bool is_deopt_at(address instr) {
Unimplemented();
// Unimplemented();
return false;
}

View File

@ -2857,6 +2857,44 @@ class StubGenerator: public StubCodeGenerator {
return start;
}
RuntimeStub* generate_cont_doYield() {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
address generate_cont_thaw(bool return_barrier, bool exception) {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
address generate_cont_thaw() {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
address generate_cont_returnBarrier() {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
address generate_cont_returnBarrier_exception() {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
#if INCLUDE_JFR
RuntimeStub* generate_jfr_write_checkpoint() {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return nullptr;
}
#endif // INCLUD_JFR
void generate_initial() {
// Generates all stubs and initializes the entry points.
@ -2895,6 +2933,20 @@ class StubGenerator: public StubCodeGenerator {
StubRoutines::zarch::_trot_table_addr = (address)StubRoutines::zarch::_trot_table;
}
void generate_phase1() {
if (!Continuations::enabled()) return;
// Continuation stubs:
StubRoutines::_cont_thaw = generate_cont_thaw();
StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier();
StubRoutines::_cont_returnBarrierExc = generate_cont_returnBarrier_exception();
StubRoutines::_cont_doYield_stub = generate_cont_doYield();
StubRoutines::_cont_doYield = StubRoutines::_cont_doYield_stub == nullptr ? nullptr
: StubRoutines::_cont_doYield_stub->entry_point();
JFR_ONLY(StubRoutines::_jfr_write_checkpoint_stub = generate_jfr_write_checkpoint();)
JFR_ONLY(StubRoutines::_jfr_write_checkpoint = StubRoutines::_jfr_write_checkpoint_stub->entry_point();)
}
void generate_all() {
// Generates all stubs and initializes the entry points.
@ -2971,12 +3023,14 @@ class StubGenerator: public StubCodeGenerator {
}
public:
StubGenerator(CodeBuffer* code, bool all) : StubCodeGenerator(code) {
_stub_count = !all ? 0x100 : 0x200;
if (all) {
generate_all();
} else {
StubGenerator(CodeBuffer* code, int phase) : StubCodeGenerator(code) {
_stub_count = (phase == 0) ? 0x100 : 0x200;
if (phase == 0) {
generate_initial();
} else if (phase == 1) {
generate_phase1(); // stubs that must be available for the interpreter
} else {
generate_all();
}
}

View File

@ -483,6 +483,7 @@ address TemplateInterpreterGenerator::generate_abstract_entry(void) {
}
address TemplateInterpreterGenerator::generate_Continuation_doYield_entry(void) {
if (!Continuations::enabled()) return nullptr;
Unimplemented();
return NULL;
}

View File

@ -165,7 +165,6 @@ Node* compress_expand_identity(PhaseGVN* phase, Node* n) {
if(phase->type(n->in(2))->higher_equal(TypeInteger::zero(bt))) return n->in(2);
// compress(x, -1) == x, expand(x, -1) == x
if(phase->type(n->in(2))->higher_equal(TypeInteger::minus_1(bt))) return n->in(1);
return n;
// expand(-1, x) == x
if(n->Opcode() == Op_ExpandBits &&
phase->type(n->in(1))->higher_equal(TypeInteger::minus_1(bt))) return n->in(2);
@ -259,7 +258,9 @@ static const Type* bitshuffle_value(const TypeInteger* src_type, const TypeInteg
} else {
// Case 3) Mask value range only includes +ve values.
assert(mask_type->lo_as_long() >= 0, "");
mask_max_bw = max_bw - count_leading_zeros(mask_type->hi_as_long());
jlong clz = count_leading_zeros(mask_type->hi_as_long());
clz = bt == T_INT ? clz - 32 : clz;
mask_max_bw = max_bw - clz;
}
if ( opc == Op_CompressBits) {
lo = mask_max_bw == max_bw ? lo : 0L;
@ -270,6 +271,8 @@ static const Type* bitshuffle_value(const TypeInteger* src_type, const TypeInteg
} else {
assert(opc == Op_ExpandBits, "");
jlong max_mask = mask_type->hi_as_long();
// Since mask here a range and not a constant value, hence being
// conservative in determining the value range of result.
lo = mask_type->lo_as_long() >= 0L ? 0L : lo;
hi = mask_type->lo_as_long() >= 0L ? max_mask : hi;
}

View File

@ -12892,15 +12892,21 @@ myInit() {
id="ThreadStart" const="JVMTI_EVENT_THREAD_START" num="52" phase="start">
<description>
A thread start event is generated by a new thread before its initial
method executes. The capability
<internallink id="jvmtiCapabilities.can_support_virtual_threads">
<code>can_support_virtual_threads</code></internallink> determines
if a new virtual thread generates a <code>ThreadStart</code> event or
a <eventlink id="VirtualThreadStart"/> event. If disabled, a virtual
thread generates a <code>ThreadStart</code> event. If enabled, a virtual
thread generates a <code>VirtualThreadStart</code> event.
method executes.
<p/>
A thread may be listed in the array returned by
This event is generated by platform threads. It is also generated by
virtual threads when the capability
<internallink id="jvmtiCapabilities.can_support_virtual_threads">
<code>can_support_virtual_threads</code></internallink> is not enabled.
Agents without support for virtual threads that enable this event will
therefore be notified by all newly started threads.
<p/>
If the capability <code>can_support_virtual_threads</code> is enabled then
this event is not generated by virtual threads. Agents with support for
virtual threads can enable <eventlink id="VirtualThreadStart"></eventlink>
to be notified by newly started virtual threads.
<p/>
A platform thread may be listed in the array returned by
<functionlink id="GetAllThreads"></functionlink>
before its thread start event is generated.
It is possible for other events to be generated
@ -12934,21 +12940,26 @@ myInit() {
<description>
A thread end event is generated by a terminating thread after its
initial method has finished execution.
The capability
<internallink id="jvmtiCapabilities.can_support_virtual_threads">
<code>can_support_virtual_threads</code></internallink> determines
if a terminating virtual thread generates a <code>ThreadEnd</code>
event or a <eventlink id="VirtualThreadEnd"/> event. If disabled, a
virtual thread generates a <code>ThreadEnd</code> event. If enabled,
a virtual thread generates a <code>VirtualThreadEnd</code> event.
<p/>
A thread may be listed in the array returned by
This event is generated by platform threads. It is also generated by
virtual threads when the capability
<internallink id="jvmtiCapabilities.can_support_virtual_threads">
<code>can_support_virtual_threads</code></internallink> is not enabled.
Agents without support for virtual threads that enable this event for
all threads will therefore be notified by all terminating threads.
<p/>
If the capability <code>can_support_virtual_threads</code> is enabled then
this event is not generated by virtual threads. Agents with support for
virtual threads can enable <eventlink id="VirtualThreadEnd"></eventlink>
to be notified by terminating virtual threads.
<p/>
A platform thread may be listed in the array returned by
<functionlink id="GetAllThreads"></functionlink>
after its thread end event is generated.
No events are generated on a thread
after its thread end event.
<p/>
The event is sent on the dying <paramlink id="thread"></paramlink>.
The event is sent on the terminating <paramlink id="thread"></paramlink>.
</description>
<origin>jvmdi</origin>
<capabilities>
@ -12980,7 +12991,7 @@ myInit() {
<p/>
A virtual thread start event is generated before its initial method executes.
<p/>
The event is sent on the <paramlink id="virtual_thread"></paramlink>.
The event is sent on the newly started <paramlink id="virtual_thread"></paramlink>.
</description>
<origin>new</origin>
<capabilities>
@ -13013,7 +13024,7 @@ myInit() {
<p/>
A virtual thread end event is generated after its initial method has finished execution.
<p/>
The event is sent on the <paramlink id="virtual_thread"></paramlink>.
The event is sent on the terminating <paramlink id="virtual_thread"></paramlink>.
</description>
<origin>new</origin>
<capabilities>

View File

@ -115,6 +115,7 @@ enum {
JVMTI_VERSION_1_2 = 0x30010200,
JVMTI_VERSION_9 = 0x30090000,
JVMTI_VERSION_11 = 0x300B0000,
JVMTI_VERSION_19 = 0x30130000,
JVMTI_VERSION = 0x30000000 + (</xsl:text>
<xsl:value-of select="$majorversion"/>

View File

@ -160,6 +160,10 @@ void JavaThread::set_threadOopHandles(oop p) {
}
oop JavaThread::threadObj() const {
Thread* current = Thread::current_or_null_safe();
assert(current != nullptr, "cannot be called by a detached thread");
guarantee(current != this || JavaThread::cast(current)->is_oop_safe(),
"current cannot touch oops after its GC barrier is detached.");
return _threadObj.resolve();
}
@ -1510,9 +1514,15 @@ void JavaThread::print_name_on_error(outputStream* st, char *buf, int buflen) co
// JavaThread::print() is that we can't grab lock or allocate memory.
void JavaThread::print_on_error(outputStream* st, char *buf, int buflen) const {
st->print("%s \"%s\"", type_name(), get_thread_name_string(buf, buflen));
oop thread_obj = threadObj();
if (thread_obj != NULL) {
if (java_lang_Thread::is_daemon(thread_obj)) st->print(" daemon");
Thread* current = Thread::current_or_null_safe();
assert(current != nullptr, "cannot be called by a detached thread");
if (!current->is_Java_thread() || JavaThread::cast(current)->is_oop_safe()) {
// Only access threadObj() if current thread is not a JavaThread
// or if it is a JavaThread that can safely access oops.
oop thread_obj = threadObj();
if (thread_obj != nullptr) {
if (java_lang_Thread::is_daemon(thread_obj)) st->print(" daemon");
}
}
st->print(" [");
st->print("%s", _get_thread_state_name(_thread_state));
@ -1571,23 +1581,43 @@ const char* JavaThread::name() const {
// descriptive string if there is no set name.
const char* JavaThread::get_thread_name_string(char* buf, int buflen) const {
const char* name_str;
oop thread_obj = threadObj();
if (thread_obj != NULL) {
oop name = java_lang_Thread::name(thread_obj);
if (name != NULL) {
if (buf == NULL) {
name_str = java_lang_String::as_utf8_string(name);
#ifdef ASSERT
Thread* current = Thread::current_or_null_safe();
assert(current != nullptr, "cannot be called by a detached thread");
if (!current->is_Java_thread() || JavaThread::cast(current)->is_oop_safe()) {
// Only access threadObj() if current thread is not a JavaThread
// or if it is a JavaThread that can safely access oops.
#endif
oop thread_obj = threadObj();
if (thread_obj != NULL) {
oop name = java_lang_Thread::name(thread_obj);
if (name != NULL) {
if (buf == NULL) {
name_str = java_lang_String::as_utf8_string(name);
} else {
name_str = java_lang_String::as_utf8_string(name, buf, buflen);
}
} else if (is_attaching_via_jni()) { // workaround for 6412693 - see 6404306
name_str = "<no-name - thread is attaching>";
} else {
name_str = java_lang_String::as_utf8_string(name, buf, buflen);
name_str = "<un-named>";
}
} else if (is_attaching_via_jni()) { // workaround for 6412693 - see 6404306
name_str = "<no-name - thread is attaching>";
} else {
name_str = "<un-named>";
name_str = Thread::name();
}
#ifdef ASSERT
} else {
name_str = Thread::name();
// Current JavaThread has exited...
if (current == this) {
// ... and is asking about itself:
name_str = "<no-name - current JavaThread has exited>";
} else {
// ... and it can't safely determine this JavaThread's name so
// use the default thread name.
name_str = Thread::name();
}
}
#endif
assert(name_str != NULL, "unexpected NULL thread name");
return name_str;
}

View File

@ -997,9 +997,6 @@ JRT_END
jlong SharedRuntime::get_java_tid(Thread* thread) {
if (thread != NULL && thread->is_Java_thread()) {
Thread* current = Thread::current();
guarantee(current != thread || JavaThread::cast(thread)->is_oop_safe(),
"current cannot touch oops after its GC barrier is detached.");
oop obj = JavaThread::cast(thread)->threadObj();
return (obj == NULL) ? 0 : java_lang_Thread::thread_id(obj);
}

View File

@ -235,14 +235,14 @@ void Fingerprinter::do_type_calling_convention(BasicType type) {
case T_BYTE:
case T_SHORT:
case T_INT:
#if defined(PPC64)
#if defined(PPC64) || defined(S390)
if (_int_args < Argument::n_int_register_parameters_j) {
_int_args++;
} else {
_stack_arg_slots += 1;
}
break;
#endif // defined(PPC64)
#endif // defined(PPC64) || defined(S390)
case T_LONG:
case T_OBJECT:
case T_ARRAY:
@ -251,23 +251,25 @@ void Fingerprinter::do_type_calling_convention(BasicType type) {
_int_args++;
} else {
PPC64_ONLY(_stack_arg_slots = align_up(_stack_arg_slots, 2));
S390_ONLY(_stack_arg_slots = align_up(_stack_arg_slots, 2));
_stack_arg_slots += 2;
}
break;
case T_FLOAT:
#if defined(PPC64)
#if defined(PPC64) || defined(S390)
if (_fp_args < Argument::n_float_register_parameters_j) {
_fp_args++;
} else {
_stack_arg_slots += 1;
}
break;
#endif // defined(PPC64)
#endif // defined(PPC64) || defined(S390)
case T_DOUBLE:
if (_fp_args < Argument::n_float_register_parameters_j) {
_fp_args++;
} else {
PPC64_ONLY(_stack_arg_slots = align_up(_stack_arg_slots, 2));
S390_ONLY(_stack_arg_slots = align_up(_stack_arg_slots, 2));
_stack_arg_slots += 2;
}
break;

View File

@ -504,7 +504,7 @@ public class TagletWriterImpl extends TagletWriter {
excName = htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER,
substituteType));
} else if (exception == null) {
excName = new RawHtml(ch.getExceptionName(throwsTag).toString());
excName = new RawHtml(throwsTag.getExceptionName().toString());
} else if (exception.asType() == null) {
excName = new RawHtml(utils.getFullyQualifiedName(exception));
} else {

View File

@ -48,9 +48,7 @@ import com.sun.source.doctree.ThrowsTree;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
* A taglet that processes {@link ThrowsTree}, which represents
@ -64,14 +62,14 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
@Override
public void inherit(DocFinder.Input input, DocFinder.Output output) {
Utils utils = input.utils;
var utils = input.utils;
Element target;
CommentHelper ch = utils.getCommentHelper(input.element);
var ch = utils.getCommentHelper(input.element);
if (input.tagId == null) {
var tag = (ThrowsTree) input.docTreeInfo.docTree();
target = ch.getException(tag);
input.tagId = target == null
? ch.getExceptionName(tag).getSignature()
? tag.getExceptionName().getSignature()
: utils.getFullyQualifiedName(target);
} else {
target = input.utils.findClass(input.element, input.tagId);
@ -97,20 +95,20 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
@Override
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
Utils utils = writer.configuration().utils;
var utils = writer.configuration().utils;
var executable = (ExecutableElement) holder;
ExecutableType instantiatedType = utils.asInstantiatedMethodType(
writer.getCurrentPageElement(), executable);
List<? extends TypeMirror> thrownTypes = instantiatedType.getThrownTypes();
Map<String, TypeMirror> typeSubstitutions = getSubstitutedThrownTypes(
writer.configuration().utils.typeUtils,
utils.typeUtils,
executable.getThrownTypes(),
thrownTypes);
Map<List<ThrowsTree>, ExecutableElement> tagsMap = new LinkedHashMap<>();
tagsMap.put(utils.getThrowsTrees(executable), executable);
Map<ThrowsTree, ExecutableElement> tagsMap = new LinkedHashMap<>();
utils.getThrowsTrees(executable).forEach(t -> tagsMap.put(t, executable));
Content result = writer.getOutputInstance();
Set<String> alreadyDocumented = new HashSet<>();
result.add(throwsTagsOutput(tagsMap, writer, alreadyDocumented, typeSubstitutions, true));
result.add(throwsTagsOutput(tagsMap, alreadyDocumented, typeSubstitutions, writer));
result.add(inheritThrowsDocumentation(executable, thrownTypes, alreadyDocumented, typeSubstitutions, writer));
result.add(linkToUndocumentedDeclaredExceptions(thrownTypes, alreadyDocumented, writer));
return result;
@ -145,45 +143,48 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
/**
* Returns the generated content for a collection of {@code @throws} tags.
*
* @param throwsTags the collection of tags to be converted
* @param throwsTags the tags to be converted; each tag is mapped to
* a method it appears on
* @param alreadyDocumented the set of exceptions that have already been
* documented and thus must not be documented by
* this method. All exceptions documented by this
* method will be added to this set upon the
* method's return.
* @param writer the taglet-writer used by the doclet
* @param alreadyDocumented the set of exceptions that have already been documented
* @param allowDuplicates {@code true} if we allow duplicate tags to be documented
* @return the generated content for the tags
*/
protected Content throwsTagsOutput(Map<List<ThrowsTree>, ExecutableElement> throwsTags,
TagletWriter writer,
Set<String> alreadyDocumented,
Map<String, TypeMirror> typeSubstitutions,
boolean allowDuplicates) {
Utils utils = writer.configuration().utils;
private Content throwsTagsOutput(Map<ThrowsTree, ExecutableElement> throwsTags,
Set<String> alreadyDocumented,
Map<String, TypeMirror> typeSubstitutions,
TagletWriter writer) {
var utils = writer.configuration().utils;
Content result = writer.getOutputInstance();
for (Entry<List<ThrowsTree>, ExecutableElement> entry : throwsTags.entrySet()) {
var documentedInThisCall = new HashSet<String>();
for (Entry<ThrowsTree, ExecutableElement> entry : throwsTags.entrySet()) {
Element e = entry.getValue();
CommentHelper ch = utils.getCommentHelper(e);
for (ThrowsTree tag : entry.getKey()) {
Element te = ch.getException(tag);
String excName = ch.getExceptionName(tag).toString();
TypeMirror substituteType = typeSubstitutions.get(excName);
if ((!allowDuplicates) &&
(alreadyDocumented.contains(excName) ||
(te != null && alreadyDocumented.contains(utils.getFullyQualifiedName(te, false)))) ||
(substituteType != null && alreadyDocumented.contains(substituteType.toString()))) {
continue;
}
if (alreadyDocumented.isEmpty()) {
result.add(writer.getThrowsHeader());
}
result.add(writer.throwsTagOutput(e, tag, substituteType));
if (substituteType != null) {
alreadyDocumented.add(substituteType.toString());
} else {
alreadyDocumented.add(te != null
? utils.getFullyQualifiedName(te, false)
: excName);
}
var ch = utils.getCommentHelper(e);
ThrowsTree tag = entry.getKey();
Element te = ch.getException(tag);
String excName = tag.getExceptionName().toString();
TypeMirror substituteType = typeSubstitutions.get(excName);
if (alreadyDocumented.contains(excName)
|| (te != null && alreadyDocumented.contains(utils.getFullyQualifiedName(te, false)))
|| (substituteType != null && alreadyDocumented.contains(substituteType.toString()))) {
continue;
}
if (alreadyDocumented.isEmpty() && documentedInThisCall.isEmpty()) {
result.add(writer.getThrowsHeader());
}
result.add(writer.throwsTagOutput(e, tag, substituteType));
if (substituteType != null) {
documentedInThisCall.add(substituteType.toString());
} else {
documentedInThisCall.add(te != null
? utils.getFullyQualifiedName(te, false)
: excName);
}
}
alreadyDocumented.addAll(documentedInThisCall);
return result;
}
@ -205,8 +206,8 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
assert holder.getKind() == ElementKind.CONSTRUCTOR : holder.getKind();
return result;
}
Utils utils = writer.configuration().utils;
Map<List<ThrowsTree>, ExecutableElement> declaredExceptionTags = new LinkedHashMap<>();
var utils = writer.configuration().utils;
Map<ThrowsTree, ExecutableElement> declaredExceptionTags = new LinkedHashMap<>();
for (TypeMirror declaredExceptionType : declaredExceptionTypes) {
var input = new DocFinder.Input(utils, holder, this,
utils.getTypeName(declaredExceptionType, false));
@ -220,14 +221,12 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
if (inheritedDoc.holder == null) {
inheritedDoc.holder = holder;
}
List<ThrowsTree> inheritedTags = inheritedDoc.tagList.stream()
.map(t -> (ThrowsTree) t)
.toList();
declaredExceptionTags.put(inheritedTags, (ExecutableElement) inheritedDoc.holder);
var h = (ExecutableElement) inheritedDoc.holder;
inheritedDoc.tagList.forEach(t -> declaredExceptionTags.put((ThrowsTree) t, h));
}
}
result.add(throwsTagsOutput(declaredExceptionTags, writer, alreadyDocumented,
typeSubstitutions, false));
result.add(throwsTagsOutput(declaredExceptionTags, alreadyDocumented, typeSubstitutions,
writer));
return result;
}
@ -235,7 +234,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Set<String> alreadyDocumented,
TagletWriter writer) {
// TODO: assert declaredExceptionTypes are instantiated
Utils utils = writer.configuration().utils;
var utils = writer.configuration().utils;
Content result = writer.getOutputInstance();
for (TypeMirror declaredExceptionType : declaredExceptionTypes) {
TypeElement te = utils.asTypeElement(declaredExceptionType);

View File

@ -547,10 +547,6 @@ public class CommentHelper {
return dtree.getKind() == SEE ? ((SeeTree)dtree).getReference() : null;
}
public ReferenceTree getExceptionName(ThrowsTree tt) {
return tt.getExceptionName();
}
public IdentifierTree getName(DocTree dtree) {
switch (dtree.getKind()) {
case PARAM:

View File

@ -412,6 +412,12 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread)
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error, "getting vthread state");
}
if ((vthread_state & JVMTI_THREAD_STATE_ALIVE) == 0) {
// Thread not alive so put on otherThreads list instead of runningVThreads.
// It might not have started yet or might have terminated. Either way,
// otherThreads is the place for it.
list = &otherThreads;
}
if (suspendAllCount > 0) {
// Assume the suspendAllCount, just like the regular thread case above.
node->suspendCount = suspendAllCount;
@ -419,7 +425,6 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread)
// If state == 0, then this is a new vthread that has not been started yet.
// Need to suspendOnStart in that case, just like the regular thread case above.
node->suspendOnStart = JNI_TRUE;
list = &otherThreads; // Put on otherThreads list instead of runningVThreads
}
}
if (vthread_state != 0) {

View File

@ -54,7 +54,7 @@ public final class RecordedThread extends RecordedObject {
/**
* Returns the thread ID used by the operating system.
*
* @return The Java thread ID, or {@code -1} if doesn't exist
* @return the OS thread ID, or {@code -1} if doesn't exist
*/
public long getOSThreadId() {
Long l = getTyped("osThreadId", Long.class, -1L);
@ -86,6 +86,8 @@ public final class RecordedThread extends RecordedObject {
* Returns the Java thread ID, or {@code -1} if it's not a Java thread.
*
* @return the Java thread ID, or {@code -1} if it's not a Java thread
*
* @see java.lang.Thread#threadId()
*/
public long getJavaThreadId() {
Long l = getTyped("javaThreadId", Long.class, -1L);
@ -97,7 +99,10 @@ public final class RecordedThread extends RecordedObject {
* reused within the lifespan of the JVM.
* <p>
* See {@link #getJavaThreadId()} for the ID that is returned by
* {@code java.lang.Thread.getId()}
* {@code java.lang.Thread.threadId()}.
* <p>
* See {@link #getOSThreadId()} for the ID that is returned by
* the operating system.
*
* @return a unique ID for the thread
*/

View File

@ -84,8 +84,6 @@ public class SelfSuspendDisablerTest {
resume(t1);
testJvmtiThreadState(t1, RUNNABLE);
suspendAllVirtualThreads();
});

View File

@ -495,7 +495,6 @@ java/lang/invoke/LFCaching/LFMultiThreadCachingTest.java 8151492 generic-
java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-all
java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64
java/lang/invoke/RicochetTest.java 8251969 generic-all
java/lang/CompressExpandTest.java 8287851 generic-all
java/lang/ref/OOMEInReferenceHandler.java 8066859 generic-all
############################################################################

View File

@ -494,6 +494,7 @@ jdk_jdi_sanity = \
com/sun/jdi/ResumeOneThreadTest.java \
com/sun/jdi/RunToExit.java \
com/sun/jdi/SourceNameFilterTest.java \
com/sun/jdi/SuspendAfterDeath.java \
com/sun/jdi/VarargsTest.java \
com/sun/jdi/Vars.java \
com/sun/jdi/redefineMethod/RedefineTest.java \

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2022, 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 id=platform_thread
* @bug 8287847
* @summary Test suspending a platform thread after it has terminated.
* @enablePreview
* @requires vm.continuations
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile SuspendAfterDeath.java
* @run main/othervm SuspendAfterDeath
*/
/**
* @test id=virtual_thread
* @bug 8287847
* @summary Test suspending a virtual thread after it has terminated.
* @enablePreview
* @requires vm.continuations
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile SuspendAfterDeath.java
* @run main/othervm SuspendAfterDeath Virtual
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;
class SuspendAfterDeathTarg {
static final String THREAD_NAME = "duke";
// breakpoint here
static void done() {
}
public static void main(String[] args) throws Exception {
boolean useVirtualThread = ((args.length > 0) && args[0].equals("Virtual"));
Thread thread;
System.out.println("Starting debuggee " + (useVirtualThread ? "virtual" : "platform") + " thread.");
if (useVirtualThread) {
thread = Thread.ofVirtual().name(THREAD_NAME).start(() -> { });
} else {
thread = Thread.ofPlatform().name(THREAD_NAME).start(() -> { });
}
thread.join();
done();
}
}
public class SuspendAfterDeath extends TestScaffold {
private volatile ThreadReference targetThread;
private volatile boolean breakpointReached;
private static boolean useVirtualThread = false;
SuspendAfterDeath() {
super(new String[0]); // no args to pass along to debuggee
}
public static void main(String[] args) throws Exception {
if (args.length == 1) {
if ("Virtual".equals(args[0])) {
useVirtualThread = true; // see connect() below for how this is handled
} else {
throw new RuntimeException("SuspendAfterDeath: invalid argument: " + args[0]);
}
} else if (args.length != 0) {
throw new RuntimeException("SuspendAfterDeath: incorrect number of arguments: " + args.length);
}
new SuspendAfterDeath().startTests();
}
@Override
public void threadDied(ThreadDeathEvent event) {
ThreadReference eventThread = event.thread();
if (eventThread.name().equals(SuspendAfterDeathTarg.THREAD_NAME)) {
System.out.println("Target thread died, thread=" + eventThread +
", state=" + eventThread.status());
targetThread = eventThread;
if (targetThread.status() != ThreadReference.THREAD_STATUS_RUNNING) {
failure("FAILED: wrong state for thread: " + targetThread.status());
}
}
}
@Override
public void breakpointReached(BreakpointEvent event) {
ThreadReference eventThread = event.thread();
System.out.println("Breakpoint, thread=" + eventThread);
if (targetThread == null) {
failure("FAILED: got Breakpoint event before ThreadDeath event.");
} else {
System.out.println("Target thread status at breakpoint: thread=" + targetThread +
", state=" + targetThread.status());
if (targetThread.status() != ThreadReference.THREAD_STATUS_ZOMBIE) {
failure("FAILED: wrong state for thread: " + targetThread.status());
}
breakpointReached = true;
/* Suspend the thread. This is being done after the thread has exited. */
targetThread.suspend();
}
}
@Override
public void connect(String args[]) {
if (useVirtualThread) {
/* Append the "Virtual" argument to the arguments used for the debuggee. */
List<String> argList = new ArrayList(Arrays.asList(args));
argList.add("Virtual");
args = argList.toArray(args);
}
super.connect(args);
}
@Override
protected void runTests() throws Exception {
BreakpointEvent bpe = startToMain("SuspendAfterDeathTarg");
EventRequestManager erm = vm().eventRequestManager();
// listener for ThreadDeathEvent captures reference to the thread
ThreadDeathRequest request1 = erm.createThreadDeathRequest();
request1.enable();
// listener for BreakpointEvent attempts to suspend the thread
ReferenceType targetClass = bpe.location().declaringType();
Location loc = findMethod(targetClass, "done", "()V").location();
BreakpointRequest request2 = erm.createBreakpointRequest(loc);
request2.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request2.enable();
listenUntilVMDisconnect();
if (targetThread == null) {
failure("FAILED: never got ThreadDeath event for target thread.");
}
if (!breakpointReached) {
failure("FAILED: never got Breakpoint event for target thread.");
}
if (!testFailed) {
println("SuspendAfterDeath: passed");
} else {
throw new Exception("SuspendAfterDeath: failed");
}
}
}

View File

@ -66,6 +66,7 @@ abstract public class TestScaffold extends TargetAdapter {
final String[] args;
protected boolean testFailed = false;
protected long startTime;
public static final String OLD_MAIN_THREAD_NAME = "old-m-a-i-n";
static private class ArgInfo {
String targetVMArgs = "";
@ -359,10 +360,10 @@ abstract public class TestScaffold extends TargetAdapter {
}
protected void startUp(String targetName) {
List argList = new ArrayList(Arrays.asList(args));
List<String> argList = new ArrayList(Arrays.asList(args));
argList.add(targetName);
println("run args: " + argList);
connect((String[]) argList.toArray(args));
connect(argList.toArray(args));
waitForVMStart();
}
@ -461,6 +462,9 @@ abstract public class TestScaffold extends TargetAdapter {
if ("Virtual".equals(mainWrapper)) {
argInfo.targetAppCommandLine = TestScaffold.class.getName() + " " + mainWrapper + " ";
argInfo.targetVMArgs += "--enable-preview ";
} else if ("true".equals(System.getProperty("test.enable.preview"))) {
// the test specified @enablePreview.
argInfo.targetVMArgs += "--enable-preview ";
}
for (int i = 0; i < args.length; i++) {
@ -969,6 +973,8 @@ abstract public class TestScaffold extends TargetAdapter {
tg.uncaughtThrowable = error;
}
});
Thread.currentThread().setName(OLD_MAIN_THREAD_NAME);
vthread.setName("main");
vthread.join();
} else if (wrapper.equals("Kernel")) {
MainThreadGroup tg = new MainThreadGroup();

View File

@ -69,7 +69,7 @@ public class TestThrowsTagInheritance extends JavadocTester {
checkExit(Exit.OK);
// The method should not inherit the IOOBE throws tag from the abstract class,
// for now keep keep this bug compatible, should fix this correctly in
// for now keep this bug compatible, should fix this correctly in
// the future.
checkOutput("pkg/Extender.html", false, "java.lang.IndexOutOfBoundsException");
}

View File

@ -0,0 +1,353 @@
/*
* Copyright (c) 2022, 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 8067757
* @library /tools/lib ../../lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestOneToMany
*/
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestOneToMany extends JavadocTester {
public static void main(String... args) throws Exception {
var tester = new TestOneToMany();
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
}
private final ToolBox tb = new ToolBox();
// These tests:
//
// - Use own exceptions to not depend on platform links or a setup with
// the no-platform-links option
// - Enclose files in a package to exercise a typical source layout and
// avoid enumerating separate files in the javadoc command
@Test
public void testUncheckedException(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyRuntimeException extends RuntimeException { }
""", """
package x;
public interface I {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
void m();
}
""", """
package x;
public interface I1 extends I {
@Override
void m() throws MyRuntimeException;
}
""", """
package x;
public class IImpl implements I {
@Override
public void m() throws MyRuntimeException { }
}
""", """
package x;
public class C {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
public void m();
}
""", """
package x;
public class C1 extends C {
@Override
public void m() throws MyRuntimeException { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/IImpl.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/C1.html", true, """
<dl class="notes">
<dt>Overrides:</dt>
<dd><code><a href="C.html#m()">m</a></code>&nbsp;in class&nbsp;<code><a href="C.html" title="class in x">C</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
}
@Test
public void testUncheckedExceptionWithRedundantThrows(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyRuntimeException extends RuntimeException { }
""", """
package x;
public interface I {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
void m() throws MyRuntimeException;
}
""", """
package x;
public interface I1 extends I {
@Override
void m() throws MyRuntimeException;
}
""", """
package x;
public class IImpl implements I {
@Override
public void m() throws MyRuntimeException { }
}
""", """
package x;
public class C {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
public void m() throws MyRuntimeException;
}
""", """
package x;
public class C1 extends C {
@Override
public void m() throws MyRuntimeException { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/IImpl.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/C1.html", true, """
<dl class="notes">
<dt>Overrides:</dt>
<dd><code><a href="C.html#m()">m</a></code>&nbsp;in class&nbsp;<code><a href="C.html" title="class in x">C</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
}
@Test
public void testCheckedException(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyCheckedException extends Exception { }
""", """
package x;
public interface I {
/**
* @throws MyCheckedException if this
* @throws MyCheckedException if that
*/
void m() throws MyCheckedException;
}
""", """
package x;
public interface I1 extends I {
@Override
void m() throws MyCheckedException;
}
""", """
package x;
public class IImpl implements I {
@Override
public void m() throws MyCheckedException { }
}
""", """
package x;
public class C {
/**
* @throws MyCheckedException if this
* @throws MyCheckedException if that
*/
public void m() throws MyCheckedException;
}
""", """
package x;
public class C1 extends C {
@Override
public void m() throws MyCheckedException { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/IImpl.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if this</dd>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if that</dd>
</dl>""");
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if this</dd>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if that</dd>
</dl>""");
checkOutput("x/C1.html", true, """
<dl class="notes">
<dt>Overrides:</dt>
<dd><code><a href="C.html#m()">m</a></code>&nbsp;in class&nbsp;<code><a href="C.html" title="class in x">C</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if this</dd>
<dd><code><a href="MyCheckedException.html" title="class in x">MyCheckedException</a></code> - if that</dd>
</dl>""");
}
@Test
public void testSubExceptionDoubleInheritance(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyException extends Exception { }
""", """
package x;
public class MySubException extends MyException { }
""", """
package x;
public interface I {
/**
* @throws MyException if this
* @throws MySubException if that
*/
void m() throws MyException, MySubException;
}
""", """
package x;
public interface I1 extends I {
@Override
void m() throws MyException, MySubException;
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyException.html" title="class in x">MyException</a></code> - if this</dd>
<dd><code><a href="MySubException.html" title="class in x">MySubException</a></code> - if that</dd>
</dl>""");
}
}