New Stack Trace infrastructure.
2005-03-10 Bryce McKinlay <mckinlay@redhat.com> New Stack Trace infrastructure. * Makefile.am (libgcj0_convenience_la_SOURCES): Add stacktrace.cc. (gnu/gcj/runtime/StackTrace.lo): Removed. (ordinary_java_source_files): Remove obsolete files. (nat_source_files): Remove obsolete files. Add natVMThrowable.cc. * configure.host (fallback_backtrace_h): Set backtrace header for mingw and cygwin targets. * configure.ac: Make symlink for fallback backtrace headers. * Makefile.in, configure: Rebuilt. * defineclass.cc (_Jv_ClassReader::read_one_code_attribute): Read 'LineNumberTable' attribute. (_Jv_ClassReader::read_one_class_attribute): Read 'SourceFile' attribute. (_Jv_ClassReader::handleCodeAttribute): Initialize method line table fields. * exception.cc: Remove unused include. * interpret.cc (DIRECT_THREADED, insn_slot): Moved to java-interp.h. (SAVE_PC): New macro. Save current PC in the interpreter frame. (NULLCHECK, NULLARRAYCHECK): Use SAVE_PC. (_Jv_InterpMethod::compile): Translate bytecode PC values in the line table to direct threaded instruction values. (_Jv_StartOfInterpreter, _Jv_EndOfInterpreter): Removed. (_Jv_InterpMethod::run): No longer member function. All callers updated. Remove _Unwind calls. Call SAVE_PC whenever a call is made or where an instruction could throw. (_Jv_InterpMethod::get_source_line): New. Look up source line numbers in line_table. * prims.cc (catch_segv): Construct exception after MAKE_THROW_FRAME. (catch_fpe): Likewise. * stacktrace.cc: New file. Stack trace code now here. * gnu/gcj/runtime/MethodRef.java: * gnu/gcj/runtime/NameFinder.java: Mostly reimplemented. Now simply calls addr2line to look up PC addresses in a given binary or shared library. * gnu/gcj/runtime/StackTrace.java, gnu/gcj/runtime/natNameFinder.cc, gnu/gcj/runtime/natStackTrace.cc: Removed. * gnu/java/lang/MainThread.java (call_main): Add comment warning that this function name is specially recognised by the stack trace code and shouldn't be changed. * include/java-interp.h (DIRECT_THREADED, insn_slot): Moved here. (struct _Jv_LineTableEntry, line_table, line_table_len): New. (_Jv_InterpMethod::run): Update declaration. (_Jv_StackTrace_): New friend. NameFinder and StackTrace no longer friends. (_Jv_InterpFrame): Renamed from _Jv_MethodChain. Add PC field. * include/java-stack.h: New file. Declarations for stack tracing. * include/jvm.h (_Jv_Frame_info): Removed. * java/lang/Class.h: Update friend declarations. * java/lang/VMClassLoader.java (getSystemClassLoader): Simplify exception message. * java/lang/VMThrowable.java (fillInStackTrace): Now native. (getStackTrace): Now native. (data): New RawDataManaged field. * java/lang/natClass.cc: Update includes. (forName): Use _Jv_StackTrace::GetCallingClass for calling-classloader check. (getClassLoader): Likewise. * java/lang/natRuntime.cc: Update includes. (_load): Use _Jv_StackTrace::GetFirstNonSystemClassLoader. * java/lang/natVMSecurityManager.cc: Update includes. (getClassContext): Use _Jv_StackTrace::GetClassContext. * java/lang/natVMThrowable.cc: New file. Native methods for VMThrowable. * java/lang/reflect/natArray.cc: Update includes. (newInstance): Use _Jv_StackTrace::GetCallingClass to implement accessibility check. * java/lang/reflect/natConstructor.cc: Update includes. (newInstance): Use _Jv_StackTrace::GetCallingClass to implement accessibility check. * java/lang/reflect/natField.cc: Update includes. (getAddr): Use _Jv_StackTrace::GetCallingClass to implement accessibility check. * java/lang/reflect/natMethod.cc: Update includes. (invoke): Use _Jv_StackTrace::GetCallingClass to implement accessibility check. * java/util/natResourceBundle.cc: Update includes. (getCallingClassLoader): Use _Jv_StackTrace::GetCallingClass. * java/util/logging/natLogger.cc: Update includes. Use _Jv_StackTrace::GetCallerInfo to get call-site info. * sysdep/generic/backtrace.h: Fallback backtrace code. Stub implementation. * sysdep/i386/backtrace.h: New. Fallback backtrace code. i386 implementation. From-SVN: r96253
This commit is contained in:
parent
ca1593fda4
commit
18744d9b72
32 changed files with 1174 additions and 832 deletions
|
@ -13,11 +13,6 @@ details. */
|
|||
#include <config.h>
|
||||
#include <platform.h>
|
||||
|
||||
// Define this to get the direct-threaded interpreter. If undefined,
|
||||
// we revert to a basic bytecode interpreter. The former is faster
|
||||
// but uses more memory.
|
||||
#define DIRECT_THREADED
|
||||
|
||||
#pragma implementation "java-interp.h"
|
||||
|
||||
#include <jvm.h>
|
||||
|
@ -83,26 +78,6 @@ void _Jv_InitInterpreter() {}
|
|||
|
||||
extern "C" double __ieee754_fmod (double,double);
|
||||
|
||||
// This represents a single slot in the "compiled" form of the
|
||||
// bytecode.
|
||||
union insn_slot
|
||||
{
|
||||
// Address of code.
|
||||
void *insn;
|
||||
// An integer value used by an instruction.
|
||||
jint int_val;
|
||||
// A pointer value used by an instruction.
|
||||
void *datum;
|
||||
};
|
||||
|
||||
// The type of the PC depends on whether we're doing direct threading
|
||||
// or a more ordinary bytecode interpreter.
|
||||
#ifdef DIRECT_THREADED
|
||||
typedef insn_slot *pc_t;
|
||||
#else
|
||||
typedef unsigned char *pc_t;
|
||||
#endif
|
||||
|
||||
static inline void dupx (_Jv_word *sp, int n, int x)
|
||||
{
|
||||
// first "slide" n+x elements n to the right
|
||||
|
@ -117,7 +92,6 @@ static inline void dupx (_Jv_word *sp, int n, int x)
|
|||
{
|
||||
sp[top-(n+x)-i] = sp[top-i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Used to convert from floating types to integral types.
|
||||
|
@ -248,15 +222,16 @@ static jint get4(unsigned char* loc) {
|
|||
| (((jint)(loc[3])) << 0);
|
||||
}
|
||||
|
||||
#define SAVE_PC() frame_desc.pc = pc
|
||||
|
||||
#ifdef HANDLE_SEGV
|
||||
#define NULLCHECK(X)
|
||||
#define NULLARRAYCHECK(X)
|
||||
#define NULLCHECK(X) SAVE_PC()
|
||||
#define NULLARRAYCHECK(X) SAVE_PC()
|
||||
#else
|
||||
#define NULLCHECK(X) \
|
||||
do { if ((X)==NULL) throw_null_pointer_exception (); } while (0)
|
||||
do { SAVE_PC(); if ((X)==NULL) throw_null_pointer_exception (); } while (0)
|
||||
#define NULLARRAYCHECK(X) \
|
||||
do { if ((X)==NULL) { throw_null_pointer_exception (); } } while (0)
|
||||
do { SAVE_PC(); if ((X)==NULL) { throw_null_pointer_exception (); } } while (0)
|
||||
#endif
|
||||
|
||||
#define ARRAYBOUNDSCHECK(array, index) \
|
||||
|
@ -274,7 +249,7 @@ _Jv_InterpMethod::run_normal (ffi_cif *,
|
|||
void* __this)
|
||||
{
|
||||
_Jv_InterpMethod *_this = (_Jv_InterpMethod *) __this;
|
||||
_this->run (ret, args);
|
||||
run (ret, args, _this);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -288,7 +263,7 @@ _Jv_InterpMethod::run_synch_object (ffi_cif *,
|
|||
jobject rcv = (jobject) args[0].ptr;
|
||||
JvSynchronize mutex (rcv);
|
||||
|
||||
_this->run (ret, args);
|
||||
run (ret, args, _this);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -299,7 +274,7 @@ _Jv_InterpMethod::run_class (ffi_cif *,
|
|||
{
|
||||
_Jv_InterpMethod *_this = (_Jv_InterpMethod *) __this;
|
||||
_Jv_InitClass (_this->defining_class);
|
||||
_this->run (ret, args);
|
||||
run (ret, args, _this);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -314,7 +289,7 @@ _Jv_InterpMethod::run_synch_class (ffi_cif *,
|
|||
_Jv_InitClass (sync);
|
||||
JvSynchronize mutex (sync);
|
||||
|
||||
_this->run (ret, args);
|
||||
run (ret, args, _this);
|
||||
}
|
||||
|
||||
#ifdef DIRECT_THREADED
|
||||
|
@ -783,29 +758,23 @@ _Jv_InterpMethod::compile (const void * const *insn_targets)
|
|||
exc[i].handler_type.p = handler;
|
||||
}
|
||||
|
||||
// Translate entries in the LineNumberTable from bytecode PC's to direct
|
||||
// threaded interpreter instruction values.
|
||||
for (int i = 0; i < line_table_len; i++)
|
||||
{
|
||||
int byte_pc = line_table[i].bytecode_pc;
|
||||
line_table[i].pc = &insns[pc_mapping[byte_pc]];
|
||||
}
|
||||
|
||||
prepared = insns;
|
||||
}
|
||||
#endif /* DIRECT_THREADED */
|
||||
|
||||
// These exist so that the stack-tracing code can find the boundaries
|
||||
// of the interpreter.
|
||||
void *_Jv_StartOfInterpreter;
|
||||
void *_Jv_EndOfInterpreter;
|
||||
extern "C" void *_Unwind_FindEnclosingFunction (void *pc);
|
||||
|
||||
void
|
||||
_Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
||||
_Jv_InterpMethod::run (void *retp, ffi_raw *args, _Jv_InterpMethod *meth)
|
||||
{
|
||||
using namespace java::lang::reflect;
|
||||
|
||||
// Record the address of the start of this member function in
|
||||
// _Jv_StartOfInterpreter. Such a write to a global variable
|
||||
// without acquiring a lock is correct iff reads and writes of words
|
||||
// in memory are atomic, but Java requires that anyway.
|
||||
foo:
|
||||
if (_Jv_StartOfInterpreter == NULL)
|
||||
_Jv_StartOfInterpreter = _Unwind_FindEnclosingFunction (&&foo);
|
||||
|
||||
// FRAME_DESC registers this particular invocation as the top-most
|
||||
// interpreter frame. This lets the stack tracing code (for
|
||||
// Throwable) print information about the method being interpreted
|
||||
|
@ -813,20 +782,20 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
// destructor so it cleans up automatically when the interpreter
|
||||
// returns.
|
||||
java::lang::Thread *thread = java::lang::Thread::currentThread();
|
||||
_Jv_MethodChain frame_desc (this,
|
||||
(_Jv_MethodChain **) &thread->interp_frame);
|
||||
_Jv_InterpFrame frame_desc (meth,
|
||||
(_Jv_InterpFrame **) &thread->interp_frame);
|
||||
|
||||
_Jv_word stack[max_stack];
|
||||
_Jv_word stack[meth->max_stack];
|
||||
_Jv_word *sp = stack;
|
||||
|
||||
_Jv_word locals[max_locals];
|
||||
_Jv_word locals[meth->max_locals];
|
||||
|
||||
/* Go straight at it! the ffi raw format matches the internal
|
||||
stack representation exactly. At least, that's the idea.
|
||||
*/
|
||||
memcpy ((void*) locals, (void*) args, args_raw_size);
|
||||
memcpy ((void*) locals, (void*) args, meth->args_raw_size);
|
||||
|
||||
_Jv_word *pool_data = defining_class->constants.data;
|
||||
_Jv_word *pool_data = meth->defining_class->constants.data;
|
||||
|
||||
/* These three are temporaries for common code used by several
|
||||
instructions. */
|
||||
|
@ -1068,14 +1037,14 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
#define AMPAMP(label) &&label
|
||||
|
||||
// Compile if we must. NOTE: Double-check locking.
|
||||
if (prepared == NULL)
|
||||
if (meth->prepared == NULL)
|
||||
{
|
||||
_Jv_MutexLock (&compile_mutex);
|
||||
if (prepared == NULL)
|
||||
compile (insn_target);
|
||||
if (meth->prepared == NULL)
|
||||
meth->compile (insn_target);
|
||||
_Jv_MutexUnlock (&compile_mutex);
|
||||
}
|
||||
pc = (insn_slot *) prepared;
|
||||
pc = (insn_slot *) meth->prepared;
|
||||
|
||||
#else
|
||||
|
||||
|
@ -1132,7 +1101,8 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
* the corresponding bit JV_CONSTANT_ResolvedFlag in the tag
|
||||
* directly. For now, I don't think it is worth it. */
|
||||
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
SAVE_PC();
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).rmethod;
|
||||
|
||||
sp -= rmeth->stack_item_count;
|
||||
|
@ -1140,7 +1110,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
// working if the method is final. So instead we do an
|
||||
// explicit test.
|
||||
if (! sp[0].o)
|
||||
throw new java::lang::NullPointerException;
|
||||
{
|
||||
//printf("invokevirtual pc = %p/%i\n", pc, meth->get_pc_val(pc));
|
||||
throw new java::lang::NullPointerException;
|
||||
}
|
||||
|
||||
if (rmeth->vtable_index == -1)
|
||||
{
|
||||
|
@ -1173,7 +1146,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
// working if the method is final. So instead we do an
|
||||
// explicit test.
|
||||
if (! sp[0].o)
|
||||
throw new java::lang::NullPointerException;
|
||||
{
|
||||
SAVE_PC();
|
||||
throw new java::lang::NullPointerException;
|
||||
}
|
||||
|
||||
if (rmeth->vtable_index == -1)
|
||||
{
|
||||
|
@ -1193,6 +1169,8 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
|
||||
perform_invoke:
|
||||
{
|
||||
SAVE_PC();
|
||||
|
||||
/* here goes the magic again... */
|
||||
ffi_cif *cif = &rmeth->cif;
|
||||
ffi_raw *raw = (ffi_raw*) sp;
|
||||
|
@ -2423,7 +2401,8 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_getstatic:
|
||||
{
|
||||
jint fieldref_index = GET2U ();
|
||||
_Jv_Linker::resolve_pool_entry (defining_class, fieldref_index);
|
||||
SAVE_PC(); // Constant pool resolution could throw.
|
||||
_Jv_Linker::resolve_pool_entry (meth->defining_class, fieldref_index);
|
||||
_Jv_Field *field = pool_data[fieldref_index].field;
|
||||
|
||||
if ((field->flags & Modifier::STATIC) == 0)
|
||||
|
@ -2510,7 +2489,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_getfield:
|
||||
{
|
||||
jint fieldref_index = GET2U ();
|
||||
_Jv_Linker::resolve_pool_entry (defining_class, fieldref_index);
|
||||
_Jv_Linker::resolve_pool_entry (meth->defining_class, fieldref_index);
|
||||
_Jv_Field *field = pool_data[fieldref_index].field;
|
||||
|
||||
if ((field->flags & Modifier::STATIC) != 0)
|
||||
|
@ -2626,7 +2605,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_putstatic:
|
||||
{
|
||||
jint fieldref_index = GET2U ();
|
||||
_Jv_Linker::resolve_pool_entry (defining_class, fieldref_index);
|
||||
_Jv_Linker::resolve_pool_entry (meth->defining_class, fieldref_index);
|
||||
_Jv_Field *field = pool_data[fieldref_index].field;
|
||||
|
||||
jclass type = field->type;
|
||||
|
@ -2713,7 +2692,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_putfield:
|
||||
{
|
||||
jint fieldref_index = GET2U ();
|
||||
_Jv_Linker::resolve_pool_entry (defining_class, fieldref_index);
|
||||
_Jv_Linker::resolve_pool_entry (meth->defining_class, fieldref_index);
|
||||
_Jv_Field *field = pool_data[fieldref_index].field;
|
||||
|
||||
jclass type = field->type;
|
||||
|
@ -2839,7 +2818,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
{
|
||||
int index = GET2U ();
|
||||
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).rmethod;
|
||||
|
||||
sp -= rmeth->stack_item_count;
|
||||
|
@ -2847,7 +2826,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
// We don't use NULLCHECK here because we can't rely on that
|
||||
// working for <init>. So instead we do an explicit test.
|
||||
if (! sp[0].o)
|
||||
throw new java::lang::NullPointerException;
|
||||
{
|
||||
SAVE_PC();
|
||||
throw new java::lang::NullPointerException;
|
||||
}
|
||||
|
||||
fun = (void (*)()) rmeth->method->ncode;
|
||||
|
||||
|
@ -2868,7 +2850,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
// We don't use NULLCHECK here because we can't rely on that
|
||||
// working for <init>. So instead we do an explicit test.
|
||||
if (! sp[0].o)
|
||||
throw new java::lang::NullPointerException;
|
||||
{
|
||||
SAVE_PC();
|
||||
throw new java::lang::NullPointerException;
|
||||
}
|
||||
fun = (void (*)()) rmeth->method->ncode;
|
||||
}
|
||||
goto perform_invoke;
|
||||
|
@ -2878,7 +2863,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
{
|
||||
int index = GET2U ();
|
||||
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).rmethod;
|
||||
|
||||
sp -= rmeth->stack_item_count;
|
||||
|
@ -2908,7 +2893,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
{
|
||||
int index = GET2U ();
|
||||
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
rmeth = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).rmethod;
|
||||
|
||||
sp -= rmeth->stack_item_count;
|
||||
|
@ -2952,7 +2937,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_new:
|
||||
{
|
||||
int index = GET2U ();
|
||||
jclass klass = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
jclass klass = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).clazz;
|
||||
jobject res = _Jv_AllocObject (klass);
|
||||
PUSHA (res);
|
||||
|
@ -2986,7 +2971,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
insn_anewarray:
|
||||
{
|
||||
int index = GET2U ();
|
||||
jclass klass = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
jclass klass = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).clazz;
|
||||
int size = POPI();
|
||||
jobject result = _Jv_NewObjectArray (size, klass, 0);
|
||||
|
@ -3027,9 +3012,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
|
||||
insn_checkcast:
|
||||
{
|
||||
SAVE_PC();
|
||||
jobject value = POPA();
|
||||
jint index = GET2U ();
|
||||
jclass to = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
jclass to = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).clazz;
|
||||
|
||||
if (value != NULL && ! to->isInstance (value))
|
||||
|
@ -3047,6 +3033,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
#ifdef DIRECT_THREADED
|
||||
checkcast_resolved:
|
||||
{
|
||||
SAVE_PC();
|
||||
jobject value = POPA ();
|
||||
jclass to = (jclass) AVAL ();
|
||||
if (value != NULL && ! to->isInstance (value))
|
||||
|
@ -3058,9 +3045,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
|
||||
insn_instanceof:
|
||||
{
|
||||
SAVE_PC();
|
||||
jobject value = POPA();
|
||||
jint index = GET2U ();
|
||||
jclass to = (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
jclass to = (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
index)).clazz;
|
||||
PUSHI (to->isInstance (value));
|
||||
|
||||
|
@ -3123,7 +3111,7 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
int dim = GET1U ();
|
||||
|
||||
jclass type
|
||||
= (_Jv_Linker::resolve_pool_entry (defining_class,
|
||||
= (_Jv_Linker::resolve_pool_entry (meth->defining_class,
|
||||
kind_index)).clazz;
|
||||
jint *sizes = (jint*) __builtin_alloca (sizeof (jint)*dim);
|
||||
|
||||
|
@ -3212,10 +3200,10 @@ _Jv_InterpMethod::run (void *retp, ffi_raw *args)
|
|||
#else
|
||||
int logical_pc = pc - 1 - bytecode ();
|
||||
#endif
|
||||
_Jv_InterpException *exc = exceptions ();
|
||||
_Jv_InterpException *exc = meth->exceptions ();
|
||||
jclass exc_class = ex->getClass ();
|
||||
|
||||
for (int i = 0; i < exc_count; i++)
|
||||
for (int i = 0; i < meth->exc_count; i++)
|
||||
{
|
||||
if (PCVAL (exc[i].start_pc) <= logical_pc
|
||||
&& logical_pc < PCVAL (exc[i].end_pc))
|
||||
|
@ -3272,6 +3260,21 @@ throw_null_pointer_exception ()
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Look up source code line number for given bytecode (or direct threaded
|
||||
interpreter) PC. */
|
||||
int
|
||||
_Jv_InterpMethod::get_source_line(pc_t mpc)
|
||||
{
|
||||
int line = line_table_len > 0 ? line_table[0].line : -1;
|
||||
for (int i = 1; i < line_table_len; i++)
|
||||
if (line_table[i].pc > mpc)
|
||||
break;
|
||||
else
|
||||
line = line_table[i].line;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/** Do static initialization for fields with a constant initializer */
|
||||
void
|
||||
_Jv_InitField (jobject obj, jclass klass, int index)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue