753 lines
23 KiB
C
Raw Normal View History

2025-01-22 16:18:30 +01:00
#ifndef HX_STACK_CONTEXT_H
#define HX_STACK_CONTEXT_H
#include "QuickVec.h"
#ifdef HXCPP_SINGLE_THREADED_APP
#define HX_CTX_GET ::hx::gMainThreadContext
#else
#define HX_CTX_GET ((::hx::StackContext *)::hx::tlsStackContext)
#endif
// Set:
// HXCPP_STACK_LINE if stack line numbers need to be tracked
// HXCPP_STACK_TRACE if stack frames need to be tracked
// Keep track of lines - more accurate stack traces for exceptions, also
// needed for the debugger
#if (defined(HXCPP_DEBUG) || defined(HXCPP_DEBUGGER)) && !defined(HXCPP_STACK_LINE)
#define HXCPP_STACK_LINE
#endif
// Do we need to keep a stack trace - for basic exception handelling, also needed for the debugger
// At a minimum, you can track the functions calls and nothing else
#if (defined(HXCPP_STACK_LINE) || defined(HXCPP_TELEMETRY) || defined(HXCPP_PROFILER) || defined(HXCPP_DEBUG)) && !defined(HXCPP_STACK_TRACE)
#define HXCPP_STACK_TRACE
#endif
#if defined(HXCPP_STACK_TRACE) && defined(HXCPP_SCRIPTABLE)
#define HXCPP_STACK_SCRIPTABLE
#endif
// HXCPP_DEBUG_HASH == HXCPP_DEBUGGER
// HXCPP_STACK_VARS == HXCPP_DEBUGGER
// HX_STACKFRAME(pos) - tracks position according to define. May be optimized away.
// HX_GC_STACKFRAME(pos) - tracks position according to define, but is never optimized away
// HX_JUST_GC_STACKFRAME - never tracks position, never optimized away
// Setup the _hx_stackframe variable
#ifdef HXCPP_STACK_TRACE
// Setup the 'HX_DEFINE_STACK_FRAME' 'HX_LOCAL_STACK_FRAME' macro.
// This will be empty, just track functions(release), track functions and lines(debug) or track everything (debugger)
#define HX_DECLARE_STACK_FRAME(name) extern ::hx::StackPosition name;
#ifdef HXCPP_STACK_LINE
#ifdef HXCPP_DEBUGGER
#define HX_DEFINE_STACK_FRAME(varName, className, functionName, classFunctionHash, fullName,fileName, \
lineNumber, fileHash ) \
::hx::StackPosition varName(className, functionName, fullName, fileName, lineNumber, \
classFunctionHash, fileHash);
#else
#define HX_DEFINE_STACK_FRAME(varName, className, functionName, classFunctionHash, fullName,fileName, \
lineNumber, fileHash ) \
::hx::StackPosition varName(className, functionName, fullName, fileName, lineNumber);
#endif
#else
#define HX_DEFINE_STACK_FRAME(varName, className, functionName, classFunctionHash, fullName,fileName, \
lineNumber, fileHash ) \
::hx::StackPosition varName(className, functionName, fullName, fileName);
#endif
#define HX_LOCAL_STACK_FRAME(a,b,c,d,e,f,g,h) static HX_DEFINE_STACK_FRAME(a,b,c,d,e,f,g,h)
// Haxe < 330 does not create position pointers, and we must use a local one.
// This code will hst the 'HX_STACK_FRAME' macro
#define HX_STACK_FRAME(className, functionName, classFunctionHash, fullName,fileName, lineNumber, fileHash ) \
HX_DEFINE_STACK_FRAME(__stackPosition, className, functionName, classFunctionHash, fullName,fileName, lineNumber, fileHash ) \
::hx::StackFrame _hx_stackframe(&__stackPosition);
// Newer code will use the HX_STACKFRAME macro
#define HX_STACKFRAME(pos) ::hx::StackFrame _hx_stackframe(pos);
#define HX_GC_STACKFRAME(pos) ::hx::StackFrame _hx_stackframe(pos);
// Must record the stack state at the catch
#define HX_STACK_BEGIN_CATCH __hxcpp_stack_begin_catch();
#define HX_JUST_GC_STACKFRAME ::hx::JustGcStackFrame _hx_stackframe;
#define HX_CTX _hx_stackframe.ctx
#else
// No need to track frame
#define HX_DECLARE_STACK_FRAME(name)
#define HX_STACK_BEGIN_CATCH
#define HX_DEFINE_STACK_FRAME(__stackPosition, className, functionName, classFunctionHash, fullName,fileName, lineNumber, fileHash )
#define HX_LOCAL_STACK_FRAME(a,b,c,d,e,f,g,h)
#define HX_STACK_FRAME(className, functionName, classFunctionHash, fullName,fileName, lineNumber, fileHash )
#define HX_STACKFRAME(pos)
#define HX_JUST_GC_STACKFRAME ::hx::StackContext *_hx_ctx = HX_CTX_GET;
#define HX_GC_STACKFRAME(pos) HX_JUST_GC_STACKFRAME
#define HX_CTX _hx_ctx
#endif
#define HX_GC_CTX HX_CTX
// Setup debugger catchable and variable macros...
#ifdef HXCPP_DEBUGGER
// Emitted at the beginning of every instance fuction. ptr is "this".
// Only if stack variables are to be tracked
#define HX_STACK_THIS(ptr) ::hx::StackThis __stackthis(_hx_stackframe.variables, ptr);
// Emitted at the beginning of every function that takes arguments.
// name is the name of the argument.
// For the lifetime of this object, the argument will be in the [arguments]
// list of the stack frame in which the arg was declared
// Only if stack variables are to be tracked
#define HX_STACK_ARG(cpp_var, haxe_name) \
::hx::StackVariable __stackargument_##cpp_var(_hx_stackframe.variables, true, haxe_name, &cpp_var);
// Emitted whenever a Haxe value is pushed on the stack. cpp_var is the local
// cpp variable, haxe_name is the name that was used in haxe for it
// Only if stack variables are to be tracked
#define HX_STACK_VAR(cpp_var, haxe_name) \
::hx::StackVariable __stackvariable_##cpp_var(_hx_stackframe.variables, false, haxe_name, &cpp_var);
#define HX_STACK_CATCHABLE(T, n) \
hx::StackCatchable __stackcatchable_##n \
(_hx_stackframe, reinterpret_cast<T *>(&_hx_stackframe));
// If HXCPP_DEBUGGER is enabled, then a throw is checked to see if it
// can be caught and if not, the debugger is entered. Otherwise, the
// throw proceeds as normal.
#define HX_STACK_DO_THROW(e) __hxcpp_dbg_checkedThrow(e)
#define HX_STACK_DO_RETHROW(e) __hxcpp_dbg_checkedRethrow(e)
#define HX_VAR(type,name) type name; HX_STACK_VAR(name, #name)
#define HX_VARI(type,name) type name; HX_STACK_VAR(name, #name) name
#define HX_VAR_NAME(type,name,dbgName) type name; HX_STACK_VAR(name, dbgName)
#define HX_VARI_NAME(type,name,dbgName) type name; HX_STACK_VAR(name, dbgName) name
#else // Non-debugger versions. Just stub-out.
#define HX_STACK_THIS(ptr)
#define HX_STACK_ARG(cpp_var, haxe_name)
#define HX_STACK_VAR(cpp_var, haxe_name)
#define HX_STACK_CATCHABLE(T, n)
#define HX_VAR(type,name) type name
#define HX_VARI(type,name) type name
#define HX_VAR_NAME(type,name,dbgName) type name
#define HX_VARI_NAME(type,name,dbgName) type name
// Just throw - move to hx::Throw function?
#define HX_STACK_DO_THROW(e) ::hx::Throw(e)
#define HX_STACK_DO_RETHROW(e) ::hx::Rethrow(e)
#endif // HXCPP_STACK_VARS
// Emitted after every Haxe line. number is the original Haxe line number.
// Only if stack lines are to be tracked
#ifdef HXCPP_STACK_LINE
// If the debugger is enabled, must check for a breakpoint at every line.
#ifdef HXCPP_DEBUGGER
#define HX_STACK_LINE(number) \
_hx_stackframe.lineNumber = number; \
/* This is incorrect - a read memory barrier is needed here. */ \
/* For now, just live with the exceedingly rare cases where */ \
/* breakpoints are missed */ \
if (::hx::gShouldCallHandleBreakpoints) { \
__hxcpp_on_line_changed(_hx_stackframe.ctx); \
}
#define HX_STACK_LINE_QUICK(number) _hx_stackframe.lineNumber = number;
#else
// Just set it
#define HX_STACK_LINE(number) _hx_stackframe.lineNumber = number;
#define HX_STACK_LINE_QUICK(number) _hx_stackframe.lineNumber = number;
#endif
#else
#define HX_STACK_LINE(number)
#define HX_STACK_LINE_QUICK(number)
#endif
// For tidier generated code
#define HXLINE(number) HX_STACK_LINE(number)
#define HXDLIN(number)
// To support older versions of the haxe compiler that emit HX_STACK_PUSH
// instead of HX_STACK_FRAME. If the old haxe compiler is used with this
// new debugger implementation, className.functionName breakpoints will
// not work, and stack reporting will be a little weird. If you want to
// use debugging, you really should upgrade to a newer haxe compiler.
#undef HX_STACK_PUSH
#define HX_STACK_PUSH(fullName, fileName, lineNumber) \
HX_STACK_FRAME("", fullName, 0, fullName, fileName, lineNumber, 0)
#if defined(HXCPP_STACK_TRACE) || defined(HXCPP_TELEMETRY)
#define HXCPP_STACK_IDS
#endif
namespace hx
{
class StackFrame;
struct StackContext;
class Profiler;
void profDestroy(Profiler *);
void profAttach(Profiler *, StackContext *);
void profDetach(Profiler *, StackContext *);
void profSample(Profiler *, StackContext *inContext);
class Telemetry;
Telemetry *tlmCreate(StackContext *);
void tlmDestroy(Telemetry *);
void tlmAttach(Telemetry *, StackContext *);
void tlmDetach(Telemetry *);
void tlmSampleEnter(Telemetry *, StackFrame *inFrame);
void tlmSampleExit(Telemetry *);
class DebuggerContext;
DebuggerContext *dbgCtxCreate(StackContext *);
void dbgCtxDestroy(DebuggerContext *);
void dbgCtxAttach(DebuggerContext *, StackContext *);
void dbgCtxDetach(DebuggerContext *);
void dbgCtxEnable(DebuggerContext *, bool inEnable);
struct scriptCallable;
class StackVariable;
class StackCatchable;
template<typename T> struct Hash;
struct TWeakStringSet;
typedef Hash<TWeakStringSet> WeakStringSet;
extern const char* EXTERN_CLASS_NAME;
#ifdef HXCPP_DEBUGGER
extern volatile bool gShouldCallHandleBreakpoints;
// These must match the values present in cpp.vm.Debugger
enum DebugStatus
{
DBG_STATUS_INVALID = 0, // Not present or needed in cpp.vm.Debugger
DBG_STATUS_RUNNING = 1,
DBG_STATUS_STOPPED_BREAK_IMMEDIATE = 2,
DBG_STATUS_STOPPED_BREAKPOINT = 3,
DBG_STATUS_STOPPED_UNCAUGHT_EXCEPTION = 4,
DBG_STATUS_STOPPED_CRITICAL_ERROR = 5
};
enum ExecutionTrace
{
exeTraceOff = 0,
exeTraceFuncs = 1,
exeTraceLines = 2,
};
extern ExecutionTrace sExecutionTrace;
#endif
class StackPosition
{
public:
// These are constant during the lifetime of the stack frame
const char *className;
const char *functionName;
const char *fullName; // this is className.functionName - used for profiler
const char *fileName;
int firstLineNumber;
#if defined(HXCPP_STACK_SCRIPTABLE)
// Information about the current cppia function
struct ScriptCallable *scriptCallable;
#endif
// These are only used if HXCPP_DEBUGGER is defined
#ifdef HXCPP_DEBUGGER
int fileHash;
int classFuncHash;
#else
enum { fileHash = 0, classFuncHash=0 };
#endif
inline StackPosition() { }
// The constructor automatically adds the StackFrame to the list of
// stack frames for the current thread
inline StackPosition(const char *inClassName, const char *inFunctionName,
const char *inFullName, const char *inFileName
#ifdef HXCPP_STACK_LINE
, int inLineNumber
#endif
#ifdef HXCPP_DEBUGGER
,int inClassFunctionHash, int inFileHash
#endif
)
: className(inClassName), functionName(inFunctionName)
,fullName(inFullName), fileName(inFileName)
#ifdef HXCPP_DEBUGGER
,classFuncHash(inClassFunctionHash)
,fileHash(inFileHash)
#endif
#ifdef HXCPP_STACK_LINE
,firstLineNumber(inLineNumber)
#endif
{
#if defined(HXCPP_STACK_SCRIPTABLE)
// Information about the current cppia function
scriptCallable = 0;
#endif
}
};
#ifdef HXCPP_STACK_TRACE
struct ExceptionStackFrame
{
#ifdef HXCPP_STACK_LINE
int line;
#endif
#if HXCPP_API_LEVEL > 330
const hx::StackPosition *position;
#else
const char *className;
const char *functionName;
const char *fileName;
#endif
ExceptionStackFrame(const StackFrame &inFrame);
::String format(bool inForDisplay);
::String toDisplay();
::String toString();
};
#endif
#ifdef HXCPP_SCRIPTABLE
enum
{
bcrBreak = 0x01,
bcrContinue = 0x02,
bcrReturn = 0x04,
bcrLoop = (bcrBreak | bcrContinue),
};
#endif
struct MarkChunk
{
enum { SIZE = 62 };
enum { OBJ_ARRAY_JOB = -1 };
inline MarkChunk() : count(0), next(0) { }
int count;
union
{
hx::Object *stack[SIZE];
struct
{
hx::Object **arrayBase;
int arrayElements;
};
};
MarkChunk *next;
inline void push(Object *inObj)
{
stack[count++] = inObj;
}
inline hx::Object *pop()
{
if (count)
return stack[--count];
return 0;
}
MarkChunk *swapForNew();
};
struct StackContext : public hx::ImmixAllocator
{
#ifdef HXCPP_STACK_IDS
int mThreadId;
#endif
#ifdef HXCPP_STACK_TRACE
hx::QuickVec<StackFrame *> mStackFrames;
hx::QuickVec<hx::ExceptionStackFrame> mExceptionStack;
// Updated only when a thrown exception unwinds the stack
bool mIsUnwindingException;
#ifdef HXCPP_STACK_SCRIPTABLE
// TODO - combine CppaCtx and StackContext
#endif
#ifdef HXCPP_DEBUGGER
DebuggerContext *mDebugger;
#endif
#ifdef HXCPP_PROFILER
// Profiling support
Profiler *mProfiler;
#endif
#endif
#ifdef HXCPP_TELEMETRY
// Telemetry support
Telemetry *mTelemetry;
#endif
#ifdef HXCPP_COMBINE_STRINGS
WeakStringSet *stringSet;
#endif
#ifdef HXCPP_GC_GENERATIONAL
MarkChunk *mOldReferrers;
inline void pushReferrer(hx::Object *inObj)
{
// If collector is running on non-generational mode, mOldReferrers will be null
if (mOldReferrers)
{
mOldReferrers->push(inObj);
if (mOldReferrers->count==MarkChunk::SIZE)
mOldReferrers = mOldReferrers->swapForNew();
}
}
#endif
#ifdef HXCPP_CATCH_SEGV
#ifdef _MSC_VER
_se_translator_function mOldSignalFunc;
#else
void (*mOldSignalFunc)(int);
#endif
#endif
StackContext();
~StackContext();
void onThreadAttach();
void onThreadDetach();
#ifdef HXCPP_STACK_TRACE // {
void tracePosition();
// Note that the stack frames are manipulated without holding any locks.
// This is because the manipulation of stack frames can only be done by
// the thread that "owns" that stack frame. The only other contention on
// the call stack is from calls to GetThreadInfo() and GetThreadInfos(),
// and these should only be called when the thread for which the call
// stack is being acquired is stopped in a breakpoint anyway, thus there
// can be no contention on the contents of the CallStack in that case
// either.
inline void pushFrame(StackFrame *inFrame)
{
#ifdef HXCPP_PROFILER
if (mProfiler)
profSample(mProfiler,this);
#endif
#ifdef HXCPP_TELEMETRY
if (mTelemetry)
tlmSampleEnter(mTelemetry,inFrame);
#endif
mIsUnwindingException = false;
mStackFrames.push(inFrame);
#ifdef HXCPP_DEBUGGER
if (sExecutionTrace!=exeTraceOff)
tracePosition();
#endif
}
inline void popFrame(StackFrame *inFrame)
{
#ifdef HXCPP_TELEMETRY
if (mTelemetry)
tlmSampleExit(mTelemetry);
#endif
if (mIsUnwindingException)
{
// Use default operator=
mExceptionStack.push( *inFrame );
}
mStackFrames.pop_back();
}
void getCurrentCallStackAsStrings(Array<String> result, bool skipLast);
void getCurrentExceptionStackAsStrings(Array<String> result);
StackFrame *getCurrentStackFrame() { return mStackFrames.back(); }
StackFrame *getStackFrame(int inIndex) { return mStackFrames[inIndex]; }
int getDepth() const { return mStackFrames.size(); }
inline const char *getFullNameAtDepth(int depth) const;
void dumpExceptionStack();
// Called when a throw occurs
void setLastException();
void pushLastException();
// Called when a catch block begins to be executed. hxcpp wants to track
// the stack back through the catches so that it can be dumped if
// uncaught. If inAll is true, the entire stack is captured immediately.
// If inAll is false, only the last stack frame is captured.
void beginCatch(bool inAll);
#endif // } HXCPP_STACK_TRACE
#ifdef HXCPP_DEBUGGER
void enableCurrentThreadDebugging(bool inEnable)
{
dbgCtxEnable(mDebugger,inEnable);
}
#endif
static inline StackContext *getCurrent()
{
return HX_CTX_GET;
}
#ifdef HXCPP_STACK_IDS
static void getAllStackIds( QuickVec<int> &outIds );
static StackContext *getStackForId(int id);
#endif
#ifdef HXCPP_SCRIPTABLE
unsigned char *stack;
unsigned char *pointer;
unsigned char *frame;
class Object *exception;
unsigned int breakContReturn;
int byteMarkId;
template<typename T>
void push(T inValue)
{
*(T *)pointer = inValue;
pointer += sizeof(T);
}
unsigned char *stackAlloc(int inSize)
{
unsigned char *p = pointer;
pointer += inSize;
return p;
}
void stackFree(int inSize)
{
pointer -= inSize;
}
int getFrameSize() const { return pointer-frame; }
int runInt(void *vtable);
Float runFloat(void *vtable);
String runString(void *vtable);
void runVoid(void *vtable);
Dynamic runObject(void *vtable);
hx::Object *runObjectPtr(void *vtable);
void push(bool &inValue) { *(int *)pointer = inValue; pointer += sizeof(int); }
inline void pushBool(bool b) { *(int *)pointer = b; pointer += sizeof(int); }
inline void pushInt(int i) { *(int *)pointer = i; pointer += sizeof(int); }
inline void pushFloat(Float f);
inline void pushString(const String &s);
inline void pushObject(Dynamic d);
inline void returnFloat(Float f);
inline void returnString(const String &s);
inline void returnObject(Dynamic d);
inline hx::Object *getThis(bool inCheckPtr=true);
inline void returnBool(bool b) { *(int *)frame = b; }
inline void returnInt(int i) { *(int *)frame = i; }
inline bool getBool(int inPos=0) { return *(bool *)(frame+inPos); }
inline int getInt(int inPos=0) { return *(int *)(frame+inPos); }
inline Float getFloat(int inPos=0);
inline String getString(int inPos=0);
inline Dynamic getObject(int inPos=0);
inline hx::Object *getObjectPtr(int inPos=0) { return *(hx::Object **)(frame+inPos); }
void breakFlag() { breakContReturn |= bcrBreak; }
void continueFlag() { breakContReturn |= bcrContinue; }
void returnFlag() { breakContReturn |= bcrReturn; }
#endif
};
typedef StackContext CppiaCtx;
class StackFrame
{
public:
StackContext *ctx;
#ifdef HXCPP_STACK_TRACE // {
const StackPosition *position;
#ifdef HXCPP_STACK_LINE
// Current line number, changes during the lifetime of the stack frame.
// Only updated if HXCPP_STACK_LINE is defined.
int lineNumber;
#ifdef HXCPP_DEBUGGER
// Function arguments and local variables in reverse order of their
// declaration. If a variable name is in here twice, the first version is
// the most recently scoped one and should be used. Only updated if
// HXCPP_DEBUGGER is defined.
StackVariable *variables;
// The list of types that can be currently caught in the stack frame.
StackCatchable *catchables;
#endif
#endif
// The constructor automatically adds the StackFrame to the list of
// stack frames for the current thread
inline StackFrame(const StackPosition *inPosition
) : position(inPosition)
{
#ifdef HXCPP_STACK_LINE
lineNumber = inPosition->firstLineNumber;
#ifdef HXCPP_DEBUGGER
variables = 0;
catchables = 0;
#endif
#endif
ctx = HX_CTX_GET;
ctx->pushFrame(this);
}
// The destructor automatically removes the StackFrame from the list of
// stack frames for the current thread
~StackFrame()
{
ctx->popFrame(this);
}
::String toString();
::String toDisplay();
#else // } !HXCPP_STACK_TRACE {
// Release version only has ctx
inline StackFrame()
{
ctx = HX_CTX_GET;
}
#endif // }
};
#ifdef HXCPP_STACK_TRACE
const char *StackContext::getFullNameAtDepth(int depth) const
{
return mStackFrames[depth]->position->fullName;
}
#endif
class JustGcStackFrame
{
public:
StackContext *ctx;
inline JustGcStackFrame() : ctx(HX_CTX_GET) { }
};
} // end namespace hx
// Some functions used by AdvancedDebug.cpp
// Returns the thread number of the calling thread
HXCPP_EXTERN_CLASS_ATTRIBUTES
int __hxcpp_GetCurrentThreadNumber();
// Called by the main function when an uncaught exception occurs to dump
// the stack leading to the exception
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hx_dump_stack();
// The macro HX_STACK_BEGIN_CATCH, which is emitted at the beginning of every
// catch block, calls this in debug mode to let the debugging system know that
// a catch block has been entered
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_stack_begin_catch();
// Last chance to throw an exception for null-pointer access
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_set_critical_error_handler(Dynamic inHandler);
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_execution_trace(int inLevel);
// Used by debug breakpoints and execution trace
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_set_stack_frame_line(int);
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_on_line_changed(hx::StackContext *);
HXCPP_EXTERN_CLASS_ATTRIBUTES
void __hxcpp_set_debugger_info(const char **inAllClasses, const char **inFullPaths);
void __hxcpp_dbg_getScriptableVariables(hx::StackFrame *stackFrame, ::Array< ::Dynamic> outNames);
bool __hxcpp_dbg_getScriptableValue(hx::StackFrame *stackFrame, String inName, ::Dynamic &outValue);
bool __hxcpp_dbg_setScriptableValue(hx::StackFrame *StackFrame, String inName, ::Dynamic inValue);
#endif // HX_STACK_CTX_H