//----------------------------------------------------------------------- // github.com/samisalreadytaken/sqdbg //----------------------------------------------------------------------- // // Squirrel Debugger // #define SQDBG_SV_VER 8 #define SQDBG_SOURCENAME_HAS_PATH #include "sqdbg.h" #include // INT_MIN #include // FLT_MAX #include // offsetof #include #include // qsort, strtod #include // snprintf #include #include #ifndef SQDBG_DISABLE_PROFILER #include // isfinite #include // high_resolution_clock #endif #define ___CAT(a, b) a##b #define __CAT(a, b) ___CAT(a, b) #define STR_NOMEM "**OUT OF MEMORY**" #define AssertOOM(p, size) AssertMsg1((p), "**OUT OF MEMORY** (%u)", (unsigned int)(size)) #include "debug.h" #include "vec.h" #include "net.h" // Everything works fine, but client sent invalid message #ifndef AssertClient #define AssertClient Assert #define AssertClientMsg1 AssertMsg1 #endif // For Squirrel headers #ifndef assert #define assert Assert #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(SQUNICODE) && !defined(_WIN32) #include // swprintf #endif STATIC_ASSERT(sizeof(int) == sizeof(int32_t)); STATIC_ASSERT(sizeof(short) == sizeof(int16_t)); STATIC_ASSERT(sizeof(char) == sizeof(int8_t)); #ifdef _WIN32 void sqdbg_sleep(int ms) { ::Sleep((DWORD)ms); } #else #include void sqdbg_sleep(int ms) { timespec t; t.tv_nsec = ms * 1000000; t.tv_sec = 0; nanosleep(&t, NULL); } #endif #if defined(SQDBG_DEBUGGER_ECHO_OUTPUT) && defined(_WIN32) #ifdef SQUNICODE #define _OutputDebugString(s) OutputDebugStringW(s) #else #define _OutputDebugString(s) OutputDebugStringA(s) #endif #define _OutputDebugStringA(s) OutputDebugStringA(s) void _OutputDebugStringFmt(const SQChar *fmt, ...) { SQChar buf[256]; va_list va; va_start(va, fmt); #ifdef SQUNICODE int len = vswprintf(buf, sizeof(buf) / sizeof(SQChar), fmt, va); #else int len = vsnprintf(buf, sizeof(buf) / sizeof(SQChar), fmt, va); #endif va_end(va); #if defined(_MSC_VER) && _MSC_VER < 1900 if (len < 0 || len > (int)(sizeof(buf) / sizeof(SQChar)) - 1) buf[sizeof(buf) / sizeof(SQChar) - 1] = 0; #else (void)len; #endif _OutputDebugString(buf); } #else #define _OutputDebugString(s) (void)0 #define _OutputDebugStringA(s) (void)0 #define _OutputDebugStringFmt(...) (void)0 #endif #define _ArraySize(p) (sizeof((p)) / sizeof(*(p))) #define memzero(p) memset((char *)(p), 0, sizeof(*(p))) #define ALIGN(v, a) (((v) + ((a) - 1)) & ~((a) - 1)) #define ROUND(v, a) ((v) + (a) - (v) % (a)) #ifndef _WIN32 #undef offsetof #define offsetof(a, b) ((size_t)(&(((a *)0)->b))) #endif #ifndef max #define max(a, b) (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a, b) (((a) < (b)) ? (a) : (b)) #endif #undef _SC #ifdef SQUNICODE #define _SC(s) __CAT(L, s) #else #define _SC(s) s #endif #ifdef SQUNICODE #ifdef _WIN32 #ifndef WCHAR_SIZE #define WCHAR_SIZE 2 #endif typedef uint16_t SQUnsignedChar; #else #ifndef WCHAR_SIZE #define WCHAR_SIZE 4 #endif typedef uint32_t SQUnsignedChar; #endif #else typedef unsigned char SQUnsignedChar; #endif STATIC_ASSERT(sizeof(SQChar) == sizeof(SQUnsignedChar)); #ifndef scstrlen #ifdef SQUNICODE #define scstrlen wcslen #else #define scstrlen strlen #endif #endif #ifndef scstrchr #ifdef SQUNICODE #define scstrchr wcschr #else #define scstrchr strchr #endif #endif #ifndef scstrcmp #ifdef SQUNICODE #define scstrcmp wcscmp #else #define scstrcmp strcmp #endif #endif #ifndef scstricmp #ifdef SQUNICODE #ifdef _WIN32 #define scstricmp _wcsicmp #else #define scstricmp sqdbg_wcsicmp #endif #else #ifdef _WIN32 #define scstricmp _stricmp #else #define scstricmp strcasecmp #endif #endif #endif #undef scsprintf #ifdef SQUNICODE #define scsprintf swprintf #else #define scsprintf snprintf #endif #undef scvsprintf #ifdef SQUNICODE #define scvsprintf vswprintf #else #define scvsprintf vsnprintf #endif #ifndef sq_rsl #define sq_rsl(l) ((l) * sizeof(SQChar)) #endif #define SQStringFromSQChar(_pch) \ ((SQString *)((char *)(_pch) - (char *)offsetof(SQString, _val))) #ifndef SQUIRREL_VERSION_NUMBER #error "SQUIRREL_VERSION_NUMBER is undefined" #endif #if SQUIRREL_VERSION_NUMBER >= 300 #define _fp(func) (func) #define _outervalptr(outervar) (_outer((outervar))->_valptr) #define _nativenoutervalues(p) (p)->_noutervalues #define NATIVE_DEBUG_HOOK #ifdef NATIVE_DEBUG_HOOK #define SUPPORTS_RESTART_FRAME #define DEBUG_HOOK_CACHED_SQDBG #endif #ifdef NATIVE_DEBUG_HOOK typedef SQDEBUGHOOK _SQDEBUGHOOK; #else typedef SQFUNCTION _SQDEBUGHOOK; #endif #define CLOSURE_ENV_ISVALID(env) (env) #define CLOSURE_ENV_OBJ(env) ((env)->_obj) #if SQUIRREL_VERSION_NUMBER >= 310 #define CLOSURE_ROOT #endif #else #define _fp(func) _funcproto(func) #define _outervalptr(outervar) (&(outervar)) #define _nativenoutervalues(p) (p)->_outervalues.size() typedef SQFUNCTION _SQDEBUGHOOK; #if SQUIRREL_VERSION_NUMBER >= 212 #define CLOSURE_ENV_ISVALID(env) (sq_type(env) == OT_WEAKREF && _weakref(env)) #define CLOSURE_ENV_OBJ(env) (_weakref(env)->_obj) #endif #if SQUIRREL_VERSION_NUMBER < 225 #undef _rawval #if defined(_SQ64) #define _rawval(o) ((uint64_t)((o)._unVal.nInteger)) #elif defined(SQUSEDOUBLE) #define _rawval(o) (*(uint64_t *)&((o)._unVal.fFloat)) #else #define _rawval(o) ((uintptr_t)((o)._unVal.pRefCounted)) #endif #if SQUIRREL_VERSION_NUMBER < 223 #if defined(_SQ64) || defined(SQUSEDOUBLE) typedef uint64_t SQRawObjectVal; #else typedef uintptr_t SQRawObjectVal; #endif #endif #endif #endif #if SQUIRREL_VERSION_NUMBER < 310 #undef type #undef is_delegable #define is_delegable(t) (sq_type(t) & SQOBJECT_DELEGABLE) #endif #if !defined(SQDBG_DISABLE_COMPILER) && !defined(NO_GARBAGE_COLLECTOR) #define SUPPORTS_DEREF_OP #if SQUIRREL_VERSION_NUMBER >= 300 #define ACCESSIBLE_FUNCPROTO #endif #endif #if defined(SQDBG_SUPPORTS_FUNCPROTO_LIST) && !defined(ACCESSIBLE_FUNCPROTO) #define ACCESSIBLE_FUNCPROTO #endif #if defined(SQDBG_DISABLE_PROFILER) && !defined(SQDBG_DISABLE_PROFILER_AUTO) #define SQDBG_DISABLE_PROFILER_AUTO #endif #if defined(SQDBG_DISABLE_COMPILER) && !defined(SQDBG_DISABLE_EVAL_FUNC) #define SQDBG_DISABLE_EVAL_FUNC #endif #include "str.h" #include "json.h" #include "protocol.h" #define SQ_FOREACH_OP(obj, key, val) \ int __CAT(_jump, __LINE__); \ for (SQObjectPtr _pos; \ m_pCurVM->FOREACH_OP(obj, key, val, _pos, 0, 666, __CAT(_jump, __LINE__)) && \ __CAT(_jump, __LINE__) != 666;) #define FOREACH_SQTABLE(pTable, key, val) \ SQInteger __CAT(_i, __LINE__) = 0; \ for (SQObjectPtr _pi = __CAT(_i, __LINE__); \ (__CAT(_i, __LINE__) = pTable->Next(false, _pi, key, val)) != -1; \ _pi._unVal.nInteger = __CAT(_i, __LINE__)) #ifndef SQDBG_EXCLUDE_DEFAULT_MEMFUNCTIONS inline void *sqdbg_malloc(unsigned int size) { extern void *sq_vm_malloc(SQUnsignedInteger size); return sq_vm_malloc(size); } inline void *sqdbg_realloc(void *p, unsigned int oldsize, unsigned int size) { extern void *sq_vm_realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size); return sq_vm_realloc(p, oldsize, size); } inline void sqdbg_free(void *p, unsigned int size) { extern void sq_vm_free(void *p, SQUnsignedInteger size); sq_vm_free(p, size); } #endif static inline HSQDEBUGSERVER sqdbg_get(HSQUIRRELVM vm); static inline HSQDEBUGSERVER sqdbg_get_debugger(HSQUIRRELVM vm); #ifdef NATIVE_DEBUG_HOOK #ifdef DEBUG_HOOK_CACHED_SQDBG static inline HSQDEBUGSERVER sqdbg_get_debugger_cached_debughook(HSQUIRRELVM vm); #else #define sqdbg_get_debugger_cached_debughook sqdbg_get_debugger #endif #endif static inline void sqdbg_get_debugger_ref(HSQUIRRELVM vm, SQObjectPtr &ref); template void CopyString(CScratch *allocator, const T &src, T *dst) { Assert(src.ptr); if (src.len) { if (src.len != dst->len) { if (dst->len) allocator->Free(dst->ptr); void *mem = allocator->Alloc((src.len + 1) * sizeof(*dst->ptr)); if (!mem) { if (dst->ptr && dst->len) { if (sizeof(*dst->ptr) == 1) { memcpy(dst->ptr, STR_NOMEM, min(dst->len, STRLEN(STR_NOMEM)) * sizeof(*dst->ptr)); } else { memcpy(dst->ptr, _SC(STR_NOMEM), min(dst->len, STRLEN(STR_NOMEM)) * sizeof(*dst->ptr)); } } return; } *(void **)&dst->ptr = mem; dst->len = src.len; dst->ptr[dst->len] = 0; } else { Assert(dst->ptr); Assert(dst->ptr[dst->len] == 0); } memcpy(dst->ptr, src.ptr, dst->len * sizeof(*dst->ptr)); } else { if (dst->len) { allocator->Free(dst->ptr); dst->ptr = NULL; dst->len = 0; } else { Assert(!dst->ptr); } } } #ifdef SQUNICODE void CopyString(CScratch *allocator, const string_t &src, sqstring_t *dst) { Assert(src.ptr); unsigned int srclen = SQUnicodeLength(src.ptr, src.len); if (srclen) { if (srclen != dst->len) { if (dst->len) allocator->Free(dst->ptr); void *mem = allocator->Alloc((srclen + 1) * sizeof(*dst->ptr)); if (!mem) { if (dst->ptr && dst->len) memcpy(dst->ptr, _SC(STR_NOMEM), min(dst->len, STRLEN(STR_NOMEM)) * sizeof(*dst->ptr)); return; } *(void **)&dst->ptr = mem; dst->len = srclen; dst->ptr[dst->len] = 0; } else { Assert(dst->ptr); Assert(dst->ptr[dst->len] == 0); } UTF8ToSQUnicode(dst->ptr, dst->len * sizeof(*dst->ptr), src.ptr, src.len); } else { if (dst->len) { allocator->Free(dst->ptr); dst->ptr = NULL; dst->len = 0; } else { Assert(!dst->ptr); } } } void CopyString(CScratch *allocator, const sqstring_t &src, string_t *dst) { Assert(src.ptr); unsigned int srclen = UTF8Length(src.ptr, src.len); if (srclen) { if (srclen != dst->len) { if (dst->len) allocator->Free(dst->ptr); void *mem = allocator->Alloc((srclen + 1) * sizeof(*dst->ptr)); if (!mem) { if (dst->ptr && dst->len) memcpy(dst->ptr, STR_NOMEM, min(dst->len, STRLEN(STR_NOMEM)) * sizeof(*dst->ptr)); return; } *(void **)&dst->ptr = mem; dst->len = srclen; dst->ptr[dst->len] = 0; } else { Assert(dst->ptr); Assert(dst->ptr[dst->len] == 0); } SQUnicodeToUTF8(dst->ptr, dst->len * sizeof(*dst->ptr), src.ptr, src.len); } else { if (dst->len) { allocator->Free(dst->ptr); dst->ptr = NULL; dst->len = 0; } else { Assert(!dst->ptr); } } } #endif template void FreeString(CScratch *allocator, T *dst) { if (dst->len) { allocator->Free(dst->ptr); dst->ptr = NULL; dst->len = 0; } } inline SQString *CreateSQString(HSQUIRRELVM vm, const sqstring_t &str) { return SQString::Create(vm->_sharedstate, str.ptr, str.len); } inline SQString *CreateSQString(SQSharedState *ss, const sqstring_t &str) { return SQString::Create(ss, str.ptr, str.len); } inline bool SQTable_Get(SQTable *table, const sqstring_t &key, SQObjectPtr &val) { #if SQUIRREL_VERSION_NUMBER >= 310 #ifdef SQUNICODE Assert(key.ptr[key.len] == 0); return table->GetStr(key.ptr, key.len, val); #else // SQTable::GetStr ignores string length with strcmp, need a terminated string here. // If the string is not already terminated, then it's a writable, non-const string // and its source is the network buffer char z = key.ptr[key.len]; if (z != 0) key.ptr[key.len] = 0; bool r = table->GetStr(key.ptr, key.len, val); if (z != 0) key.ptr[key.len] = z; return r; #endif #else SQObjectPtr str = SQString::Create(table->_sharedstate, key.ptr, key.len); return table->Get(str, val); #endif } // uses the scratch pad for unicode conversion temporary buffer inline SQString *CreateSQString(SQDebugServer *dbg, const string_t &str); inline bool SQTable_Get(SQDebugServer *dbg, SQTable *table, const string_t &key, SQObjectPtr &val); inline int GetFunctionDeclarationLine(SQFunctionProto *func) { // not exactly function declaration line, but close enough Assert(func->_nlineinfos > 0); Assert(func->_lineinfos[0]._line == func->GetLine(func->_instructions)); return func->_lineinfos[0]._line; } inline bool IsFalse(const SQObject &obj) { #if SQUIRREL_VERSION_NUMBER >= 225 || defined(_SQ64) == defined(SQUSEDOUBLE) return ((sq_type(obj) & SQOBJECT_CANBEFALSE) && _rawval(obj) == 0); #else #if defined(_SQ64) return ((sq_type(obj) & SQOBJECT_CANBEFALSE) && ((_rawval(obj) == 0) || (sq_type(obj) == OT_FLOAT && _float(obj) == 0.0))); #else // SQUSEDOUBLE return ((sq_type(obj) & SQOBJECT_CANBEFALSE) && ((_rawval(obj) == 0) || (sq_type(obj) == OT_INTEGER && _integer(obj) == 0))); #endif #endif } inline void SetBool(SQObjectPtr &obj, int state) { obj.Null(); obj._type = OT_BOOL; obj._unVal.nInteger = state; } inline bool IsEqual(const SQObject &o1, const SQObject &o2) { return (_rawval(o1) == _rawval(o2) && sq_type(o1) == sq_type(o2)); } template inline bool IsEqual(const SQChar (&s1)[s1size], const SQString *s2) { return sqstring_t(s1).IsEqualTo(s2); } template inline void StripFileName(C **ptr, unsigned int *len) { for (C *c = *ptr + *len - 1; c >= *ptr; c--) { if (*c == '/' || *c == '\\') { c++; *len = *ptr + *len - c; *ptr = c; break; } } } inline void StripWhitespace(string_t &str) { char *end = str.ptr + str.len; for (char *c = str.ptr; c < end; c++) { if (*c == ' ' || *c == '\t' || *c == '\n') { str.ptr++; str.len--; } else { break; } } for (char *c = end - 1; c >= str.ptr; c--) { if (*c == ' ' || *c == '\t' || *c == '\n') { str.len--; } else { break; } } } #ifdef _DEBUG class CStackCheck { private: HSQUIRRELVM vm; int top; public: CStackCheck(HSQUIRRELVM v) { vm = v; top = vm->_top; } ~CStackCheck() { Assert(vm->_top == top); } }; #define STACKCHECK(vm) CStackCheck stackcheck(vm) #else #define STACKCHECK(vm) (void)0 #endif #ifndef SQDBG_DISABLE_PROFILER class CProfiler { public: typedef double sample_t; typedef unsigned int hnode_t; typedef unsigned int hgroup_t; static const hnode_t INVALID_HANDLE = (hnode_t)-1; #ifndef SQDBG_DISABLE_PROFILER_AUTO struct node_t { void *func; hnode_t caller; unsigned int calls; sample_t samples; sample_t sampleStart; hnode_t id; }; struct nodetag_t { SQString *funcsrc; SQString *funcname; }; #endif struct group_t { unsigned int hits; unsigned int peakHit; sample_t samples; sample_t peak; sample_t sampleStart; SQString *tag; }; static sample_t Sample() { std::chrono::duration d = std::chrono::high_resolution_clock::now().time_since_epoch(); return d.count(); } private: enum { kProfDisabled = 0, kProfActive, kProfPaused, }; int m_State; int m_nPauseLevel; #ifndef SQDBG_DISABLE_PROFILER_AUTO vector m_Nodes; vector m_CallStack; #endif vector m_Groups; vector m_GroupStack; #ifndef SQDBG_DISABLE_PROFILER_AUTO vector m_NodeTags; #endif private: #ifndef SQDBG_DISABLE_PROFILER_AUTO node_t *FindNode(hnode_t caller, void *func, hnode_t *handle) { hnode_t count = m_Nodes.Size(); // Start searching from caller, // new nodes are added after them hnode_t i = caller != INVALID_HANDLE ? caller : 0; for (; i < count; i++) { node_t &node = m_Nodes[i]; if (node.func == func && node.caller == caller) { Assert((hnode_t)i == node.id); *handle = (hnode_t)i; return &node; } } return NULL; } #endif group_t *FindGroup(SQString *tag, hgroup_t *idx) { for (hgroup_t i = m_Groups.Size(); i--;) { group_t &group = m_Groups[i]; if (group.tag == tag) { *idx = i; return &group; } } return NULL; } public: bool IsEnabled() { return m_State != kProfDisabled; } bool IsActive() { return m_State == kProfActive; } void Start(HSQUIRRELVM vm) { Assert(!IsEnabled()); if (m_State != kProfDisabled) return; m_State = kProfActive; Assert(m_GroupStack.Capacity() == 0); #ifndef SQDBG_DISABLE_PROFILER_AUTO Assert(m_Nodes.Capacity() == 0); Assert(m_NodeTags.Capacity() == 0); Assert(m_CallStack.Capacity() == 0); m_Nodes.Reserve(max(vm->_alloccallsstacksize, 256)); m_NodeTags.Reserve(m_Nodes.Capacity()); m_CallStack.Reserve(max(vm->_alloccallsstacksize, 8)); for (int i = 0; i < vm->_callsstacksize; i++) { const SQVM::CallInfo &ci = vm->_callsstack[i]; if (sq_type(ci._closure) == OT_CLOSURE) CallBegin(_fp(_closure(ci._closure)->_function)); } #else (void)vm; #endif } void Stop() { Assert(IsEnabled()); if (m_State == kProfDisabled) return; m_State = kProfDisabled; m_nPauseLevel = 0; #ifndef SQDBG_DISABLE_PROFILER_AUTO for (hnode_t i = 0; i < m_NodeTags.Size(); i++) { nodetag_t *node = &m_NodeTags[i]; __ObjRelease(node->funcsrc); __ObjRelease(node->funcname); } #endif for (hnode_t i = 0; i < m_Groups.Size(); i++) { group_t *group = &m_Groups[i]; __ObjRelease(group->tag); } #ifndef SQDBG_DISABLE_PROFILER_AUTO m_Nodes.Purge(); m_NodeTags.Purge(); m_CallStack.Purge(); #endif m_Groups.Purge(); m_GroupStack.Purge(); } void Reset(HSQUIRRELVM vm, SQString *tag) { Assert(IsEnabled()); if (tag) { hgroup_t idx; group_t *group = FindGroup(tag, &idx); if (group) { group->hits = 0; group->peakHit = 0; group->samples = 0.0; group->peak = 0.0; group->sampleStart = 0.0; } } #ifndef SQDBG_DISABLE_PROFILER_AUTO else { for (hnode_t i = 0; i < m_NodeTags.Size(); i++) { nodetag_t *node = &m_NodeTags[i]; __ObjRelease(node->funcsrc); __ObjRelease(node->funcname); } m_Nodes.Clear(); m_NodeTags.Clear(); m_CallStack.Clear(); for (int i = 0; i < vm->_callsstacksize; i++) { const SQVM::CallInfo &ci = vm->_callsstack[i]; if (sq_type(ci._closure) == OT_CLOSURE) CallBegin(_fp(_closure(ci._closure)->_function)); } } #else (void)vm; #endif } void GroupBegin(SQString *tag) { Assert(IsActive()); hgroup_t idx; group_t *group = FindGroup(tag, &idx); if (group) { m_GroupStack.Append(idx); group->hits++; group->sampleStart = Sample(); return; } m_GroupStack.Append(m_Groups.Size()); group = &m_Groups.Append(); group->tag = tag; __ObjAddRef(tag); group->hits = 1; group->sampleStart = Sample(); } void GroupEnd() { Assert(IsActive()); sample_t sample = Sample(); if (!m_GroupStack.Size()) { AssertClient(!"profiler group mismatch"); return; } hgroup_t idx = m_GroupStack.Top(); m_GroupStack.Pop(); group_t *group = &m_Groups[idx]; // group was ended while profiler was paused AssertClient(group->sampleStart != 0.0); sample_t dt = sample - group->sampleStart; group->samples += dt; if (group->peak < dt) { group->peak = dt; group->peakHit = group->hits; } } void Pause() { Assert(IsEnabled()); sample_t sample = Sample(); ++m_nPauseLevel; if (m_State != kProfPaused) { m_State = kProfPaused; #ifndef SQDBG_DISABLE_PROFILER_AUTO for (unsigned int i = 0; i < m_CallStack.Size(); i++) { hnode_t caller = m_CallStack[i]; node_t *node = &m_Nodes[caller]; node->samples += sample - node->sampleStart; #ifdef _DEBUG node->sampleStart = 0.0; #endif } #endif for (unsigned int i = 0; i < m_GroupStack.Size(); i++) { hgroup_t idx = m_GroupStack[i]; group_t *group = &m_Groups[idx]; sample_t dt = sample - group->sampleStart; group->samples += dt; #ifdef _DEBUG group->sampleStart = 0.0; #endif if (group->peak < dt) { group->peak = dt; group->peakHit = group->hits; } } } } void Resume() { Assert(IsEnabled()); if (m_State == kProfPaused && --m_nPauseLevel == 0) { m_State = kProfActive; sample_t sample = Sample(); #ifndef SQDBG_DISABLE_PROFILER_AUTO for (unsigned int i = 0; i < m_CallStack.Size(); i++) { hnode_t caller = m_CallStack[i]; node_t *node = &m_Nodes[caller]; node->sampleStart = sample; } #endif for (unsigned int i = 0; i < m_GroupStack.Size(); i++) { hgroup_t idx = m_GroupStack[i]; group_t *group = &m_Groups[idx]; group->sampleStart = sample; } } } #ifndef SQDBG_DISABLE_PROFILER_AUTO void CallBegin(SQFunctionProto *func) { Assert(IsActive() || m_State == kProfPaused); hnode_t caller = m_CallStack.Size() ? m_CallStack.Top() : INVALID_HANDLE; hnode_t id; node_t *node = FindNode(caller, func, &id); if (node) { m_CallStack.Append(id); node->calls++; node->sampleStart = Sample(); return; } SQString *funcname = (sq_type(func->_name) == OT_STRING) ? _string(func->_name) : NULL; SQString *funcsrc = (sq_type(func->_sourcename) == OT_STRING) ? _string(func->_sourcename) : NULL; id = m_Nodes.Size(); node = &m_Nodes.Append(); nodetag_t *tag = &m_NodeTags.Append(); m_CallStack.Append(id); node->id = id; node->func = func; node->caller = caller; node->calls = 1; node->samples = 0.0; #if SQUIRREL_VERSION_NUMBER >= 300 SQSharedState *ss = func->_sharedstate; #else // null sourcename is not possible in the compiler // if it is, get SS from 'this': _string(func->_localvarinfos[0]._name)->_sharedstate; // if there are no parameters either, // the sharedstate needs to be accessible to the profiler Assert(funcsrc); SQSharedState *ss = funcsrc->_sharedstate; #endif if (funcname) { tag->funcname = funcname; } else { SQChar *tmp = ss->GetScratchPad(sq_rsl(FMT_PTR_LEN + 1)); int len = printhex(tmp, FMT_PTR_LEN, (uintptr_t)func); tmp[len] = 0; tag->funcname = SQString::Create(ss, tmp, FMT_PTR_LEN); } if (funcsrc) { unsigned int len = funcsrc->_len + 1 + FMT_UINT32_LEN + 1; SQChar *tmp = ss->GetScratchPad(sq_rsl(len)); len = funcsrc->_len; memcpy(tmp, funcsrc->_val, sq_rsl(len)); #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&tmp, &len); #endif int line = GetFunctionDeclarationLine(func); if (line) { tmp[len++] = ':'; len += printint(tmp + len, FMT_UINT32_LEN, line); } tmp[len] = 0; tag->funcsrc = SQString::Create(ss, tmp, len); } else { tag->funcsrc = CreateSQString(ss, _SC("??")); } __ObjAddRef(tag->funcname); __ObjAddRef(tag->funcsrc); node->sampleStart = Sample(); } void CallEnd() { Assert(IsActive()); sample_t sample = Sample(); if (!m_CallStack.Size()) { // metamethod calls don't execute debug hook pre-221 #if SQUIRREL_VERSION_NUMBER >= 221 AssertClient(!"profiler call mismatch"); #endif return; } hnode_t id = m_CallStack.Top(); m_CallStack.Pop(); node_t *node = &m_Nodes[id]; // call ended while profiler was paused AssertClient(node->sampleStart != 0.0); node->samples += sample - node->sampleStart; } void CallEndAll() { while (m_CallStack.Size()) CallEnd(); } #endif #ifndef SQDBG_CALLGRAPH_MAX_DEPTH #define SQDBG_CALLGRAPH_MAX_DEPTH 10 #endif #define PROF_OUTPUT_HEADER " % total time time/call calls func\n" // "100.00 100.00 ms 100.00 ms 4294967295 func\n" #define PROF_GROUP_OUTPUT_START \ "(sqdbg) prof | " #define PROF_GROUP_OUTPUT_TEMPLATE \ "(sqdbg) prof | : " \ "total 100.00 ms, avg 100.00 ms, peak 100.00 ms(4294967295), hits 4294967295\n" #ifndef PROF_GROUP_NAME_LEN_ALIGNMENT #define PROF_GROUP_NAME_LEN_ALIGNMENT 16 #endif // Returns character length int GetMaxOutputLen(SQString *tag, int type) { if (tag) { return STRLEN(PROF_GROUP_OUTPUT_TEMPLATE) + ROUND(tag->_len, PROF_GROUP_NAME_LEN_ALIGNMENT) + 1; } #ifndef SQDBG_DISABLE_PROFILER_AUTO switch (type) { // call graph case 0: { const int header = STRLEN(PROF_OUTPUT_HEADER); const int bufsize = header + m_Nodes.Size() * (header - STRLEN("func") + // depth[SQDBG_CALLGRAPH_MAX_DEPTH*3]func, src (addr)\n SQDBG_CALLGRAPH_MAX_DEPTH * 3 + /*func*/ 2 + /*src*/ 1 + 2 + FMT_PTR_LEN + 1) + 1; int len = 0; for (hnode_t i = 0; i < m_NodeTags.Size(); i++) { nodetag_t *node = &m_NodeTags[i]; len += (int)node->funcsrc->_len; len += (int)node->funcname->_len; } return bufsize + len; } // flat case 1: { const int header = STRLEN(PROF_OUTPUT_HEADER); const int bufsize = header + m_Nodes.Size() * (header - STRLEN("func") + // func, src (addr)\n /*func*/ 2 + /*src*/ 1 + 2 + FMT_PTR_LEN + 1) + 1; int len = 0; for (hnode_t i = 0; i < m_NodeTags.Size(); i++) { nodetag_t *node = &m_NodeTags[i]; len += (int)node->funcsrc->_len; len += (int)node->funcname->_len; } return bufsize + len; } default: { return 0; } } #else (void)type; return 0; #endif } // Returns character length int Output(SQString *tag, int type, SQChar *buf, int size) { Assert(size > 0); if (tag) { hgroup_t idx; const group_t *group = FindGroup(tag, &idx); if (!group) return 0; const SQChar *bufstart = buf; int len = STRLEN(PROF_GROUP_OUTPUT_START); memcpy(buf, _SC(PROF_GROUP_OUTPUT_START), sq_rsl(len)); buf += len; size -= len; len = group->tag->_len; memcpy(buf, group->tag->_val, sq_rsl(len)); buf += len; size -= len; for (int i = ROUND(len, PROF_GROUP_NAME_LEN_ALIGNMENT) - len; i-- > 0;) { *buf++ = ' '; size--; } *buf++ = ':'; size--; *buf++ = ' '; size--; len = STRLEN("total "); memcpy(buf, _SC("total "), sq_rsl(len)); buf += len; size -= len; sample_t time; if (group->hits != 1) { time = group->samples - group->peak; } else { time = group->samples; } PrintTime(time, buf, size); *buf++ = ','; size--; *buf++ = ' '; size--; len = STRLEN("avg "); memcpy(buf, _SC("avg "), sq_rsl(len)); buf += len; size -= len; if (group->hits != 1) { time = (group->samples - group->peak) / (sample_t)(group->hits - 1); } else { time = group->samples / (sample_t)group->hits; } PrintTime(time, buf, size); *buf++ = ','; size--; *buf++ = ' '; size--; len = STRLEN("peak "); memcpy(buf, _SC("peak "), sq_rsl(len)); buf += len; size -= len; PrintTime(group->peak, buf, size); *buf++ = '('; size--; len = printint(buf, size, group->peakHit); buf += len; size -= len; *buf++ = ')'; size--; *buf++ = ','; size--; *buf++ = ' '; size--; len = STRLEN("hits "); memcpy(buf, _SC("hits "), sq_rsl(len)); buf += len; size -= len; len = printint(buf, size, group->hits); buf += len; size -= len; *buf++ = '\n'; *buf = 0; return (int)(buf - bufstart); } #ifndef SQDBG_DISABLE_PROFILER_AUTO sample_t sample = (m_CallStack.Size() && m_State != kProfPaused) ? Sample() : 0.0; vector nodes(m_Nodes); switch (type) { // call graph case 0: { break; } // flat // merge all calls of identical functions case 1: { hnode_t c = nodes.Size(); for (hnode_t i = 0; i < c; i++) { node_t &node = nodes[i]; node.caller = INVALID_HANDLE; for (hnode_t j = i + 1; j < c; j++) { node_t &nj = nodes[j]; if (nj.func == node.func) { node.samples += nj.samples; node.calls += nj.calls; nodes.Remove(j); j--; c--; } } } break; } default: { return 0; } } hnode_t nodecount = nodes.Size(); sample_t totalSamples = 0.0; const SQChar *bufstart = buf; nodes.Sort(_sort); for (hnode_t i = 0; i < nodecount; i++) { const node_t &node = nodes[i]; // Only accumulate parent call times if (node.caller == INVALID_HANDLE) { totalSamples += node.samples; // Within the call frame, take current time if (m_CallStack.Size() && m_CallStack.Top() == node.id && m_State != kProfPaused) { totalSamples += sample - node.sampleStart; } } } int len = STRLEN(PROF_OUTPUT_HEADER); memcpy(buf, _SC(PROF_OUTPUT_HEADER), sq_rsl(len)); buf += len; size -= len; for (hnode_t i = 0; i < nodecount; i++) { const node_t &node = nodes[i]; if (node.caller != INVALID_HANDLE) break; Assert(size > 0); DoPrint(nodes, i, totalSamples, 0, buf, size); } *buf = 0; Assert((int)scstrlen(bufstart) == (int)(buf - bufstart)); return (int)(buf - bufstart); #else (void)type; return 0; #endif } private: #ifndef SQDBG_DISABLE_PROFILER_AUTO void DoPrint(const vector &nodes, hnode_t i, sample_t totalSamples, int depth, SQChar *&buf, int &size) { const node_t &node = nodes[i]; sample_t frac = (node.samples / totalSamples) * 100.0; sample_t avg = node.samples / (sample_t)node.calls; int len; if (node.samples && isfinite(frac)) { if (frac > 100.0) frac = 100.0; len = scsprintf(buf, size, _SC("%6.2f"), frac); buf += len; size -= len; } else { *buf++ = ' '; size--; *buf++ = ' '; size--; *buf++ = ' '; size--; len = STRLEN("N/A"); memcpy(buf, _SC("N/A"), sq_rsl(len)); buf += len; size -= len; } *buf++ = ' '; size--; *buf++ = ' '; size--; PrintTime(node.samples, buf, size); *buf++ = ' '; size--; *buf++ = ' '; size--; PrintTime(avg, buf, size); *buf++ = ' '; size--; // right align len = FMT_UINT32_LEN - countdigits(node.calls); while (len--) { *buf++ = ' '; size--; } len = printint(buf, size, node.calls); buf += len; size -= len; *buf++ = ' '; size--; *buf++ = ' '; size--; STATIC_ASSERT(SQDBG_CALLGRAPH_MAX_DEPTH >= 3 && SQDBG_CALLGRAPH_MAX_DEPTH <= 9999); if (depth <= SQDBG_CALLGRAPH_MAX_DEPTH) { for (int d = depth; d--;) { *buf++ = '|'; *buf++ = ' '; *buf++ = ' '; } size -= depth * 3; } else { for (int d = SQDBG_CALLGRAPH_MAX_DEPTH - 2; d--;) { *buf++ = '|'; size--; *buf++ = ' '; size--; *buf++ = ' '; size--; } buf--; size++; if (depth < 10000) { len = printint(buf, size, depth); } else { len = STRLEN("999+"); memcpy(buf, _SC("999+"), sq_rsl(len)); } buf += len; size -= len; for (int d = ((len + 2) / 3) * 3; d-- > len;) { *buf++ = ' '; size--; } *buf++ = ' '; size--; len = ((len + 2) / 3) * 3; for (int d = ((2) * 3 - len) / 3; d--;) { *buf++ = '|'; size--; *buf++ = ' '; size--; *buf++ = ' '; size--; } *(buf - 1) = '.'; *(buf - 2) = '.'; } const nodetag_t &tag = m_NodeTags[node.id]; len = tag.funcname->_len; memcpy(buf, tag.funcname->_val, sq_rsl(len)); buf += len; size -= len; *buf++ = ','; size--; *buf++ = ' '; size--; len = tag.funcsrc->_len; memcpy(buf, tag.funcsrc->_val, sq_rsl(len)); buf += len; size -= len; if (tag.funcname->_val[0] != '0') { *buf++ = ' '; size--; *buf++ = '('; size--; len = printhex(buf, size, (uintptr_t)node.func); buf += len; size -= len; *buf++ = ')'; size--; } *buf++ = '\n'; size--; int more = 0; avg = 0.0; for (hnode_t j = i + 1; j < nodes.Size(); j++) { const node_t &nj = nodes[j]; if (nj.caller == node.id) { // Prevent stack overflow // Limit should be at most 3 digits for the depth print if (depth < 100) { DoPrint(nodes, j, totalSamples, depth + 1, buf, size); } else { avg = (more * avg + (nj.samples / (sample_t)nj.calls)) / (more + 1); more++; } } } if (more) { *buf++ = ' '; size--; *buf++ = ' '; size--; *buf++ = ' '; size--; len = STRLEN("N/A"); memcpy(buf, _SC("N/A"), sq_rsl(len)); buf += len; size -= len; *buf++ = ' '; size--; *buf++ = ' '; size--; PrintTime(NAN, buf, size); *buf++ = ' '; size--; *buf++ = ' '; size--; PrintTime(avg, buf, size); *buf++ = ' '; size--; // right align len = FMT_UINT32_LEN - STRLEN("N/A"); while (len--) { *buf++ = ' '; size--; } len = STRLEN("N/A"); memcpy(buf, _SC("N/A"), sq_rsl(len)); buf += len; size -= len; *buf++ = ' '; size--; *buf++ = ' '; size--; for (int d = SQDBG_CALLGRAPH_MAX_DEPTH; d--;) { *buf++ = '|'; *buf++ = ' '; *buf++ = ' '; } size -= SQDBG_CALLGRAPH_MAX_DEPTH * 3; *(buf - 1) = '.'; *(buf - 2) = '.'; *buf++ = ' '; size--; len = printint(buf, size, more); buf += len; size -= len; len = STRLEN(" more"); memcpy(buf, _SC(" more"), sq_rsl(len)); buf += len; size -= len; *buf++ = '\n'; size--; } } #endif // Print time and its unit to 9 chars: "000.00 ms" static void PrintTime(sample_t us, SQChar *&buf, int &size) { // because a value less than 1000.0 // is not guaranteed to print less than 1000.0 #define FIX_FLT_PRINT(unit) \ if (len == 10) \ { \ len = STRLEN("999.99 " unit); \ memcpy(buf, _SC("999.99 " unit), sq_rsl(len)); \ } if (us < 1.0) { if (us > 0.0) { int len = scsprintf(buf, size, _SC("%6.2f ns"), us * 1.e3); Assert(len <= 10); FIX_FLT_PRINT("ns"); buf += len; size -= len; } else { goto LNAN; } } else if (us < 1.e3) { int len = scsprintf(buf, size, _SC("%6.2f us"), us); Assert(len <= 10); FIX_FLT_PRINT("us"); buf += len; size -= len; } else if (us < 1.e6) { int len = scsprintf(buf, size, _SC("%6.2f ms"), us / 1.e3); Assert(len <= 10); FIX_FLT_PRINT("ms"); buf += len; size -= len; } else if (us < 60.e6 * 15.0) // 900s { int len = scsprintf(buf, size, _SC("%6.2f s"), us / 1.e6); Assert(len <= 9); buf += len; size -= len; } else if (us < 36.e8) // 60m { int len = scsprintf(buf, size, _SC("%6.2f m"), us / 6.e7); Assert(len <= 9); buf += len; size -= len; } else if (us >= 36.e8) { if (us < 6048.e8) // 24h * 7 { int len = scsprintf(buf, size, _SC("%6.2f h"), us / 36.e8); Assert(len <= 9); buf += len; size -= len; } else { if (!isfinite(us)) goto LNAN; int len = 9 - STRLEN("> 1 w"); while (len--) { *buf++ = ' '; size--; } *buf++ = '>'; size--; *buf++ = ' '; size--; *buf++ = '1'; size--; *buf++ = ' '; size--; *buf++ = 'w'; size--; } } else { LNAN: int len = 9 - STRLEN("N/A"); while (len--) { *buf++ = ' '; size--; } *buf++ = 'N'; size--; *buf++ = '/'; size--; *buf++ = 'A'; size--; } #undef FIX_FLT_PRINT } #ifndef SQDBG_DISABLE_PROFILER_AUTO static int _sort(const node_t *a, const node_t *b) { if (a->caller != b->caller) { if (a->caller == INVALID_HANDLE) return -1; if (b->caller == INVALID_HANDLE) return 1; } if (a->samples > b->samples) return -1; if (b->samples > a->samples) return 1; return 0; } #endif }; #endif // !SQDBG_DISABLE_PROFILER // // Longest return value is 16 bytes including nul byte // inline conststring_t GetType(const SQObjectPtr &obj) { switch (_RAW_TYPE(sq_type(obj))) { case _RT_NULL: return "null"; case _RT_INTEGER: return "integer"; case _RT_FLOAT: return "float"; case _RT_BOOL: return "bool"; case _RT_STRING: return "string"; case _RT_TABLE: return "table"; case _RT_ARRAY: return "array"; case _RT_GENERATOR: return "generator"; case _RT_CLOSURE: return "function"; case _RT_NATIVECLOSURE: return "native function"; case _RT_USERDATA: case _RT_USERPOINTER: return "userdata"; case _RT_THREAD: return "thread"; case _RT_FUNCPROTO: return "funcproto"; case _RT_CLASS: return "class"; case _RT_INSTANCE: return "instance"; case _RT_WEAKREF: return "weakref"; #if SQUIRREL_VERSION_NUMBER >= 300 case _RT_OUTER: return "outer"; #endif default: UNREACHABLE(); } } #if SQUIRREL_VERSION_NUMBER >= 300 conststring_t const g_InstructionName[_OP_CLOSE + 1] = { "LINE", "LOAD", "LOADINT", "LOADFLOAT", "DLOAD", "TAILCALL", "CALL", "PREPCALL", "PREPCALLK", "GETK", "MOVE", "NEWSLOT", "DELETE", "SET", "GET", "EQ", "NE", "ADD", "SUB", "MUL", "DIV", "MOD", "BITW", "RETURN", "LOADNULLS", "LOADROOT", "LOADBOOL", "DMOVE", "JMP", "JCMP", "JZ", "SETOUTER", "GETOUTER", "NEWOBJ", "APPENDARRAY", "COMPARITH", "INC", "INCL", "PINC", "PINCL", "CMP", "EXISTS", "INSTANCEOF", "AND", "OR", "NEG", "NOT", "BWNOT", "CLOSURE", "YIELD", "RESUME", "FOREACH", "POSTFOREACH", "CLONE", "TYPEOF", "PUSHTRAP", "POPTRAP", "THROW", "NEWSLOTA", "GETBASE", "CLOSE", }; #elif SQUIRREL_VERSION_NUMBER >= 212 conststring_t const g_InstructionName[_OP_NEWSLOTA + 1] = { "LINE", "LOAD", "LOADINT", "LOADFLOAT", "DLOAD", "TAILCALL", "CALL", "PREPCALL", "PREPCALLK", "GETK", "MOVE", "NEWSLOT", "DELETE", "SET", "GET", "EQ", "NE", "ARITH", "BITW", "RETURN", "LOADNULLS", "LOADROOTTABLE", "LOADBOOL", "DMOVE", "JMP", "JNZ", "JZ", "LOADFREEVAR", "VARGC", "GETVARGV", "NEWTABLE", "NEWARRAY", "APPENDARRAY", "GETPARENT", "COMPARITH", "COMPARITHL", "INC", "INCL", "PINC", "PINCL", "CMP", "EXISTS", "INSTANCEOF", "AND", "OR", "NEG", "NOT", "BWNOT", "CLOSURE", "YIELD", "RESUME", "FOREACH", "POSTFOREACH", "DELEGATE", "CLONE", "TYPEOF", "PUSHTRAP", "POPTRAP", "THROW", "CLASS", "NEWSLOTA", }; #endif conststring_t const g_MetaMethodName[MT_LAST] = { "_add", "_sub", "_mul", "_div", "_unm", "_modulo", "_set", "_get", "_typeof", "_nexti", "_cmp", "_call", "_cloned", "_newslot", "_delslot", #if SQUIRREL_VERSION_NUMBER >= 210 "_tostring", "_newmember", "_inherited", #endif }; #define KW_CALLFRAME "\xFF\xFF\xF0" #define KW_DELEGATE "\xFF\xFF\xF1" #ifdef CLOSURE_ROOT #define KW_ROOT "\xFF\xFF\xF2" #endif #define KW_THIS "__this" #define KW_VARGV "__vargv" #define KW_VARGC "__vargc" #define IS_INTERNAL_TAG(str) ((str)[0] == '$') #define INTERNAL_TAG(name) "$" name #define INTERNAL_TAG_PREFIX '$' #define ANONYMOUS_FUNCTION_BREAKPOINT_NAME "()" #define INVALID_ID -1 #define DUPLICATE_ID -2 #define ISVALID_ID(i) ((i) > 0) #define ARRAY_PAGE_LIMIT 1024 #define INVALID_FRAME -1 typedef enum { kFS_None = 0x0000, kFS_Hexadecimal = 0x0001, kFS_Binary = 0x0002, kFS_Decimal = 0x0004, kFS_Float = 0x0008, kFS_FloatE = 0x0010, kFS_FloatG = 0x0020, kFS_Octal = 0x0040, kFS_Character = 0x0080, kFS_NoQuote = 0x0100, kFS_Uppercase = 0x0200, kFS_Padding = 0x0400, kFS_NoPrefix = 0x0800, kFS_KeyVal = 0x1000, kFS_NoAddr = 0x2000, kFS_ListMembers = 0x4000, kFS_Lock = 0x8000, } VARSPEC; struct breakreason_t { typedef enum { None = 0, Step = 1, Breakpoint, Exception, Pause, Restart, Goto, FunctionBreakpoint, DataBreakpoint, } EBreakReason; EBreakReason reason; string_t text; int id; breakreason_t(EBreakReason r = None, const string_t &t = {0, 0}, int i = 0) : reason(r), text(t), id(i) { } }; struct breakpoint_t { int line; sqstring_t src; sqstring_t funcsrc; SQObjectPtr conditionFn; SQObjectPtr conditionEnv; int hitsTarget; int hits; string_t logMessage; int id; }; typedef enum { VARREF_OBJ = 0, VARREF_SCOPE_LOCALS, VARREF_SCOPE_OUTERS, VARREF_STACK, VARREF_INSTRUCTIONS, VARREF_OUTERS, VARREF_LITERALS, #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST VARREF_FUNCTIONS, #endif VARREF_METAMETHODS, VARREF_ATTRIBUTES, VARREF_CALLSTACK, VARREF_MAX } EVARREF; inline bool IsScopeRef(EVARREF type) { Assert(type >= 0 && type < VARREF_MAX); return (type == VARREF_SCOPE_LOCALS || type == VARREF_SCOPE_OUTERS || type == VARREF_STACK); } inline bool IsObjectRef(EVARREF type) { Assert(type >= 0 && type < VARREF_MAX); return (type == VARREF_OBJ || type == VARREF_INSTRUCTIONS || type == VARREF_OUTERS || type == VARREF_LITERALS || #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST type == VARREF_FUNCTIONS || #endif type == VARREF_METAMETHODS || type == VARREF_ATTRIBUTES || type == VARREF_CALLSTACK); } struct varref_t { EVARREF type; union { struct { SQWeakRef *thread; int frame; } scope; struct { SQWeakRef *weakref; bool isStrong; // temporary strong reference for inspecting vars bool hasNonStringMembers; } obj; }; int id; HSQUIRRELVM GetThread() const { Assert(IsScopeRef(type)); Assert(scope.thread); Assert(sq_type(scope.thread->_obj) == OT_THREAD); return _thread(scope.thread->_obj); } const SQObject &GetVar() const { Assert(IsObjectRef(type)); Assert(obj.weakref); Assert(!obj.isStrong || ISREFCOUNTED(sq_type(obj.weakref->_obj))); return obj.weakref->_obj; } }; struct watch_t { string_t expression; SQWeakRef *thread; int frame; }; struct classdef_t { SQClass *base; string_t name; SQObjectPtr value; SQObjectPtr metamembers; SQObjectPtr custommembers; }; struct frameid_t { int frame; int threadId; }; struct script_t { char *sourceptr; char *scriptptr; unsigned int sourcelen; unsigned int scriptlen; }; struct objref_t { typedef enum { INVALID = 0, TABLE, INSTANCE, CLASS, ARRAY, DELEGABLE_META, CUSTOMMEMBER, STACK, OUTER, #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST FUNCPROTO, #endif INT, VIRTUAL_REF, VIRTUAL_SIZE, VIRTUAL_ALLOCATED, VIRTUAL_STATE, PTR = 0x1000, READONLY = 0x2000, } EOBJREF; EOBJREF type; union { SQObjectPtr *ptr; int val; // for data watches struct { SQWeakRef *thread; int frame; int start; int end; int index; } stack; }; // Let src hold strong ref for compiler assignment expressions such as ( a.b = a = null ) SQObjectPtr src; // Let key hold strong ref for compiler newslot target SQObjectPtr key; }; struct datawatch_t { SQWeakRef *container; objref_t obj; string_t name; // Hold strong ref to be able to print its value when it's a ref counted object // if the old value is to be released, it will be done in CheckDataBreakpoints SQObjectPtr oldvalue; int hitsTarget; int hits; SQObjectPtr condition; unsigned int condtype; int id; }; struct returnvalue_t { SQObjectPtr value; SQString *funcname; uintptr_t funcptr; }; struct cachedinstr_t { #ifdef SQDBG_WEAK_INSTRUCTION_REF SQWeakRef *func; int index; #else SQInstruction *ip; #endif SQInstruction instr; }; #ifndef SQDBG_DISABLE_PROFILER struct threadprofiler_t { SQWeakRef *thread; CProfiler prof; }; #endif typedef enum { ThreadState_Running = 0, ThreadState_Suspended, ThreadState_NextStatement, ThreadState_StepOver, ThreadState_StepIn, ThreadState_StepOut, ThreadState_StepOverInstruction, ThreadState_StepInInstruction, ThreadState_StepOutInstruction, ThreadState_SuspendNow, } EThreadState; // // Squirrel doesn't read files, it usually keeps file names passed in from host programs. // DAP returns file path on breakpoints; try to construct file paths from these partial // file names. This will not work for multiple files with identical names and for files // where breakpoints were not set. // class CFilePathMap { public: struct pair_t { string_t name; string_t path; }; vector map; ~CFilePathMap() { Assert(map.Size() == 0); } void Add(CScratch *allocator, const string_t &name, const string_t &path) { for (unsigned int i = 0; i < map.Size(); i++) { pair_t &v = map[i]; if (v.name.IsEqualTo(name)) { if (!v.path.IsEqualTo(path)) { CopyString(allocator, path, &v.path); } return; } } pair_t &v = map.Append(); CopyString(allocator, name, &v.name); CopyString(allocator, path, &v.path); } pair_t *Get(const string_t &name) { for (unsigned int i = 0; i < map.Size(); i++) { pair_t &v = map[i]; if (v.name.IsEqualTo(name)) return &v; } return NULL; } void Clear(CScratch *allocator) { for (unsigned int i = 0; i < map.Size(); i++) { pair_t &v = map[i]; FreeString(allocator, &v.name); FreeString(allocator, &v.path); } map.Purge(); } }; #define Print(...) \ { \ _OutputDebugStringFmt(__VA_ARGS__); \ m_Print(m_pCurVM, __VA_ARGS__); \ } #define PrintError(...) \ { \ _OutputDebugStringFmt(__VA_ARGS__); \ m_PrintError(m_pCurVM, __VA_ARGS__); \ } struct SQDebugServer { private: EThreadState m_State; int m_nStateCalls; int m_nCalls; int m_Sequence; public: HSQUIRRELVM m_pRootVM; private: HSQUIRRELVM m_pCurVM; HSQUIRRELVM m_pStateVM; HSQUIRRELVM m_pPausedThread; SQPRINTFUNCTION m_Print; SQPRINTFUNCTION m_PrintError; SQObjectPtr m_ErrorHandler; #ifndef SQDBG_DISABLE_PROFILER CProfiler *m_pProfiler; vector m_Profilers; bool m_bProfilerEnabled; #endif bool m_bInREPL; bool m_bBreakOnExceptions; bool m_bExceptionPause; bool m_bDebugHookGuard; bool m_bDebugHookGuardAlways; #if SQUIRREL_VERSION_NUMBER < 300 bool m_bInDebugHook; #endif #ifndef SQDBG_DISABLE_COMPILER bool m_bClientColumnOffset; #endif // Ignore debug hook calls from debugger executed scripts class CCallGuard { public: SQDebugServer *dbg; HSQUIRRELVM vm; SQObjectPtr temp_reg; CCallGuard(SQDebugServer *p, HSQUIRRELVM v) : dbg(p), vm(v), temp_reg(v->temp_reg) { if (dbg->m_bDebugHookGuardAlways || !dbg->m_bInREPL) { dbg->m_bDebugHookGuard = true; } } ~CCallGuard() { dbg->m_bDebugHookGuard = false; vm->temp_reg = temp_reg; } }; private: SQObjectPtr m_sqfnGet; SQObjectPtr m_sqfnSet; SQObjectPtr m_sqfnNewSlot; SQObjectPtr m_EnvGetVal; SQObjectPtr m_sqstrCallFrame; SQObjectPtr m_sqstrDelegate; #ifdef CLOSURE_ROOT SQObjectPtr m_sqstrRoot; #endif int m_nBreakpointIndex; int m_nVarRefIndex; unsigned int m_iYieldValues; unsigned int m_nFunctionBreakpointsIdx; vector m_CachedInstructions; vector m_ReturnValues; vector m_Vars; vector m_LockedWatches; vector m_Breakpoints; vector m_DataWatches; vector m_ClassDefinitions; vector m_Threads; vector m_FrameIDs; CFilePathMap m_FilePathMap; vector m_Scripts; CBuffer m_SendBuf; CScratch m_ReadBuf; CMemory m_Scratch; CMemory m_VarMemberCache; CScratch m_Strings; CServerSocket m_Server; public: char *ScratchPad(unsigned int size) { m_Scratch.Ensure(size); return m_Scratch.Base(); } stringbufext_t ScratchPadBuf(unsigned int size) { m_Scratch.Ensure(size); return {m_Scratch.Base(), m_Scratch.Base() ? size : 0}; } public: void Attach(HSQUIRRELVM vm); void SetErrorHandler(bool state); void DoSetDebugHook(HSQUIRRELVM vm, _SQDEBUGHOOK fn); void SetDebugHook(_SQDEBUGHOOK fn); bool ListenSocket(unsigned short port); void Shutdown(); void DisconnectClient(); void OnClientConnected(const char *addr); void Frame(); bool IsClientConnected() { return m_Server.IsClientConnected(); } private: void PrintLastServerMessage() { #ifdef SQUNICODE int len = 0; SQChar wcs[256]; #define _bs (int)(sizeof(wcs) - len * sizeof(SQChar)) #define _bl (int)(sizeof(wcs) / sizeof(SQChar)) len = UTF8ToSQUnicode(wcs, _bs, m_Server.m_pszLastMsgFmt, strlen(m_Server.m_pszLastMsgFmt)); if (m_Server.m_pszLastMsg && len < _bl - 2) { wcs[len++] = ' '; wcs[len++] = '('; len += UTF8ToSQUnicode(wcs + len, _bs, m_Server.m_pszLastMsg, strlen(m_Server.m_pszLastMsg)); if (len < _bl - 2) { wcs[len++] = ')'; wcs[len++] = '\n'; } } else if (len < _bl - 1) { wcs[len++] = '\n'; } wcs[min(len, _bl - 1)] = 0; #undef _bs #undef _bl PrintError(wcs); #else stringbuf_t<256> buf; buf.Puts({m_Server.m_pszLastMsgFmt, (unsigned int)strlen(m_Server.m_pszLastMsgFmt)}); if (m_Server.m_pszLastMsg) { buf.Put(' '); buf.Put('('); buf.Puts({m_Server.m_pszLastMsg, (unsigned int)strlen(m_Server.m_pszLastMsg)}); buf.Put(')'); } buf.Put('\n'); buf.Term(); PrintError(buf.ptr); #endif m_Server.m_pszLastMsg = NULL; } void Recv() { if (!m_Server.Recv()) { PrintLastServerMessage(); DisconnectClient(); } } void Parse() { if (!m_Server.Parse()) { PrintLastServerMessage(); DisconnectClient(); } } void Send(const char *buf, int len) { if (m_Server.IsClientConnected() && !m_Server.Send(buf, len)) { PrintLastServerMessage(); DisconnectClient(); } } void OnMessageReceived(char *ptr, int len); void ProcessRequest(const json_table_t &table, int seq); void ProcessResponse(const json_table_t &table, int seq); void ProcessEvent(const json_table_t &table); void OnRequest_Initialize(const json_table_t &arguments, int seq); void OnRequest_SetBreakpoints(const json_table_t &arguments, int seq); void OnRequest_SetFunctionBreakpoints(const json_table_t &arguments, int seq); void OnRequest_SetExceptionBreakpoints(const json_table_t &arguments, int seq); void OnRequest_SetDataBreakpoints(const json_table_t &arguments, int seq); void OnRequest_DataBreakpointInfo(const json_table_t &arguments, int seq); #ifndef SQDBG_DISABLE_COMPILER void OnRequest_Completions(const json_table_t &arguments, int seq); #endif void OnRequest_Evaluate(const json_table_t &arguments, int seq); void OnRequest_Scopes(const json_table_t &arguments, int seq); void OnRequest_Threads(int seq); void OnRequest_StackTrace(const json_table_t &arguments, int seq); void OnRequest_Variables(const json_table_t &arguments, int seq); void OnRequest_SetVariable(const json_table_t &arguments, int seq); #ifdef SQDBG_SUPPORTS_SET_INSTRUCTION void OnRequest_SetVariable_Instruction(const varref_t *ref, string_t &strName, string_t &strValue, int seq); #endif void OnRequest_SetExpression(const json_table_t &arguments, int seq); void OnRequest_Disassemble(const json_table_t &arguments, int seq); #ifdef SUPPORTS_RESTART_FRAME void OnRequest_RestartFrame(const json_table_t &arguments, int seq); #endif void OnRequest_GotoTargets(const json_table_t &arguments, int seq); void OnRequest_Goto(const json_table_t &arguments, int seq); void OnRequest_Next(const json_table_t &arguments, int seq); void OnRequest_StepIn(const json_table_t &arguments, int seq); void OnRequest_StepOut(const json_table_t &arguments, int seq); private: int AddBreakpoint(int line, const string_t &src, const string_t &condition, int hitsTarget, const string_t &logMessage); int AddFunctionBreakpoint(const string_t &func, const string_t &funcsrc, int line, const string_t &condition, int hitsTarget, const string_t &logMessage); breakpoint_t *GetBreakpoint(int line, const sqstring_t &src); breakpoint_t *GetFunctionBreakpoint(const sqstring_t &func, const sqstring_t &funcsrc, int line); void FreeBreakpoint(breakpoint_t &bp); static inline bool HasCondition(const breakpoint_t *bp); bool CheckBreakpointCondition(breakpoint_t *bp, HSQUIRRELVM vm, const SQVM::CallInfo *ci); int EvalAndWriteExpr(HSQUIRRELVM vm, int frame, string_t &expression, char *buf, int size); void TracePoint(breakpoint_t *bp, HSQUIRRELVM vm, int frame); enum { ECMP_NONE = 0, ECMP_EQ = 0x01, ECMP_NE = 0x02, ECMP_G = 0x04, ECMP_GE = ECMP_G | ECMP_EQ, ECMP_L = 0x08, ECMP_LE = ECMP_L | ECMP_EQ, ECMP_BWA = 0x10, ECMP_BWAZ = 0x20, ECMP_BWAEQ = 0x40, }; int CompareObj(const SQObjectPtr &lhs, const SQObjectPtr &rhs); bool CompileDataBreakpointCondition(string_t condition, SQObjectPtr &out, unsigned int &type); int AddDataBreakpoint(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &dataId, const string_t &condition, int hitsTarget); bool CheckDataBreakpoints(HSQUIRRELVM vm); void FreeDataWatch(datawatch_t &dw); inline void RemoveAllBreakpoints(); inline void RemoveBreakpoints(const string_t &source); inline void RemoveFunctionBreakpoints(); inline void RemoveDataBreakpoints(); bool InstructionStep(HSQUIRRELVM vm, SQVM::CallInfo *ci, int instrOffset = 0); bool Step(HSQUIRRELVM vm, SQVM::CallInfo *ci); #ifdef SQDBG_WEAK_INSTRUCTION_REF void CacheInstruction(SQFunctionProto *func, SQInstruction *instr); #else void CacheInstruction(SQInstruction *instr); #endif void ClearCachedInstructions(); void RestoreCachedInstructions(); void UndoRestoreCachedInstructions(); void SetSource(wjson_table_t &source, SQString *sourcename); public: static inline bool IsJumpOp(const SQInstruction *instr); static inline int GetJumpCount(const SQInstruction *instr); static int DeduceJumpCount(const SQInstruction *instr); private: static inline bool IsValidStackFrame(HSQUIRRELVM vm, int frame); static inline SQVM::CallInfo *GetStackFrame(HSQUIRRELVM vm, int frame); static inline int GetStackBase(HSQUIRRELVM vm, const SQVM::CallInfo *ci); static inline int GetStackBase(HSQUIRRELVM vm, int frame) { return GetStackBase(vm, vm->_callsstack + frame); } static HSQUIRRELVM GetThread(SQWeakRef *wr) { Assert(wr); Assert(sq_type(wr->_obj) == OT_THREAD); Assert(_thread(wr->_obj)); return _thread(wr->_obj); } static SQWeakRef *GetWeakRef(HSQUIRRELVM vm) { return GetWeakRef(vm, OT_THREAD); } static SQWeakRef *GetWeakRef(SQRefCounted *obj, SQObjectType type) { return obj->GetWeakRef(type); } string_t GetValue(const SQObject &obj, int flags = 0); conststring_t GetValue(SQInteger val, int flags = 0) { SQObject obj; obj._type = OT_INTEGER; obj._unVal.nInteger = val; string_t str = GetValue(obj, flags & ~kFS_Character); conststring_t ret; ret.ptr = str.ptr; ret.len = str.len; return ret; } static SQObject ToSQObject(SQClass *val) { SQObject obj; obj._type = OT_CLASS; obj._unVal.pClass = val; return obj; } static SQObject ToSQObject(SQTable *val) { SQObject obj; obj._type = OT_TABLE; obj._unVal.pTable = val; return obj; } static SQObject ToSQObject(SQFunctionProto *val) { SQObject obj; obj._type = OT_FUNCPROTO; obj._unVal.pFunctionProto = val; return obj; } void JSONSetString(wjson_table_t &elem, const string_t &key, const SQObject &obj, int flags = 0); #ifndef SQDBG_DISABLE_COMPILER public: class CCompiler; enum ECompileReturnCode { CompileReturnCode_Success, // Lookup failed CompileReturnCode_DoesNotExist, CompileReturnCode_CallError, // String/number parsing failed CompileReturnCode_SyntaxError, CompileReturnCode_Fallback, CompileReturnCode_OpFailure, CompileReturnCode_NoValue, // Unrecognised token or token sequences CompileReturnCode_Unsupported, // Valid but too many parameters in a function call CompileReturnCode_CallBufferFull, // Valid but too many unary operators in a row CompileReturnCode_OpBufferFull, }; // NOTE: Expression string will be modified if it contains a string with escape characters // This may cause RunExpression fallback after Evaluate to fail // This can be avoided by exiting compilation before escaped strings are parsed // by putting "0," at the beginning of the expression - this is valid squirrel while not allowed here ECompileReturnCode Evaluate(string_t &expression, HSQUIRRELVM vm, int frame, SQObjectPtr &ret); ECompileReturnCode Evaluate(string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret) { return Evaluate(expression, vm, ci ? ci - vm->_callsstack : INVALID_FRAME, ret); } ECompileReturnCode Evaluate(string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret, objref_t &obj); private: static inline SQTable *GetDefaultDelegate(HSQUIRRELVM vm, SQObjectType type); bool ArithOp(char op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out); bool NewSlot(const objref_t &obj, const SQObjectPtr &value); bool Delete(const objref_t &obj, SQObjectPtr &value); bool Increment(const objref_t &obj, int amt); #endif private: static inline void ConvertPtr(objref_t &obj); bool GetObj_VarRef(const varref_t *ref, string_t &expression, objref_t &out, SQObjectPtr &value); bool GetObj_Var(const SQObjectPtr &var, const SQObjectPtr &key, objref_t &out, SQObjectPtr &value); bool GetObj_Var(const SQObjectPtr &var, string_t &expression, bool identifierIsString, objref_t &out, SQObjectPtr &value); bool GetObj_Frame(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &expression, objref_t &out, SQObjectPtr &value); bool GetObj_Frame(HSQUIRRELVM vm, int frame, const string_t &expression, objref_t &out, SQObjectPtr &value) { return GetObj_Frame(vm, GetStackFrame(vm, frame), expression, out, value); } bool Get(const objref_t &obj, SQObjectPtr &value); bool Set(const objref_t &obj, const SQObjectPtr &value); private: bool RunExpression(const string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &out, bool multiline = false); bool RunExpression(const string_t &expression, HSQUIRRELVM vm, int frame, SQObjectPtr &out, bool multiline = false) { return RunExpression(expression, vm, GetStackFrame(vm, frame), out, multiline); } bool CompileScript(const string_t &script, SQObjectPtr &out); bool RunScript(HSQUIRRELVM vm, const string_t &script, #ifdef CLOSURE_ROOT SQWeakRef *root, #endif const SQObject *env, SQObjectPtr &out, bool multiline = false); bool RunClosure(const SQObjectPtr &closure, const SQObject *env, SQObjectPtr &ret) { CCallGuard cg(this, m_pCurVM); return RunClosure(m_pCurVM, closure, env, ret); } bool RunClosure(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, SQObjectPtr &ret) { CCallGuard cg(this, m_pCurVM); return RunClosure(m_pCurVM, closure, env, p1, ret); } bool RunClosure(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret) { CCallGuard cg(this, m_pCurVM); return RunClosure(m_pCurVM, closure, env, p1, p2, ret); } bool RunClosure(const SQObjectPtr &closure, const SQObjectPtr *argv, int argc, SQObjectPtr &ret) { CCallGuard cg(this, m_pCurVM); return RunClosure(m_pCurVM, closure, argv, argc, ret); } static inline bool RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, SQObjectPtr &ret); static inline bool RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, SQObjectPtr &ret); static inline bool RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret); static inline bool RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObjectPtr *argv, int argc, SQObjectPtr &ret); static SQInteger SQMM_Get(HSQUIRRELVM vm); static SQInteger SQMM_Set(HSQUIRRELVM vm); static SQInteger SQMM_NewSlot(HSQUIRRELVM vm); static bool GetVariable(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQObject &mtenv, const SQObject &index, SQObjectPtr &out); static bool SetVariable(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQObject &mtenv, const SQObject &index, const SQObject &value); static bool NewSlot(HSQUIRRELVM vm, const SQObject &mtenv, const SQObject &index, const SQObject &value); private: script_t *GetScript(const string_t &source); void RemoveScripts(); public: void OnScriptCompile(const SQChar *script, unsigned int scriptlen, const SQChar *sourcename, unsigned int sourcenamelen); private: static bool ParseEvaluateName(const string_t &expression, HSQUIRRELVM vm, int frame, objref_t &out, SQObjectPtr &value); static int ParseFormatSpecifiers(string_t &expression, char **ppComma = NULL); static bool ParseBinaryNumber(const string_t &value, SQObject &out); static inline bool ShouldParseEvaluateName(const string_t &expression); static inline bool ShouldPageArray(const SQObject &obj, unsigned int limit); #ifndef SQDBG_DISABLE_COMPILER void FillCompletions(const SQObjectPtr &target, HSQUIRRELVM vm, int frame, int token, const string_t &partial, int start, int length, wjson_array_t &targets); #endif private: void InitEnv_GetVal(SQObjectPtr &env); void SetCallFrame(SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci); void SetEnvDelegate(SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci); void ClearEnvDelegate(SQObjectPtr &env); #ifdef CLOSURE_ROOT void SetEnvRoot(SQObjectPtr &env, const SQObjectPtr &root); #endif private: static inline bool ShouldIgnoreStackFrame(HSQUIRRELVM vm, const SQVM::CallInfo &ci); int ConvertToFrameID(int threadId, int stackFrame); bool TranslateFrameID(int frameId, HSQUIRRELVM *thread, int *stackFrame); int ThreadToID(HSQUIRRELVM vm); HSQUIRRELVM ThreadFromID(int id); inline void RemoveThreads(); private: int ToVarRef(EVARREF type, HSQUIRRELVM vm, int frame); int ToVarRef(EVARREF type, const SQObject &obj, bool isStrong = false); int ToVarRef(const SQObject &obj, bool isStrong = false) { return ToVarRef(VARREF_OBJ, obj, isStrong); } static inline void ConvertToWeakRef(varref_t &v); inline varref_t *FromVarRef(int i); inline void RemoveVarRefs(bool all); inline void RemoveLockedWatches(); inline void RemoveReturnValues(); void Suspend(); void Break(HSQUIRRELVM vm, breakreason_t reason); void Continue(HSQUIRRELVM vm); classdef_t *FindClassDef(SQClass *base); // Fallback to base class const SQObjectPtr *GetClassDefValue(SQClass *base); const SQObjectPtr *GetClassDefMetaMembers(SQClass *base); const SQObjectPtr *GetClassDefCustomMembers(SQClass *base); bool CallCustomMembersGetFunc(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &key, SQObjectPtr &ret); bool CallCustomMembersSetFunc(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &key, const SQObjectPtr &val, SQObjectPtr &ret); void DefineClass(SQClass *target, SQTable *params); inline void RemoveClassDefs(); private: static SQUnsignedInteger s_nTargetAssignment; static inline SQString *GetLocalVarName(const SQFunctionProto *func, const SQInstruction *instr, unsigned int pos); static inline void PrintStackVar(const SQFunctionProto *func, const SQInstruction *instr, unsigned int pos, stringbufext_t &buf); static inline void PrintOuter(const SQFunctionProto *func, int pos, stringbufext_t &buf); inline void PrintLiteral(const SQFunctionProto *func, int pos, stringbufext_t &buf); static inline void PrintStackTarget(const SQFunctionProto *func, const SQInstruction *instr, stringbufext_t &buf); static inline void PrintStackTargetVar(const SQFunctionProto *func, const SQInstruction *instr, stringbufext_t &buf); static inline void PrintDeref(const SQFunctionProto *func, const SQInstruction *instr, unsigned int self, unsigned int key, stringbufext_t &buf); void DescribeInstruction(const SQInstruction *instr, const SQFunctionProto *func, stringbufext_t &buf); inline int DisassemblyBufLen(SQClosure *target); sqstring_t PrintDisassembly(SQClosure *target, SQChar *scratch, int bufsize); #ifndef SQDBG_DISABLE_PROFILER public: bool IsProfilerEnabled() { return m_bProfilerEnabled; } CProfiler *GetProfiler(HSQUIRRELVM vm); inline CProfiler *GetProfilerFast(HSQUIRRELVM vm); void ProfSwitchThread(HSQUIRRELVM vm); void ProfStart(); void ProfStop(); void ProfPause(HSQUIRRELVM vm); void ProfResume(HSQUIRRELVM vm); void ProfReset(HSQUIRRELVM vm, SQString *tag); void ProfGroupBegin(HSQUIRRELVM vm, SQString *tag); void ProfGroupEnd(HSQUIRRELVM vm); sqstring_t ProfGets(HSQUIRRELVM vm, SQString *tag, int type); void ProfPrint(HSQUIRRELVM vm, SQString *tag, int type); #endif private: inline void StepOutInstruction(HSQUIRRELVM vm, SQVM::CallInfo *ci); void ErrorHandler(HSQUIRRELVM vm); void DebugHook(HSQUIRRELVM vm, int type, const SQChar *sourcename, int line, const SQChar *funcname); #ifndef SQDBG_DISABLE_PROFILER_AUTO void ProfHook(HSQUIRRELVM vm, int type); #endif void OnSQPrint(HSQUIRRELVM vm, const SQChar *buf, int len); void OnSQError(HSQUIRRELVM vm, const SQChar *buf, int len); template void SendEvent_OutputStdOut(const T &strOutput, const SQVM::CallInfo *ci); public: static SQInteger SQDefineClass(HSQUIRRELVM vm); static SQInteger SQPrintDisassembly(HSQUIRRELVM vm); static SQInteger SQBreak(HSQUIRRELVM vm); #ifndef SQDBG_DISABLE_COMPILER static SQInteger SQAddDataBreakpoint(HSQUIRRELVM vm); #endif #ifndef SQDBG_DISABLE_EVAL_FUNC static SQInteger SQEval(HSQUIRRELVM vm); #endif #ifndef SQDBG_DISABLE_PROFILER static SQInteger SQProfStart(HSQUIRRELVM vm); static SQInteger SQProfStop(HSQUIRRELVM vm); static SQInteger SQProfPause(HSQUIRRELVM vm); static SQInteger SQProfResume(HSQUIRRELVM vm); static SQInteger SQProfReset(HSQUIRRELVM vm); static SQInteger SQProfGroupBegin(HSQUIRRELVM vm); static SQInteger SQProfGroupEnd(HSQUIRRELVM vm); static SQInteger SQProfGets(HSQUIRRELVM vm); static SQInteger SQProfPrint(HSQUIRRELVM vm); #endif static void SQPrint(HSQUIRRELVM vm, const SQChar *fmt, ...); static void SQError(HSQUIRRELVM vm, const SQChar *fmt, ...); #ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER static void SQErrorAtFrame(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQChar *fmt, ...); void PrintVar(HSQUIRRELVM vm, const SQChar *name, const SQObjectPtr &obj); void PrintStack(HSQUIRRELVM vm); #endif static SQInteger SQErrorHandler(HSQUIRRELVM vm); #ifdef NATIVE_DEBUG_HOOK static void SQDebugHook(HSQUIRRELVM vm, SQInteger type, const SQChar *sourcename, SQInteger line, const SQChar *funcname); #else static SQInteger SQDebugHook(HSQUIRRELVM vm); #endif #ifndef SQDBG_DISABLE_PROFILER_AUTO #ifdef NATIVE_DEBUG_HOOK static void SQProfHook(HSQUIRRELVM vm, SQInteger type, const SQChar *sourcename, SQInteger line, const SQChar *funcname); #else static SQInteger SQProfHook(HSQUIRRELVM vm); #endif #endif }; inline SQString *CreateSQString(SQDebugServer *dbg, const string_t &str) { #ifdef SQUNICODE unsigned int len = SQUnicodeLength(str.ptr, str.len) + 1; SQChar *tmp = (SQChar *)dbg->ScratchPad(sq_rsl(len)); if (!tmp) return NULL; len = UTF8ToSQUnicode(tmp, sq_rsl(len), str.ptr, str.len); tmp[len] = 0; return SQString::Create(_ss(dbg->m_pRootVM), tmp, len); #else return SQString::Create(_ss(dbg->m_pRootVM), str.ptr, str.len); #endif } inline bool SQTable_Get(SQDebugServer *dbg, SQTable *table, const string_t &key, SQObjectPtr &val) { #ifdef SQUNICODE unsigned int len = SQUnicodeLength(key.ptr, key.len) + 1; SQChar *tmp = (SQChar *)dbg->ScratchPad(sq_rsl(len)); if (!tmp) return NULL; len = UTF8ToSQUnicode(tmp, sq_rsl(len), key.ptr, key.len); tmp[len] = 0; #if SQUIRREL_VERSION_NUMBER >= 310 return table->GetStr(tmp, len, val); #else SQObjectPtr str = SQString::Create(table->_sharedstate, tmp, len); return table->Get(str, val); #endif #else (void)dbg; return SQTable_Get(table, key, val); #endif } void SQDebugServer::Attach(HSQUIRRELVM vm) { if (m_pRootVM) { if (m_pRootVM == _thread(_ss(vm)->_root_vm)) { Print(_SC("(sqdbg) Debugger is already attached to this VM\n")); } else { Print(_SC("(sqdbg) Debugger is already attached to another VM\n")); } return; } m_pRootVM = _thread(_ss(vm)->_root_vm); m_pCurVM = vm; Assert(m_Threads.Size() == 0); ThreadToID(m_pRootVM); #if SQUIRREL_VERSION_NUMBER >= 300 m_Print = sq_getprintfunc(m_pRootVM); m_PrintError = sq_geterrorfunc(m_pRootVM); #else m_Print = sq_getprintfunc(m_pRootVM); m_PrintError = m_Print; #endif Assert(m_Print && m_PrintError); m_ErrorHandler = m_pRootVM->_errorhandler; if (sq_type(m_ErrorHandler) != OT_NULL) sq_addref(m_pRootVM, &m_ErrorHandler); sq_enabledebuginfo(m_pRootVM, 1); SQString *cached = CreateSQString(m_pRootVM, _SC("sqdbg")); __ObjAddRef(cached); m_sqstrCallFrame = CreateSQString(m_pRootVM, _SC(KW_CALLFRAME)); m_sqstrDelegate = CreateSQString(m_pRootVM, _SC(KW_DELEGATE)); #ifdef CLOSURE_ROOT m_sqstrRoot = CreateSQString(m_pRootVM, _SC(KW_ROOT)); #endif #if SQUIRREL_VERSION_NUMBER >= 300 m_sqfnGet = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_Get, 0); m_sqfnSet = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_Set, 0); m_sqfnNewSlot = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_NewSlot, 0); #else m_sqfnGet = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_Get); m_sqfnSet = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_Set); m_sqfnNewSlot = SQNativeClosure::Create(_ss(m_pRootVM), SQMM_NewSlot); #endif _nativeclosure(m_sqfnGet)->_nparamscheck = 2; _nativeclosure(m_sqfnSet)->_nparamscheck = 3; _nativeclosure(m_sqfnNewSlot)->_nparamscheck = 3; InitEnv_GetVal(m_EnvGetVal); sq_addref(m_pRootVM, &m_EnvGetVal); { STACKCHECK(m_pRootVM); SQObjectPtr ref; sqdbg_get_debugger_ref(m_pRootVM, ref); sq_pushroottable(m_pRootVM); sq_pushstring(m_pRootVM, _SC("sqdbg_define_class"), STRLEN("sqdbg_define_class")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQDefineClass, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_define_class")); sq_setparamscheck(m_pRootVM, 3, _SC(".yt")); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_disassemble"), STRLEN("sqdbg_disassemble")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQPrintDisassembly, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_disassemble")); sq_setparamscheck(m_pRootVM, 2, _SC(".c")); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_break"), STRLEN("sqdbg_break")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQBreak, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_break")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); #ifndef SQDBG_DISABLE_COMPILER sq_pushstring(m_pRootVM, _SC("sqdbg_watch"), STRLEN("sqdbg_watch")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQAddDataBreakpoint, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_watch")); sq_setparamscheck(m_pRootVM, -2, _SC(".ssi")); sq_newslot(m_pRootVM, -3, SQFalse); #endif #ifndef SQDBG_DISABLE_EVAL_FUNC sq_pushstring(m_pRootVM, _SC("sqdbg_eval"), STRLEN("sqdbg_eval")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQEval, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_eval")); sq_setparamscheck(m_pRootVM, -2, _SC(".s")); sq_newslot(m_pRootVM, -3, SQFalse); #endif #ifndef SQDBG_DISABLE_PROFILER sq_pushstring(m_pRootVM, _SC("sqdbg_prof_start"), STRLEN("sqdbg_prof_start")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfStart, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_start")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_stop"), STRLEN("sqdbg_prof_stop")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfStop, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_stop")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_pause"), STRLEN("sqdbg_prof_pause")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfPause, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_pause")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_resume"), STRLEN("sqdbg_prof_resume")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfResume, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_resume")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_reset"), STRLEN("sqdbg_prof_reset")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfReset, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_reset")); sq_setparamscheck(m_pRootVM, -1, _SC(".v|ss")); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_begin"), STRLEN("sqdbg_prof_begin")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfGroupBegin, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_begin")); sq_setparamscheck(m_pRootVM, 2, _SC(".s")); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_end"), STRLEN("sqdbg_prof_end")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfGroupEnd, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_end")); sq_setparamscheck(m_pRootVM, 1, NULL); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_gets"), STRLEN("sqdbg_prof_gets")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfGets, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_gets")); sq_setparamscheck(m_pRootVM, -1, _SC(".v|i|si|s")); sq_newslot(m_pRootVM, -3, SQFalse); sq_pushstring(m_pRootVM, _SC("sqdbg_prof_print"), STRLEN("sqdbg_prof_print")); sq_pushobject(m_pRootVM, ref); sq_newclosure(m_pRootVM, &SQDebugServer::SQProfPrint, 1); sq_setnativeclosurename(m_pRootVM, -1, _SC("sqdbg_prof_print")); sq_setparamscheck(m_pRootVM, -2, _SC(".v|i|si|s")); sq_newslot(m_pRootVM, -3, SQFalse); #endif sq_poptop(m_pRootVM); } Print(_SC("(sqdbg) [%d] Attached\n"), SQDBG_SV_VER); } #define FOREACH_THREAD_BEGIN(_vm) \ for (unsigned int i = m_Threads.Size(); i--;) \ { \ SQWeakRef *wr = m_Threads[i]; \ if (wr && sq_type(wr->_obj) == OT_THREAD) \ { \ HSQUIRRELVM _vm = _thread(wr->_obj); #define FOREACH_THREAD_END() \ } \ } void SQDebugServer::SetErrorHandler(bool state) { FOREACH_THREAD_BEGIN(vm) if (state) { sq_newclosure(vm, &SQErrorHandler, 0); #ifdef SQDBG_CALL_DEFAULT_ERROR_HANDLER sq_setnativeclosurename(vm, -1, _SC("sqdbg")); #endif sq_seterrorhandler(vm); } else { sq_pushobject(vm, m_ErrorHandler); sq_seterrorhandler(vm); } FOREACH_THREAD_END() } #ifdef NATIVE_DEBUG_HOOK #ifdef DEBUG_HOOK_CACHED_SQDBG static inline void sqdbg_set_debugger_cached_debughook(HSQUIRRELVM vm, bool state); #else #define sqdbg_set_debugger_cached_debughook(vm, state) (void)0 #endif #endif void SQDebugServer::DoSetDebugHook(HSQUIRRELVM vm, _SQDEBUGHOOK fn) { #ifdef NATIVE_DEBUG_HOOK sq_setnativedebughook(vm, fn); sqdbg_set_debugger_cached_debughook(vm, fn != NULL); #else if (fn) { SQObjectPtr ref; sqdbg_get_debugger_ref(vm, ref); sq_pushobject(vm, ref); sq_newclosure(vm, fn, 1); } else { sq_pushnull(vm); } sq_setdebughook(vm); #endif } void SQDebugServer::SetDebugHook(_SQDEBUGHOOK fn) { FOREACH_THREAD_BEGIN(vm) DoSetDebugHook(vm, fn); FOREACH_THREAD_END() } bool SQDebugServer::ListenSocket(unsigned short port) { Assert(m_pRootVM); if (m_Server.IsListening()) { port = m_Server.GetServerPort(); if (port) { Print(_SC("(sqdbg) Socket already open on port %d\n"), port); } else { Print(_SC("(sqdbg) Socket already open\n")); } return true; } if (!m_Server.ListenSocket(port)) { PrintLastServerMessage(); return false; } port = m_Server.GetServerPort(); Print(_SC("(sqdbg) Listening for connections on port %d\n"), port); return true; } void SQDebugServer::Shutdown() { if (!m_pRootVM) return; Print(_SC("(sqdbg) Shutdown\n")); if (IsClientConnected()) { m_Server.Execute(this); DAP_START_EVENT(++m_Sequence, "terminated"); DAP_SEND(); } m_Server.Shutdown(); #ifndef SQDBG_DISABLE_PROFILER if (IsProfilerEnabled()) ProfStop(); Assert(m_Profilers.Size() == 0); m_Profilers.Purge(); #endif SetErrorHandler(false); SetDebugHook(NULL); m_State = ThreadState_Running; m_Sequence = 0; m_nBreakpointIndex = 0; m_nVarRefIndex = 0; m_nCalls = 0; m_bInREPL = false; m_bExceptionPause = false; m_bDebugHookGuard = false; m_bDebugHookGuardAlways = false; #if SQUIRREL_VERSION_NUMBER < 300 m_bInDebugHook = false; #endif m_pPausedThread = NULL; RemoveReturnValues(); RemoveVarRefs(true); RemoveLockedWatches(); RemoveAllBreakpoints(); RemoveDataBreakpoints(); RestoreCachedInstructions(); ClearCachedInstructions(); m_CachedInstructions.Purge(); m_ReturnValues.Purge(); m_Breakpoints.Purge(); m_DataWatches.Purge(); RemoveThreads(); RemoveClassDefs(); RemoveScripts(); m_FrameIDs.Purge(); m_FilePathMap.Clear(&m_Strings); m_SendBuf.Purge(); m_ReadBuf.Free(); m_Scratch.Free(); m_VarMemberCache.Free(); m_Strings.Free(); sq_release(m_pRootVM, &m_EnvGetVal); m_EnvGetVal.Null(); m_sqfnGet.Null(); m_sqfnSet.Null(); m_sqfnNewSlot.Null(); SQString *cached = CreateSQString(m_pRootVM, _SC("sqdbg")); __ObjRelease(cached); m_sqstrCallFrame.Null(); m_sqstrDelegate.Null(); #ifdef CLOSURE_ROOT m_sqstrRoot.Null(); #endif if (sq_type(m_ErrorHandler) != OT_NULL) { sq_release(m_pRootVM, &m_ErrorHandler); m_ErrorHandler.Null(); } #if SQUIRREL_VERSION_NUMBER >= 300 sq_setprintfunc(m_pRootVM, m_Print, m_PrintError); #else sq_setprintfunc(m_pRootVM, m_Print); #endif m_Print = m_PrintError = NULL; sq_enabledebuginfo(m_pRootVM, 0); sq_notifyallexceptions(m_pRootVM, 0); m_pRootVM = m_pCurVM = NULL; } void SQDebugServer::DisconnectClient() { if (IsClientConnected()) { Print(_SC("(sqdbg) Client disconnected\n")); DAP_START_EVENT(++m_Sequence, "terminated"); DAP_SEND(); } m_Server.DisconnectClient(); SetErrorHandler(false); #ifndef SQDBG_DISABLE_PROFILER_AUTO SetDebugHook(IsProfilerEnabled() ? &SQProfHook : NULL); #else SetDebugHook(NULL); #endif m_State = ThreadState_Running; m_Sequence = 0; m_nBreakpointIndex = 0; m_nVarRefIndex = 0; m_nCalls = 0; m_bInREPL = false; m_bExceptionPause = false; m_bDebugHookGuard = false; m_bDebugHookGuardAlways = false; #if SQUIRREL_VERSION_NUMBER < 300 m_bInDebugHook = false; #endif m_pPausedThread = NULL; RemoveReturnValues(); RemoveVarRefs(true); RemoveLockedWatches(); RemoveAllBreakpoints(); RemoveDataBreakpoints(); RestoreCachedInstructions(); ClearCachedInstructions(); m_CachedInstructions.Purge(); m_ReturnValues.Purge(); m_Breakpoints.Purge(); m_DataWatches.Purge(); m_SendBuf.Purge(); m_ReadBuf.Free(); m_Scratch.Free(); m_VarMemberCache.Free(); ClearEnvDelegate(m_EnvGetVal); #if SQUIRREL_VERSION_NUMBER >= 300 sq_setprintfunc(m_pRootVM, m_Print, m_PrintError); #else sq_setprintfunc(m_pRootVM, m_Print); #endif } void SQDebugServer::OnClientConnected(const char *addr) { Print(_SC("(sqdbg) Client connected from " FMT_CSTR "\n"), addr); #if SQUIRREL_VERSION_NUMBER >= 300 sq_setprintfunc(m_pRootVM, SQPrint, SQError); #else sq_setprintfunc(m_pRootVM, SQPrint); #endif SetErrorHandler(true); SetDebugHook(&SQDebugHook); // Validate if user has manually ruined it InitEnv_GetVal(m_EnvGetVal); #define _check(var, size) \ if (var.Capacity() < size) \ var.Reserve(size); _check(m_ReturnValues, 1); _check(m_Vars, 64); _check(m_FrameIDs, 8); m_SendBuf.Reserve(16384); #undef _check } void SQDebugServer::Frame() { if (m_Server.IsClientConnected()) { Recv(); Parse(); m_Server.Execute(this); } else if (m_Server.Listen()) { OnClientConnected(m_Server.m_pszLastMsg); m_Server.m_pszLastMsg = NULL; } } #define GET_OR_FAIL(_base, _val) \ if (!(_base).Get(#_val, &_val)) \ { \ PrintError(_SC("(sqdbg) invalid DAP message, could not find '" #_val "'\n")); \ DisconnectClient(); \ return; \ } #define GET_OR_ERROR_RESPONSE(_cmd, _base, _val) \ if (!(_base).Get(#_val, &_val)) \ { \ PrintError(_SC("(sqdbg) invalid DAP message, could not find '" #_val "'\n")); \ DAP_ERROR_RESPONSE(seq, _cmd); \ DAP_ERROR_BODY(0, "invalid DAP message"); \ DAP_SEND(); \ return; \ } void SQDebugServer::OnMessageReceived(char *ptr, int len) { json_table_t table; JSONParser parser(&m_ReadBuf, ptr, len, &table); if (parser.GetError()) { PrintError(_SC("(sqdbg) Invalid JSON : " FMT_CSTR "\n"), parser.GetError()); AssertClientMsg1(0, "Invalid JSON : %s", parser.GetError()); DisconnectClient(); return; } string_t type; table.GetString("type", &type); if (type.IsEqualTo("request")) { int seq; GET_OR_FAIL(table, seq); ProcessRequest(table, seq); } else if (type.IsEqualTo("response")) { int request_seq; GET_OR_FAIL(table, request_seq); string_t command; table.GetString("command", &command); PrintError(_SC("(sqdbg) Unrecognised response '" FMT_CSTR "'\n"), command.ptr); AssertClientMsg1(0, "Unrecognised response '%s'", command.ptr); } else if (type.IsEqualTo("event")) { string_t event; table.GetString("event", &event); PrintError(_SC("(sqdbg) Unrecognised event '" FMT_CSTR "'\n"), event.ptr); AssertClientMsg1(0, "Unrecognised event '%s'", event.ptr); } else { PrintError(_SC("(sqdbg) Invalid DAP type '" FMT_CSTR "'\n"), type.ptr); AssertClientMsg1(0, "Invalid DAP type '%s'", type.ptr); DisconnectClient(); } m_ReadBuf.Release(); } void SQDebugServer::ProcessRequest(const json_table_t &table, int seq) { string_t command; table.GetString("command", &command); if (command.IsEqualTo("setBreakpoints")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setBreakpoints", table, arguments); OnRequest_SetBreakpoints(*arguments, seq); } else if (command.IsEqualTo("setFunctionBreakpoints")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setFunctionBreakpoints", table, arguments); OnRequest_SetFunctionBreakpoints(*arguments, seq); } else if (command.IsEqualTo("setExceptionBreakpoints")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setExceptionBreakpoints", table, arguments); OnRequest_SetExceptionBreakpoints(*arguments, seq); } else if (command.IsEqualTo("setDataBreakpoints")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setDataBreakpoints", table, arguments); OnRequest_SetDataBreakpoints(*arguments, seq); } else if (command.IsEqualTo("dataBreakpointInfo")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("dataBreakpointInfo", table, arguments); OnRequest_DataBreakpointInfo(*arguments, seq); } else if (command.IsEqualTo("evaluate")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("evaluate", table, arguments); OnRequest_Evaluate(*arguments, seq); } #ifndef SQDBG_DISABLE_COMPILER else if (command.IsEqualTo("completions")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("completions", table, arguments); OnRequest_Completions(*arguments, seq); } #endif else if (command.IsEqualTo("scopes")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("scopes", table, arguments); OnRequest_Scopes(*arguments, seq); } else if (command.IsEqualTo("threads")) { OnRequest_Threads(seq); } else if (command.IsEqualTo("stackTrace")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("stackTrace", table, arguments); OnRequest_StackTrace(*arguments, seq); } else if (command.IsEqualTo("variables")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("variables", table, arguments); OnRequest_Variables(*arguments, seq); } else if (command.IsEqualTo("setVariable")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setVariable", table, arguments); OnRequest_SetVariable(*arguments, seq); } else if (command.IsEqualTo("setExpression")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setExpression", table, arguments); OnRequest_SetExpression(*arguments, seq); } else if (command.IsEqualTo("setHitCount")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("setHitCount", table, arguments); int breakpointId, hitCount; arguments->GetInt("breakpointId", &breakpointId); arguments->GetInt("hitCount", &hitCount); if (hitCount < 0) hitCount = 0; if (breakpointId > 0 && breakpointId < m_nBreakpointIndex) { #define _check(vec, type) \ for (unsigned int i = 0; i < vec.Size(); i++) \ { \ type &bp = vec[i]; \ if (bp.id == breakpointId) \ { \ bp.hits = hitCount; \ DAP_START_RESPONSE(seq, "setHitCount"); \ DAP_SEND(); \ return; \ } \ } _check(m_Breakpoints, breakpoint_t); _check(m_DataWatches, datawatch_t); #undef _check } DAP_ERROR_RESPONSE(seq, "setHitCount"); DAP_ERROR_BODY(0, "invalid breakpoint {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", breakpointId); DAP_SEND(); } else if (command.IsEqualTo("source")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("source", table, arguments); json_table_t *source; if (arguments->GetTable("source", &source)) { string_t srcname; if ((!source->GetString("name", &srcname) || srcname.IsEmpty()) && source->GetString("path", &srcname)) { StripFileName(&srcname.ptr, &srcname.len); } script_t *scr = GetScript(srcname); if (scr) { DAP_START_RESPONSE(seq, "source"); DAP_SET_TABLE(body); body.SetString("content", {scr->scriptptr, scr->scriptlen}); DAP_SEND(); return; } } DAP_ERROR_RESPONSE(seq, "source"); DAP_SEND(); } else if (command.IsEqualTo("disassemble")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("disassemble", table, arguments); OnRequest_Disassemble(*arguments, seq); } #ifdef SUPPORTS_RESTART_FRAME else if (command.IsEqualTo("restartFrame")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("restartFrame", table, arguments); if (m_bExceptionPause) { DAP_START_RESPONSE(seq, "restartFrame"); DAP_SEND(); Continue(m_pCurVM); return; } #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveReturnValues(); OnRequest_RestartFrame(*arguments, seq); } #endif else if (command.IsEqualTo("gotoTargets")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("gotoTargets", table, arguments); OnRequest_GotoTargets(*arguments, seq); } else if (command.IsEqualTo("goto")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("goto", table, arguments); if (m_bExceptionPause) { DAP_START_RESPONSE(seq, "goto"); DAP_SEND(); Continue(m_pCurVM); return; } #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveReturnValues(); OnRequest_Goto(*arguments, seq); } else if (command.IsEqualTo("next")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("next", table, arguments); if (m_bExceptionPause) { DAP_START_RESPONSE(seq, "next"); DAP_SEND(); Continue(m_pCurVM); return; } #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveReturnValues(); OnRequest_Next(*arguments, seq); } else if (command.IsEqualTo("stepIn")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("stepIn", table, arguments); if (m_bExceptionPause) { DAP_START_RESPONSE(seq, "stepIn"); DAP_SEND(); Continue(m_pCurVM); return; } #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveReturnValues(); OnRequest_StepIn(*arguments, seq); } else if (command.IsEqualTo("stepOut")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("stepOut", table, arguments); if (m_bExceptionPause) { DAP_START_RESPONSE(seq, "stepOut"); DAP_SEND(); Continue(m_pCurVM); return; } #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveReturnValues(); OnRequest_StepOut(*arguments, seq); } else if (command.IsEqualTo("continue")) { DAP_START_RESPONSE(seq, "continue"); DAP_SET_TABLE(body); body.SetBool("allThreadsContinued", true); DAP_SEND(); Continue(m_pCurVM); } else if (command.IsEqualTo("pause")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("pause", table, arguments); int threadId; arguments->GetInt("threadId", &threadId, -1); HSQUIRRELVM vm = ThreadFromID(threadId); if (vm) { DAP_START_RESPONSE(seq, "pause"); DAP_SEND(); if (m_State != ThreadState_Suspended) { if (m_pPausedThread) { RestoreCachedInstructions(); ClearCachedInstructions(); } m_pPausedThread = vm; } } else { DAP_ERROR_RESPONSE(seq, "pause"); DAP_ERROR_BODY(0, "invalid thread"); DAP_SEND(); } } else if (command.IsEqualTo("attach")) { Print(_SC("(sqdbg) Client attached\n")); DAP_START_RESPONSE(seq, "attach"); DAP_SEND(); DAP_START_EVENT(seq, "process"); DAP_SET_TABLE(body); body.SetString("name", ""); body.SetString("startMethod", "attach"); body.SetInt("pointerSize", (int)sizeof(void *)); DAP_SEND(); } else if (command.IsEqualTo("disconnect") || command.IsEqualTo("terminate")) { DAP_START_RESPONSE(seq, command); DAP_SEND(); DisconnectClient(); } else if (command.IsEqualTo("initialize")) { json_table_t *arguments; GET_OR_ERROR_RESPONSE("initialize", table, arguments); OnRequest_Initialize(*arguments, seq); } else if (command.IsEqualTo("configurationDone")) { DAP_START_RESPONSE(seq, "configurationDone"); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, command); DAP_ERROR_BODY(0, "Unrecognised request '{command}'"); wjson_table_t variables = error.SetTable("variables"); variables.SetString("command", command); DAP_SEND(); AssertClientMsg1(0, "Unrecognised request '%s'", command.ptr); } } void SQDebugServer::OnScriptCompile(const SQChar *script, unsigned int scriptlen, const SQChar *sourcename, unsigned int sourcenamelen) { if (!script || !scriptlen || !sourcename || !sourcenamelen) return; #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&sourcename, &sourcenamelen); #endif #ifdef SQUNICODE unsigned int size = UTF8Length(sourcename, sourcenamelen); stringbufext_t source = ScratchPadBuf(size); source.Puts({sourcename, sourcenamelen}); #else string_t source; source.Assign(sourcename, sourcenamelen); #endif script_t *scr = GetScript(source); unsigned int scriptbufsize = ALIGN(scstombslen(script, scriptlen), 64); if (!scr) { scr = &m_Scripts.Append(); scr->sourceptr = m_Strings.Alloc(source.len); scr->scriptptr = m_Strings.Alloc(scriptbufsize); if (scr->sourceptr) { memcpy(scr->sourceptr, source.ptr, source.len); scr->sourcelen = source.len; } } else { unsigned int oldsize = ALIGN(scr->scriptlen, 64); if (oldsize != scriptbufsize) { m_Strings.Free(scr->scriptptr); scr->scriptptr = m_Strings.Alloc(scriptbufsize); } } if (scr->scriptptr) { scr->scriptlen = scstombs(scr->scriptptr, scriptbufsize, script, scriptlen); } } script_t *SQDebugServer::GetScript(const string_t &source) { for (unsigned int i = 0; i < m_Scripts.Size(); i++) { script_t &scr = m_Scripts[i]; if (source.IsEqualTo(scr.sourceptr, scr.sourcelen)) return &scr; } return NULL; } void SQDebugServer::RemoveScripts() { for (unsigned int i = 0; i < m_Scripts.Size(); i++) { script_t &scr = m_Scripts[i]; m_Strings.Free(scr.sourceptr); m_Strings.Free(scr.scriptptr); } m_Scripts.Purge(); } void SQDebugServer::OnRequest_Initialize(const json_table_t &arguments, int seq) { string_t clientID, clientName; arguments.GetString("clientID", &clientID, ""); arguments.GetString("clientName", &clientName, ""); if (clientName.IsEqualTo(clientID)) { Print(_SC("(sqdbg) Client initialised: " FMT_CSTR "\n"), clientName.ptr); } else { Print(_SC("(sqdbg) Client initialised: " FMT_CSTR " (" FMT_CSTR ")\n"), clientName.ptr, clientID.ptr); } #ifndef SQDBG_DISABLE_COMPILER arguments.GetBool("columnsStartAt1", &m_bClientColumnOffset); #endif DAP_START_RESPONSE(seq, "initialize"); DAP_SET_TABLE(body); body.SetBool("supportsConfigurationDoneRequest", true); body.SetBool("supportsFunctionBreakpoints", true); body.SetBool("supportsConditionalBreakpoints", true); body.SetBool("supportsHitConditionalBreakpoints", true); body.SetBool("supportsEvaluateForHovers", true); { wjson_array_t exceptionBreakpointFilters = body.SetArray("exceptionBreakpointFilters"); { wjson_table_t filter = exceptionBreakpointFilters.AppendTable(); filter.SetString("filter", "unhandled"); filter.SetString("label", "Unhandled exceptions"); filter.SetString("description", "Break on uncaught exceptions"); filter.SetBool("default", true); } { wjson_table_t filter = exceptionBreakpointFilters.AppendTable(); filter.SetString("filter", "all"); filter.SetString("label", "All exceptions"); filter.SetString("description", "Break on both caught and uncaught exceptions"); } } body.SetBool("supportsSetVariable", true); #ifdef SUPPORTS_RESTART_FRAME body.SetBool("supportsRestartFrame", true); #endif body.SetBool("supportsGotoTargetsRequest", true); #ifndef SQDBG_DISABLE_COMPILER body.SetBool("supportsCompletionsRequest", true); #endif body.SetBool("supportsSetExpression", true); body.SetBool("supportsSetHitCount", true); { wjson_array_t a = body.SetArray("supportedChecksumAlgorithms"); } body.SetBool("supportsValueFormattingOptions", true); body.SetBool("supportsDelayedStackTraceLoading", true); body.SetBool("supportsLogPoints", true); body.SetBool("supportsTerminateRequest", true); body.SetBool("supportsDataBreakpoints", true); body.SetBool("supportsDisassembleRequest", true); body.SetBool("supportsSteppingGranularity", true); DAP_SEND(); DAP_START_EVENT(seq, "initialized"); DAP_SEND(); } void SQDebugServer::SetSource(wjson_table_t &source, SQString *sourcename) { sqstring_t srcname; srcname.Assign(sourcename); // #ifdef SQDBG_SOURCENAME_HAS_PATH // StripFileName(&srcname.ptr, &srcname.len); // #endif #ifdef SQUNICODE stringbufext_t strName = ScratchPadBuf(UTF8Length(srcname.ptr, srcname.len)); strName.Puts(srcname); CFilePathMap::pair_t *pair = m_FilePathMap.Get(strName); #else CFilePathMap::pair_t *pair = m_FilePathMap.Get(srcname); #endif if (pair) { source.SetString("path", pair->path); #ifdef SQUNICODE source.SetString("name", pair->name); #else source.SetString("name", srcname); #endif } else { #ifdef SQUNICODE source.SetString("name", strName); #else source.SetString("name", srcname); #endif } } void SQDebugServer::OnRequest_SetBreakpoints(const json_table_t &arguments, int seq) { json_array_t *breakpoints; json_table_t *source; GET_OR_ERROR_RESPONSE("setBreakpoints", arguments, breakpoints); GET_OR_ERROR_RESPONSE("setBreakpoints", arguments, source); string_t srcname, srcpath; source->GetString("path", &srcpath); if ((!source->GetString("name", &srcname) || srcname.IsEmpty()) && !srcpath.IsEmpty()) { srcname = srcpath; StripFileName(&srcname.ptr, &srcname.len); } if (!srcname.IsEmpty() && !srcpath.IsEmpty()) { m_FilePathMap.Add(&m_Strings, srcname, srcpath); } else if (srcname.IsEmpty() && srcpath.IsEmpty()) { DAP_ERROR_RESPONSE(seq, "setBreakpoints"); DAP_ERROR_BODY(0, "invalid source"); DAP_SEND(); return; } //必须切割否则无法取消断点 #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&srcname.ptr, &srcname.len); #endif RemoveBreakpoints(srcname); DAP_START_RESPONSE(seq, "setBreakpoints"); DAP_SET_TABLE(body); wjson_array_t obps = body.SetArray("breakpoints"); for (int i = 0; i < breakpoints->Size(); i++) { json_table_t *bp; if (!breakpoints->GetTable(i, &bp)) { wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", false); obp.SetString("reason", "failed"); obp.SetString("message", "invalid"); continue; } int line, hitsTarget = 0; string_t condition, hitCondition, logMessage; bp->GetInt("line", &line); bp->GetString("condition", &condition); bp->GetString("logMessage", &logMessage); if (bp->GetString("hitCondition", &hitCondition) && !hitCondition.IsEmpty()) { strtoint(hitCondition, &hitsTarget); if (hitsTarget < 0) hitsTarget = 0; } int id = AddBreakpoint(line, srcname, condition, hitsTarget, logMessage); wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", ISVALID_ID(id)); if (ISVALID_ID(id)) { obp.SetInt("id", id); obp.SetInt("line", line); } else { obp.SetString("reason", "failed"); JSONSetString(obp, "message", m_pCurVM->_lasterror, kFS_NoQuote); } } DAP_SEND(); } void SQDebugServer::OnRequest_SetFunctionBreakpoints(const json_table_t &arguments, int seq) { json_array_t *breakpoints; GET_OR_ERROR_RESPONSE("setFunctionBreakpoints", arguments, breakpoints); RemoveFunctionBreakpoints(); DAP_START_RESPONSE(seq, "setFunctionBreakpoints"); DAP_SET_TABLE(body); wjson_array_t obps = body.SetArray("breakpoints"); for (int i = 0; i < breakpoints->Size(); i++) { json_table_t *bp; if (!breakpoints->GetTable(i, &bp)) { wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", false); obp.SetString("reason", "failed"); obp.SetString("message", "invalid"); continue; } int hitsTarget = 0; string_t name, condition, hitCondition, logMessage; bp->GetString("name", &name); bp->GetString("condition", &condition); bp->GetString("logMessage", &logMessage); if (bp->GetString("hitCondition", &hitCondition) && !hitCondition.IsEmpty()) { strtoint(hitCondition, &hitsTarget); if (hitsTarget < 0) hitsTarget = 0; } string_t funcsrc(""); int line = 0; // function source: funcname,filename:line for (int j = name.len - 1; j >= 0; j--) { if (!line && name.ptr[j] == ':') { string_t sLine; sLine.ptr = name.ptr + j + 1; sLine.len = name.len - j - 1; name.len = j; if (!sLine.len || !atoi(sLine, &line) || line <= 0) line = -1; } else if (name.ptr[j] == ',') { funcsrc.ptr = name.ptr + j + 1; funcsrc.len = name.len - j - 1; name.len = j; break; } } if (name.StartsWith(ANONYMOUS_FUNCTION_BREAKPOINT_NAME)) name.Assign(""); int id = AddFunctionBreakpoint(name, funcsrc, line, condition, hitsTarget, logMessage); wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", ISVALID_ID(id)); if (ISVALID_ID(id)) { obp.SetInt("id", id); } else { obp.SetString("reason", "failed"); JSONSetString(obp, "message", m_pCurVM->_lasterror, kFS_NoQuote); } } DAP_SEND(); } void SQDebugServer::OnRequest_SetExceptionBreakpoints(const json_table_t &arguments, int seq) { bool bCaught = false, bUncaught = false; json_array_t *filters; GET_OR_ERROR_RESPONSE("setExceptionBreakpoints", arguments, filters); for (int i = 0; i < filters->Size(); i++) { string_t filter; if (!filters->GetString(i, &filter)) continue; if (filter.IsEqualTo("unhandled")) { bUncaught = true; } else if (filter.IsEqualTo("all")) { bCaught = true; } } if (filters->Size() == 0) { m_bBreakOnExceptions = false; sq_notifyallexceptions(m_pRootVM, 0); } else { m_bBreakOnExceptions = true; if (bCaught) { sq_notifyallexceptions(m_pRootVM, 1); } else { Assert(bUncaught); (void)bUncaught; sq_notifyallexceptions(m_pRootVM, 0); } } DAP_START_RESPONSE(seq, "setExceptionBreakpoints"); DAP_SEND(); } void SQDebugServer::OnRequest_SetDataBreakpoints(const json_table_t &arguments, int seq) { json_array_t *breakpoints; GET_OR_ERROR_RESPONSE("setDataBreakpoints", arguments, breakpoints); RemoveDataBreakpoints(); DAP_START_RESPONSE(seq, "setDataBreakpoints"); DAP_SET_TABLE(body); wjson_array_t obps = body.SetArray("breakpoints"); for (int i = 0; i < breakpoints->Size(); i++) { json_table_t *bp; if (!breakpoints->GetTable(i, &bp)) { wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", false); obp.SetString("reason", "failed"); obp.SetString("message", "invalid"); continue; } int hitsTarget = 0; string_t dataId, condition, hitCondition; bp->GetString("dataId", &dataId); bp->GetString("condition", &condition); StripWhitespace(condition); if (bp->GetString("hitCondition", &hitCondition) && !hitCondition.IsEmpty()) { strtoint(hitCondition, &hitsTarget); if (hitsTarget < 0) hitsTarget = 0; } int id = AddDataBreakpoint(m_pCurVM, m_pCurVM->ci, dataId, condition, hitsTarget); wjson_table_t obp = obps.AppendTable(); obp.SetBool("verified", ISVALID_ID(id)); if (ISVALID_ID(id)) { obp.SetInt("id", id); } else { obp.SetString("reason", "failed"); JSONSetString(obp, "message", m_pCurVM->_lasterror, kFS_NoQuote); } } DAP_SEND(); } void SQDebugServer::OnRequest_DataBreakpointInfo(const json_table_t &arguments, int seq) { int variablesReference; string_t name; objref_t obj; SQObjectPtr value; arguments.GetString("name", &name); arguments.GetInt("variablesReference", &variablesReference); if (variablesReference) { varref_t *ref = FromVarRef(variablesReference); if (!ref || ref->type != VARREF_OBJ) { DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); body.SetNull("dataId"); body.SetString("description", ""); DAP_SEND(); return; } if (!name.IsEqualTo(INTERNAL_TAG("refs")) && !name.IsEqualTo(INTERNAL_TAG("allocated")) && !name.IsEqualTo(INTERNAL_TAG("state"))) { // don't modify name in GetObj stringbufext_t tmpbuf(m_ReadBuf.Alloc(name.len), name.len); tmpbuf.Puts(name); string_t tmp = tmpbuf; bool test = !GetObj_VarRef(ref, tmp, obj, value); m_ReadBuf.ReleaseTop(); if (test) { DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); body.SetNull("dataId"); body.SetString("description", ""); DAP_SEND(); return; } } DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); { jstringbuf_t buf = body.SetStringAsBuf("dataId"); buf.Put('1'); buf.PutInt(variablesReference); buf.Put(':'); buf.Puts(name); } { jstringbuf_t buf = body.SetStringAsBuf("description"); buf.Put('['); buf.PutHex((uintptr_t)_refcounted(ref->GetVar())); buf.Put(' '); buf.Puts(GetType(ref->GetVar())); buf.Put(']'); buf.Put('-'); buf.Put('>'); buf.Puts(name); } wjson_array_t at = body.SetArray("accessTypes"); at.Append("write"); DAP_SEND(); } else { #ifndef SQDBG_DISABLE_COMPILER // don't modify name in CCompiler::ParseString stringbufext_t tmpbuf(m_ReadBuf.Alloc(name.len + 1), name.len + 1); tmpbuf.Puts(name); tmpbuf.Term(); string_t tmp = tmpbuf; ECompileReturnCode r = Evaluate(tmp, m_pCurVM, m_pCurVM->ci, value, obj); m_ReadBuf.ReleaseTop(); // Check value again to see if the compiled expression was really a reference SQObjectPtr val; // objref_t::src will hold an additional one if (obj.type == objref_t::VIRTUAL_REF && sq_type(value) == OT_INTEGER) _integer(value)++; if (r != CompileReturnCode_Success || obj.type == objref_t::INVALID || obj.type == objref_t::PTR || (!ISREFCOUNTED(sq_type(obj.src)) && obj.type != objref_t::STACK) || !Get(obj, val) || !IsEqual(val, value)) { DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); body.SetNull("dataId"); body.SetString("description", ""); DAP_SEND(); return; } DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); { jstringbuf_t buf = body.SetStringAsBuf("dataId"); buf.Put('0'); buf.Puts(name); } { jstringbuf_t buf = body.SetStringAsBuf("description"); buf.Put('`'); buf.Puts(name); buf.Put('`'); } wjson_array_t at = body.SetArray("accessTypes"); at.Append("write"); DAP_SEND(); #else DAP_START_RESPONSE(seq, "dataBreakpointInfo"); DAP_SET_TABLE(body); body.SetNull("dataId"); body.SetString("description", "expression data breakpoints not implemented"); DAP_SEND(); #endif } } int SQDebugServer::AddDataBreakpoint(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &dataId, const string_t &strCondition, int hitsTarget) { if (dataId.len < 2) return INVALID_ID; string_t name; objref_t obj; SQObjectPtr value; SQWeakRef *pContainer; if (dataId.ptr[0] == '1') { char *pEnd = strchr(dataId.ptr + 1, ':'); if (!pEnd) return INVALID_ID; string_t container; container.ptr = dataId.ptr + 1; container.len = pEnd - container.ptr; name.ptr = pEnd + 1; name.len = (dataId.ptr + dataId.len) - name.ptr; int variablesReference; if (!atoi(container, &variablesReference)) { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } varref_t *ref = FromVarRef(variablesReference); if (!ref || ref->type != VARREF_OBJ) { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } // Prioritise virtual members, // real member watches can be added with sqdbg_watch as well if (name.IsEqualTo(INTERNAL_TAG("refs"))) { Assert(ISREFCOUNTED(sq_type(ref->GetVar()))); obj.type = objref_t::VIRTUAL_REF; obj.src = ref->GetVar(); value = (SQInteger)_refcounted(obj.src)->_uiRef; } else if (name.IsEqualTo(INTERNAL_TAG("allocated"))) { if (sq_type(ref->GetVar()) == OT_ARRAY) { obj.type = objref_t::VIRTUAL_ALLOCATED; obj.src = ref->GetVar(); value = (SQInteger)_array(obj.src)->_values.capacity(); } else { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } } else if (name.IsEqualTo(INTERNAL_TAG("state"))) { switch (sq_type(ref->GetVar())) { case OT_THREAD: { obj.type = objref_t::VIRTUAL_STATE; obj.src = ref->GetVar(); value = sq_getvmstate(_thread(obj.src)); break; } case OT_GENERATOR: { obj.type = objref_t::VIRTUAL_STATE; obj.src = ref->GetVar(); value = (SQInteger)_generator(obj.src)->_state; break; } default: { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } } } else { // don't modify name in GetObj stringbufext_t tmpbuf(m_ReadBuf.Alloc(name.len), name.len); tmpbuf.Puts(name); string_t tmp = tmpbuf; bool test = !GetObj_VarRef(ref, tmp, obj, value); m_ReadBuf.ReleaseTop(); if (test) { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } } ConvertPtr(obj); Assert(obj.type != objref_t::PTR); ConvertToWeakRef(*ref); pContainer = ref->obj.weakref; } #ifndef SQDBG_DISABLE_COMPILER else if (dataId.ptr[0] == '0') { name.ptr = dataId.ptr + 1; name.len = (dataId.ptr + dataId.len) - name.ptr; // don't modify name in CCompiler::ParseString stringbufext_t tmpbuf(m_ReadBuf.Alloc(name.len + 1), name.len + 1); tmpbuf.Puts(name); tmpbuf.Term(); string_t tmp = tmpbuf; ECompileReturnCode r = Evaluate(tmp, vm, ci, value, obj); m_ReadBuf.ReleaseTop(); ConvertPtr(obj); // Check value again to see if the compiled expression was really a reference SQObjectPtr val; if (r == CompileReturnCode_DoesNotExist) { vm->_lasterror = CreateSQString(vm, _SC("index does not exist")); return INVALID_ID; } if (r != CompileReturnCode_Success) { vm->_lasterror = CreateSQString(vm, _SC("invalid expression")); return INVALID_ID; } // objref_t::src will hold an additional one if (obj.type == objref_t::VIRTUAL_REF && sq_type(value) == OT_INTEGER) _integer(value)++; if (obj.type == objref_t::INVALID || obj.type == objref_t::PTR || (!ISREFCOUNTED(sq_type(obj.src)) && obj.type != objref_t::STACK) || !Get(obj, val) || !IsEqual(val, value)) { vm->_lasterror = CreateSQString(vm, _SC("invalid object")); return INVALID_ID; } pContainer = NULL; } #endif else { return INVALID_ID; } #ifdef SQDBG_DISABLE_COMPILER (void)ci; #endif bool duplicate = false; // Duplicate? if (obj.type != objref_t::STACK) for (unsigned int i = m_DataWatches.Size(); i--;) { const datawatch_t &dw = m_DataWatches[i]; if (dw.container == pContainer && _refcounted(dw.obj.src) == _refcounted(obj.src) && dw.obj.type == obj.type && IsEqual(dw.obj.key, obj.key)) { // Allow duplicate watches with different conditions if (dw.condtype != ECMP_NONE || !strCondition.IsEmpty()) { duplicate = true; break; } else { vm->_lasterror = CreateSQString(vm, _SC("duplicate breakpoint")); return DUPLICATE_ID; } } } unsigned int condtype = ECMP_NONE; SQObjectPtr condition; if (!strCondition.IsEmpty() && !CompileDataBreakpointCondition(strCondition, condition, condtype)) return INVALID_ID; if (duplicate) { // Match this exact compiled condition for (unsigned int i = m_DataWatches.Size(); i--;) { const datawatch_t &dw = m_DataWatches[i]; if (dw.container == pContainer && _refcounted(dw.obj.src) == _refcounted(obj.src) && dw.obj.type == obj.type && IsEqual(dw.obj.key, obj.key) && dw.condtype == condtype && IsEqual(dw.condition, condition)) { vm->_lasterror = CreateSQString(vm, _SC("duplicate breakpoint")); return DUPLICATE_ID; } } } Assert(m_nBreakpointIndex < INT_MAX); datawatch_t &dw = m_DataWatches.Append(); dw.id = ++m_nBreakpointIndex; CopyString(&m_Strings, name, &dw.name); if (pContainer) { dw.container = pContainer; __ObjAddRef(dw.container); } dw.obj = obj; dw.oldvalue = value; dw.hitsTarget = hitsTarget; if (condtype != ECMP_NONE) { dw.condtype = condtype; dw.condition = condition; if (is_delegable(dw.condition) && _delegable(dw.condition)->_delegate) { sq_addref(vm, &dw.condition); } } return dw.id; } // // Parse out condition type and evaluate the value. // It would be possible to let the user do ops on the data with a pseudovariable like '$data', // or allow late evaluation by making the condition a function to be executed or parsing it manually, // but that would add too much complexity and runtime overhead. // bool SQDebugServer::CompileDataBreakpointCondition(string_t condition, SQObjectPtr &out, unsigned int &type) { if (condition.StartsWith("==")) { type = ECMP_EQ; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith("!=")) { type = ECMP_NE; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith(">=")) { type = ECMP_GE; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith("<=")) { type = ECMP_LE; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith("=&")) { type = ECMP_BWAEQ; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith("!&")) { type = ECMP_BWAZ; condition.ptr += 2; condition.len -= 2; } else if (condition.StartsWith(">")) { type = ECMP_G; condition.ptr += 1; condition.len -= 1; } else if (condition.StartsWith("<")) { type = ECMP_L; condition.ptr += 1; condition.len -= 1; } else if (condition.StartsWith("&")) { type = ECMP_BWA; condition.ptr += 1; condition.len -= 1; } else { m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("missing comparator")); return false; } // Compile the condition in current stack frame #ifdef NATIVE_DEBUG_HOOK if (!RunExpression(condition, m_pCurVM, m_pCurVM->ci, out)) #else if (!RunExpression(condition, m_pCurVM, // client could send this request while not suspended m_State == ThreadState_Suspended ? m_pCurVM->ci - 1 : m_pCurVM->ci, out)) #endif return false; // Condition can only be of certain types // for comparisons and bitwise ops switch (type) { case ECMP_G: case ECMP_GE: case ECMP_L: case ECMP_LE: switch (sq_type(out)) { case OT_INTEGER: case OT_FLOAT: return true; default: if (is_delegable(out)) { // Should have valid cmp metamethod if (_delegable(out)->_delegate) { SQObjectPtr mm; if (_delegable(out)->GetMetaMethod(m_pRootVM, MT_CMP, mm)) { SQObjectPtr ret; if (RunClosure(mm, &out, out, ret) && sq_type(ret) == OT_INTEGER) { return true; } } } m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("invalid cmp metamethod")); return false; } m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("invalid type for comparator, expected integer|float|instance|table")); return false; } case ECMP_BWA: case ECMP_BWAZ: case ECMP_BWAEQ: if (sq_type(out) == OT_INTEGER) return true; m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("invalid type for bitwise op, expected integer")); return false; } return true; } int SQDebugServer::CompareObj(const SQObjectPtr &lhs, const SQObjectPtr &rhs) { SQObjectType tl = sq_type(lhs); SQObjectType tr = sq_type(rhs); if (tl == tr) { if (_rawval(lhs) == _rawval(rhs)) return ECMP_EQ; switch (tl) { case OT_INTEGER: case OT_BOOL: return _integer(lhs) < _integer(rhs) ? ECMP_L : ECMP_G; case OT_FLOAT: return _float(lhs) < _float(rhs) ? ECMP_L : ECMP_G; case OT_STRING: { int res = scstrcmp(_string(lhs)->_val, _string(rhs)->_val); if (res < 0) return ECMP_L; return ECMP_G; } default: if (is_delegable(lhs) && _delegable(lhs)->_delegate) { SQObjectPtr mm; if (_delegable(lhs)->GetMetaMethod(m_pRootVM, MT_CMP, mm)) { SQObjectPtr ret; if (RunClosure(mm, &lhs, rhs, ret) && sq_type(ret) == OT_INTEGER) { if (_integer(ret) == 0) return ECMP_EQ; if (_integer(ret) < 0) return ECMP_L; return ECMP_G; } } } return ECMP_NONE; } } else { if (tl == OT_INTEGER) { if (tr == OT_FLOAT) { if (_integer(lhs) == (SQInteger)_float(rhs)) return ECMP_EQ; if (_integer(lhs) < (SQInteger)_float(rhs)) return ECMP_L; return ECMP_G; } } else if (tl == OT_FLOAT) { if (tr == OT_INTEGER) { if ((SQInteger)_float(lhs) == _integer(rhs)) return ECMP_EQ; if ((SQInteger)_float(lhs) < _integer(rhs)) return ECMP_L; return ECMP_G; } } // uncomparable return ECMP_NONE; } } bool SQDebugServer::CheckDataBreakpoints(HSQUIRRELVM vm) { bool ret = false; for (unsigned int i = m_DataWatches.Size(); i--;) { datawatch_t &dw = m_DataWatches[i]; if (dw.container) { bool rem = (sq_type(dw.container->_obj) == OT_NULL); // objref_t::src holds strong ref for the compiler. // Manually check if the container is the source // and if its only reference is in objref_t. // Though this doesn't work when there are // multiple breakpoints on a single container if (!rem && _rawval(dw.container->_obj) == _rawval(dw.obj.src) && _refcounted(dw.obj.src)->_uiRef == 1) { dw.obj.src.Null(); rem = (sq_type(dw.container->_obj) == OT_NULL); } if (rem) { DAP_START_EVENT(++m_Sequence, "breakpoint"); DAP_SET_TABLE(body); body.SetString("reason", "removed"); wjson_table_t bp = body.SetTable("breakpoint"); bp.SetInt("id", dw.id); bp.SetBool("verified", false); DAP_SEND(); FreeDataWatch(dw); m_DataWatches.Remove(i); continue; } } SQObjectPtr value; if (Get(dw.obj, value)) { if (IsEqual(dw.oldvalue, value)) continue; SQObjectPtr oldvalue = dw.oldvalue; dw.oldvalue = value; switch (dw.condtype) { case ECMP_NONE: break; case ECMP_EQ: if (IsEqual(value, dw.condition)) break; continue; case ECMP_NE: if (!IsEqual(value, dw.condition)) break; continue; case ECMP_G: case ECMP_GE: case ECMP_L: case ECMP_LE: if (CompareObj(value, dw.condition) & dw.condtype) break; continue; case ECMP_BWA: if (sq_type(value) == OT_INTEGER && (_integer(value) & _integer(dw.condition)) != 0) break; continue; case ECMP_BWAZ: if (sq_type(value) == OT_INTEGER && (_integer(value) & _integer(dw.condition)) == 0) break; continue; case ECMP_BWAEQ: if (sq_type(value) == OT_INTEGER && (_integer(value) & _integer(dw.condition)) == _integer(dw.condition)) break; continue; default: UNREACHABLE(); } if (dw.hitsTarget) { if (++dw.hits < dw.hitsTarget) continue; dw.hits = 0; } stringbuf_t<256> buf; string_t tmp = dw.name; if (tmp.len > 128) tmp.len = 128; if (dw.container) { buf.Put('['); buf.PutHex((uintptr_t)_refcounted(dw.container->_obj)); buf.Put(' '); buf.Puts(GetType(dw.container->_obj)); buf.Put(']'); buf.Put('-'); buf.Put('>'); buf.Puts(tmp); } else { buf.Put('`'); buf.Puts(tmp); buf.Put('`'); } buf.Puts(" changed ("); tmp = GetValue(oldvalue); if (tmp.len > 128) tmp.len = 128; buf.Puts(tmp); buf.Puts(")->("); tmp = GetValue(value); if (tmp.len > 128) tmp.len = 128; buf.Puts(tmp); buf.Put(')'); buf.Term(); SQPrint(vm, _SC("(sqdbg) Data breakpoint hit: " FMT_CSTR "\n"), buf.ptr); Break(vm, {breakreason_t::DataBreakpoint, buf, dw.id}); ret = true; } else { DAP_START_EVENT(++m_Sequence, "breakpoint"); DAP_SET_TABLE(body); body.SetString("reason", "removed"); wjson_table_t bp = body.SetTable("breakpoint"); bp.SetInt("id", dw.id); bp.SetBool("verified", false); DAP_SEND(); // don't break on stack watch expiration if (dw.obj.type != objref_t::STACK) { stringbuf_t<256> buf; if (dw.container) { buf.Put('['); buf.PutHex((uintptr_t)_refcounted(dw.container->_obj)); buf.Put(' '); buf.Puts(GetType(dw.container->_obj)); buf.Put(']'); buf.Put('-'); buf.Put('>'); buf.Puts(dw.name); } else { buf.Put('`'); buf.Puts(dw.name); buf.Put('`'); } buf.Puts(" was removed"); buf.Term(); SQPrint(vm, _SC("(sqdbg) Data breakpoint hit: " FMT_CSTR "\n"), buf.ptr); Break(vm, {breakreason_t::DataBreakpoint, buf, dw.id}); ret = true; } FreeDataWatch(dw); m_DataWatches.Remove(i); } } return ret; } void SQDebugServer::FreeDataWatch(datawatch_t &dw) { if (dw.container) __ObjRelease(dw.container); if (dw.condtype != ECMP_NONE) { if (is_delegable(dw.condition) && _delegable(dw.condition)->_delegate) { sq_release(m_pRootVM, &dw.condition); dw.condition.Null(); } } FreeString(&m_Strings, &dw.name); } void SQDebugServer::RemoveDataBreakpoints() { for (unsigned int i = 0; i < m_DataWatches.Size(); i++) FreeDataWatch(m_DataWatches[i]); m_DataWatches.Clear(); } static inline bool HasEscapes(const SQChar *src, SQInteger len) { const SQChar *end = src + len; for (; src < end; src++) { switch (*src) { case '\\': case '\"': case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': return true; default: #ifdef SQUNICODE if (!IN_RANGE(*src, 0x20, 0x7E)) #else if (!IN_RANGE_CHAR(*src, 0x20, 0x7E)) #endif { #ifdef SQUNICODE int ret = IsValidUnicode(src, end - src); if (ret > 0) #else int ret = IsValidUTF8((unsigned char *)src, end - src); if (ret != 0) #endif { src += ret - 1; } else { return true; } } } } return false; } #ifndef SQUNICODE static inline int CountEscapes(const char *src, SQInteger len) { const char *end = src + len; int count = 0; for (; src < end; src++) { switch (*src) { case '\\': case '\"': case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': count++; break; default: if (!IN_RANGE_CHAR(*src, 0x20, 0x7E)) { int ret = IsValidUTF8((unsigned char *)src, end - src); if (ret != 0) { src += ret - 1; } else { count += sizeof(char) * 2 + 1; } } } } return count; } #endif // !SQUNICODE #define _memmove(dst, src, count, bound) \ { \ Assert((dst) + (count) <= bound); \ memmove((dst), (src), (count)); \ } static void Escape(char *dst, unsigned int *len, unsigned int size) { if (size < *len) *len = size; char *strEnd = dst + *len; char *memEnd = dst + size; for (; dst < strEnd; dst++) { switch (*dst) { case '\\': case '\"': case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': { if (dst + 1 >= memEnd) { *dst = '?'; break; } _memmove(dst + 1, dst, memEnd - (dst + 1), memEnd); *dst++ = '\\'; switch (*dst) { case '\\': case '\"': break; case '\a': *dst = 'a'; break; case '\b': *dst = 'b'; break; case '\f': *dst = 'f'; break; case '\n': *dst = 'n'; break; case '\r': *dst = 'r'; break; case '\t': *dst = 't'; break; case '\v': *dst = 'v'; break; default: UNREACHABLE(); } if (strEnd < memEnd) { strEnd++; (*len)++; } break; } default: if (!IN_RANGE_CHAR(*dst, 0x20, 0x7E)) { int ret = IsValidUTF8((unsigned char *)dst, strEnd - dst); if (ret == 0) { if (dst + 1 + sizeof(SQChar) * 2 >= memEnd) { do { *dst++ = '?'; } while (dst < memEnd); break; } SQUnsignedChar val = (SQUnsignedChar)((unsigned char *)dst)[0]; _memmove(dst + 2 + sizeof(SQChar) * 2, dst + 1, memEnd - (dst + 2 + sizeof(SQChar) * 2), memEnd); dst[0] = '\\'; dst[1] = 'x'; int l = printhex(dst + 2, memEnd - (dst + 2), val); dst += 1 + l; strEnd += 1 + l; *len += 1 + l; if (strEnd > memEnd) { *len -= (unsigned int)(strEnd - memEnd); strEnd = memEnd; } } else if (ret) { dst += ret - 1; } } } } } #ifndef SQUNICODE static void UndoEscape(char *dst, unsigned int *len) { char *end = dst + *len; for (; dst < end;) { if (*dst != '\\') { dst++; continue; } #define _shift(bytesWritten, bytesRead) \ Assert((bytesWritten) < (bytesRead)); \ memmove(dst + (bytesWritten), dst + (bytesRead), end - (dst + (bytesRead))); \ dst += (bytesWritten); \ end -= (bytesRead) - (bytesWritten); \ *len -= (bytesRead) - (bytesWritten); switch (dst[1]) { case '\\': shift_one: _shift(1, 2); break; case '\"': *dst = '\"'; goto shift_one; case '\'': *dst = '\''; goto shift_one; case 'a': *dst = '\a'; goto shift_one; case 'b': *dst = '\b'; goto shift_one; case 'f': *dst = '\f'; goto shift_one; case 'n': *dst = '\n'; goto shift_one; case 'r': *dst = '\r'; goto shift_one; case 't': *dst = '\t'; goto shift_one; case 'v': *dst = '\v'; goto shift_one; case 'x': { atox({dst + 2, sizeof(SQChar) * 2}, (SQChar *)dst); _shift(1, 2 + sizeof(SQChar) * 2); break; } } #undef _shift } } #endif // !SQUNICODE #if 0 template < typename T > static int StringifiedBytesLength( int len ) { return len * ( 2 + sizeof(T) * 2 ); } template < typename T > static int StringifyBytes( T *str, int strLen, char *dst, int size ) { T *strEnd = str + strLen; int len = 0; for ( ; str < strEnd && len <= size - (int)( 2 + sizeof(T) * 2 ); str++ ) { *dst++ = '\\'; *dst++ = 'x'; len++; len++; int l = printhex< true, false >( dst, size - len, *str ); dst += l; len += l; } return len; } static bool ReadStringifiedBytes( char *dst, int *len ) { char *end = dst + *len; do { if ( dst[0] == '\\' && dst[1] == 'x' ) { if ( !atox( { dst + 2, sizeof(SQChar) * 2 }, (SQChar*)dst ) ) return false; memmove( dst + sizeof(SQChar), dst + 2 + sizeof(SQChar) * 2, end - ( dst + 2 + sizeof(SQChar) * 2 ) ); end -= 2 + sizeof(SQChar) * 2 - sizeof(SQChar); *len -= 2 + sizeof(SQChar) * 2 - sizeof(SQChar); dst += sizeof(SQChar) - 1; } } while ( ++dst < end ); return true; } #endif static inline string_t SpecialFloatValue(SQFloat val) { #define _check(v) \ if (val == v) \ return #v #ifdef SQUSEDOUBLE _check(DBL_MAX); _check(DBL_MIN); _check(DBL_EPSILON); #endif _check(FLT_MAX); _check(FLT_MIN); _check(FLT_EPSILON); return {0, 0}; #undef _check } string_t SQDebugServer::GetValue(const SQObject &obj, int flags) { switch (sq_type(obj)) { case OT_STRING: { if (!(flags & kFS_NoQuote)) { #ifdef SQUNICODE unsigned int size = 2 + UTF8Length(_string(obj)->_val, _string(obj)->_len); #else int escapes = CountEscapes(_string(obj)->_val, _string(obj)->_len); unsigned int size = 2 + _string(obj)->_len + escapes; #endif char *buf = ScratchPad(size); if (!buf) return {STR_NOMEM, STRLEN(STR_NOMEM)}; unsigned int len = 0; #if 0 if ( rawBytes ) { // Identify keys while sending regular strings for values if ( flags & kFS_KeyVal ) buf[len++] = 'R'; buf[len++] = '\"'; len += StringifyBytes( _string(obj)->_val, min( ( size - len ) / sizeof(SQChar), (unsigned int)_string(obj)->_len ), buf + len, size - len - 1 ); } #endif buf[0] = '\"'; #ifdef SQUNICODE len = SQUnicodeToUTF8(buf + 1, size - 2, _string(obj)->_val, _string(obj)->_len); #else len = scstombs(buf + 1, size - 2, _string(obj)->_val, _string(obj)->_len); Escape(buf + 1, &len, size - 2); #endif len++; buf[len++] = '\"'; Assert(len == size); return {buf, len}; } else { #ifdef SQUNICODE unsigned int size = UTF8Length(_string(obj)->_val, _string(obj)->_len); char *buf = ScratchPad(size); if (!buf) return {STR_NOMEM, STRLEN(STR_NOMEM)}; unsigned int len = SQUnicodeToUTF8(buf, size, _string(obj)->_val, _string(obj)->_len); return {buf, len}; #else #ifdef _SQ64 string_t ret(_string(obj)); if (ret.len > INT_MAX) ret.len = INT_MAX; return ret; #else return _string(obj); #endif #endif } } case OT_FLOAT: { if (flags & kFS_Decimal) { const int size = FMT_INT_LEN; char *buf = ScratchPad(size); int len = printint(buf, size, _integer(obj)); return {buf, (unsigned int)len}; } else if (flags & kFS_Binary) { int len = 2 + (sizeof(SQFloat) << 3); char *buf = ScratchPad(len); char *c = buf; *c++ = '0'; *c++ = 'b'; if (flags & kFS_NoPrefix) { len -= 2; c -= 2; } for (int i = (sizeof(SQFloat) << 3); i--;) *c++ = '0' + ((_integer(obj) & ((SQUnsignedInteger)1 << i)) != 0); return {buf, (unsigned int)len}; } else { getfloat: string_t val = SpecialFloatValue(_float(obj)); if (!val.ptr || (flags & (kFS_Float | kFS_FloatE | kFS_FloatG))) { const int size = FMT_FLT_LEN + 1; val.ptr = ScratchPad(size); if (flags & kFS_FloatE) { val.len = snprintf(val.ptr, size, "%e", _float(obj)); } else if (flags & kFS_FloatG) { val.len = snprintf(val.ptr, size, "%g", _float(obj)); } else { val.len = snprintf(val.ptr, size, "%f", _float(obj)); } Assert(val.len < INT_MAX); } return val; } } case OT_INTEGER: { if (flags & kFS_Binary) { int i; if (!(flags & kFS_Padding)) { // Print at 1, 2, 4 byte boundaries if ((SQUnsignedInteger)_integer(obj) > 0xFFFFFFFF) { i = sizeof(SQInteger) * 8; } else if ((SQUnsignedInteger)_integer(obj) > 0x0000FFFF) { i = 4 * 8; } else if ((SQUnsignedInteger)_integer(obj) > 0x000000FF) { i = 2 * 8; } else { i = 1 * 8; } } else { i = sizeof(SQInteger) * 8; } unsigned int len = 2 + i; char *buf = ScratchPad(len); char *c = buf; *c++ = '0'; *c++ = 'b'; if (flags & kFS_NoPrefix) { len -= 2; c -= 2; } while (i--) *c++ = '0' + ((_integer(obj) & ((SQUnsignedInteger)1 << i)) != 0); return {buf, len}; } else if (flags & kFS_Octal) { const int size = FMT_OCT_LEN; char *buf = ScratchPad(size); int len = printoct(buf, size, (SQUnsignedInteger)_integer(obj)); return {buf, (unsigned int)len}; } else if (flags & kFS_Float) { goto getfloat; } else if (flags & kFS_Character) { const int size = FMT_INT_LEN + FMT_PTR_LEN + 3; char *buf = ScratchPad(size); int len; // Allow both signed and unsigned char values ( -1 == 255, -128 == 128, -127 == 129 ) if (_integer(obj) > (SQInteger)((SQUnsignedChar)-1 >> (int)(sizeof(SQChar) >= sizeof(SQInteger))) || _integer(obj) < (SQInteger) - (((SQUnsignedChar)-1 >> 1) + 1)) { len = printint(buf, size, _integer(obj)); return {buf, (unsigned int)len}; } SQUnsignedChar ch = (SQUnsignedChar)_integer(obj); if (!(flags & kFS_Hexadecimal)) { len = printint(buf, size, (SQUnsignedInteger)ch); } else { len = printhex(buf, size, ch); } buf[len++] = ' '; buf[len++] = '\''; if (IN_RANGE(ch, 0x20, 0x7E)) { if (ch == '\'' || ch == '\\') buf[len++] = '\\'; buf[len++] = (char)ch; } #ifdef SQUNICODE else if (IsValidUnicode((SQChar *)&ch, 1)) { len += SQUnicodeToUTF8(buf + len, size - len - 1, (SQChar *)&ch, 1); } #endif else { buf[len++] = '\\'; buf[len++] = 'x'; #ifdef SQUNICODE if (ch <= 0xFF) { len += printhex(buf + len, size, (unsigned char)ch); } else #endif { len += printhex(buf + len, size, ch); } } buf[len++] = '\''; return {buf, (unsigned int)len}; } // Check hex last to make watch format specifiers overwrite "format.hex" client option else if ((flags & kFS_Hexadecimal) && !(flags & kFS_Decimal)) { const int size = FMT_PTR_LEN; char *buf = ScratchPad(size); int len; if (!(flags & kFS_Uppercase)) { if (!(flags & kFS_NoPrefix)) { if (!(flags & kFS_Padding)) { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } else { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } } else { if (!(flags & kFS_Padding)) { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } else { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } } } else { if (!(flags & kFS_NoPrefix)) { if (!(flags & kFS_Padding)) { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } else { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } } else { if (!(flags & kFS_Padding)) { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } else { len = printhex(buf, size, (SQUnsignedInteger)_integer(obj)); } } } return {buf, (unsigned int)len}; } else { const int size = FMT_INT_LEN; char *buf = ScratchPad(size); int len = printint(buf, size, _integer(obj)); return {buf, (unsigned int)len}; } } case OT_BOOL: { if (_integer(obj)) return "true"; return "false"; } case OT_NULL: { return "null"; } case OT_ARRAY: { int size; if (!(flags & kFS_ListMembers)) { size = STRLEN(" {size=}") + FMT_PTR_LEN + FMT_INT_LEN; } else { size = FMT_PTR_LEN + 128; } if (!(flags & kFS_ListMembers)) { stringbufext_t buf = ScratchPadBuf(size); if (!(flags & kFS_NoAddr)) { buf.PutHex((uintptr_t)_refcounted(obj)); buf.Put(' '); } buf.Puts("{size="); if (!(flags & kFS_Hexadecimal)) { buf.PutInt(_array(obj)->_values.size()); } else { buf.PutHex(_array(obj)->_values.size(), false); } buf.Put('}'); return buf; } else { // HACKHACK: Just use an unused buffer stringbufext_t buf(m_ReadBuf.Alloc(size), size); if (!(flags & kFS_NoAddr)) { buf.PutHex((uintptr_t)_refcounted(obj)); buf.Put(' '); } buf.Put('['); for (unsigned int i = 0; i < _array(obj)->_values.size(); i++) { string_t str = GetValue(_array(obj)->_values[i], flags & ~kFS_NoQuote); buf.Puts(str); if (buf.BytesLeft() < 4) { buf.len -= 4 - buf.BytesLeft(); buf.Puts("..."); buf.Put(']'); return buf; } buf.Put(','); buf.Put(' '); } if (_array(obj)->_values.size()) buf.len -= 2; buf.Put(']'); return buf; } } case OT_TABLE: { int size; if (!(flags & kFS_ListMembers)) { size = STRLEN(" {size=}") + FMT_PTR_LEN + FMT_INT_LEN; } else { size = FMT_PTR_LEN + 128; } if (!(flags & kFS_ListMembers)) { stringbufext_t buf = ScratchPadBuf(size); if (!(flags & kFS_NoAddr)) { buf.PutHex((uintptr_t)_refcounted(obj)); buf.Put(' '); } buf.Puts("{size="); if (!(flags & kFS_Hexadecimal)) { buf.PutInt(_table(obj)->CountUsed()); } else { buf.PutHex((SQUnsignedInteger)_table(obj)->CountUsed(), false); } buf.Put('}'); return buf; } else { // HACKHACK: Just use an unused buffer stringbufext_t buf(m_ReadBuf.Alloc(size), size); if (!(flags & kFS_NoAddr)) { buf.PutHex((uintptr_t)_refcounted(obj)); buf.Put(' '); } buf.Put('{'); SQObjectPtr key, val; FOREACH_SQTABLE(_table(obj), key, val) { string_t str = GetValue(key, flags | kFS_NoQuote); buf.Puts(str); buf.Put('='); str = GetValue(val, flags & ~kFS_NoQuote); buf.Puts(str); if (buf.BytesLeft() < 4) { buf.len -= 4 - buf.BytesLeft(); buf.Puts("..."); buf.Put('}'); return buf; } buf.Put(','); buf.Put(' '); } if (_table(obj)->CountUsed()) buf.len -= 2; buf.Put('}'); return buf; } } case OT_INSTANCE: { SQClass *base = _instance(obj)->_class; Assert(base); const SQObjectPtr *def = GetClassDefValue(base); if (def) { SQObjectPtr res; if (RunClosure(*def, &obj, res) && sq_type(res) == OT_STRING) { if (!(flags & kFS_NoAddr)) { const int size = 1024; stringbufext_t buf = ScratchPadBuf(size); buf.PutHex((uintptr_t)_refcounted(obj)); buf.Put(' '); buf.Put('{'); buf.Puts(_string(res)); buf.Put('}'); return buf; } else { unsigned int size = scstombslen(_string(res)->_val, _string(res)->_len); char *buf = ScratchPad(size); if (!buf) return {STR_NOMEM, STRLEN(STR_NOMEM)}; unsigned int len = scstombs(buf, size, _string(res)->_val, _string(res)->_len); return {buf, len}; } } } goto default_label; } case OT_CLASS: { const classdef_t *def = FindClassDef(_class(obj)); if (def && def->name.ptr) { if (!(flags & kFS_NoAddr)) { return def->name; } else { Assert(def->name.len >= FMT_PTR_LEN + 1); return {def->name.ptr + FMT_PTR_LEN + 1, def->name.len - FMT_PTR_LEN - 1}; } } goto default_label; } case OT_CLOSURE: case OT_NATIVECLOSURE: #ifdef ACCESSIBLE_FUNCPROTO case OT_FUNCPROTO: #endif { const SQObjectPtr *name; if (sq_type(obj) == OT_CLOSURE) { name = &_fp(_closure(obj)->_function)->_name; } else if (sq_type(obj) == OT_NATIVECLOSURE) { name = &_nativeclosure(obj)->_name; } #ifdef ACCESSIBLE_FUNCPROTO else if (sq_type(obj) == OT_FUNCPROTO) { name = &_funcproto(obj)->_name; } #endif else UNREACHABLE(); if (sq_type(*name) == OT_STRING) { int size = FMT_PTR_LEN + 1 + scstombslen(_string(*name)->_val, _string(*name)->_len); char *buf = ScratchPad(size); if (!buf) return {STR_NOMEM, STRLEN(STR_NOMEM)}; int len = printhex(buf, size, (uintptr_t)_refcounted(obj)); buf[len++] = ' '; len += scstombs(buf + len, size - FMT_PTR_LEN - 1, _string(*name)->_val, _string(*name)->_len); return {buf, (unsigned int)len}; } goto default_label; } default: default_label: { const int size = FMT_PTR_LEN; char *buf = ScratchPad(size); int len = printhex(buf, size, (uintptr_t)_refcounted(obj)); return {buf, (unsigned int)len}; } } } // To make sure strings are not unnecessarily allocated and iterated through // Escape and quote strings while writing to json void SQDebugServer::JSONSetString(wjson_table_t &elem, const string_t &key, const SQObject &obj, int flags) { switch (sq_type(obj)) { case OT_STRING: elem.SetString(key, _string(obj), !(flags & kFS_NoQuote)); break; case OT_INTEGER: if (!(flags & kFS_Character)) goto conststr; goto process; // Possibly contains characters to be escaped through class def and member list case OT_TABLE: case OT_ARRAY: if (!(flags & kFS_ListMembers)) goto conststr; case OT_INSTANCE: case OT_CLASS: process: elem.SetString(key, GetValue(obj, flags)); break; // Cannot contain escapable characters, copy string verbatim to json default: conststr: { string_t str = GetValue(obj, flags); conststring_t ret; ret.ptr = str.ptr; ret.len = str.len; elem.SetString(key, ret); } } } bool SQDebugServer::IsJumpOp(const SQInstruction *instr) { return (instr->op == _OP_JMP || instr->op == _OP_AND || instr->op == _OP_OR || #if SQUIRREL_VERSION_NUMBER >= 300 instr->op == _OP_JCMP || #else instr->op == _OP_JNZ || #endif instr->op == _OP_JZ || instr->op == _OP_FOREACH || instr->op == _OP_POSTFOREACH); } int SQDebugServer::GetJumpCount(const SQInstruction *instr) { Assert(IsJumpOp(instr)); if (instr->op != _OP_POSTFOREACH) return instr->_arg1; return instr->_arg1 - 1; } // Line ops are ignored in disassembly, but jump ops account for them. // Count out all line ops in the jump // Doing this for setting instructions adds too much complexity that is not worth the effort. int SQDebugServer::DeduceJumpCount(const SQInstruction *instr) { Assert(IsJumpOp(instr)); int arg1 = GetJumpCount(instr); int sign = (arg1 < 0); if (sign) arg1 = -arg1; for (const SQInstruction *ip = instr + GetJumpCount(instr); ip != instr; ip += sign ? 1 : -1) { if (ip->op == _OP_LINE) arg1--; } if (sign) arg1 = -arg1; return arg1; } // to display the local variable name only in the target on local variable declarations SQUnsignedInteger SQDebugServer::s_nTargetAssignment = 0; SQString *SQDebugServer::GetLocalVarName(const SQFunctionProto *func, const SQInstruction *instr, unsigned int pos) { SQUnsignedInteger ip = instr - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if ((unsigned int)var._pos == pos && var._start_op <= ip + s_nTargetAssignment && var._end_op >= ip) { return sq_type(var._name) == OT_STRING ? _string(var._name) : NULL; } } return NULL; } void SQDebugServer::PrintStackVar(const SQFunctionProto *func, const SQInstruction *instr, unsigned int pos, stringbufext_t &buf) { buf.Put('['); SQString *var = GetLocalVarName(func, instr, pos); if (!var) { buf.PutInt((int)pos); } else { buf.Puts(var); } buf.Put(']'); } void SQDebugServer::PrintOuter(const SQFunctionProto *func, int pos, stringbufext_t &buf) { Assert(sq_type(func->_outervalues[pos]._name) == OT_STRING); SQString *val = _string(func->_outervalues[pos]._name); buf.Put('['); buf.Puts(val); buf.Put(']'); } void SQDebugServer::PrintLiteral(const SQFunctionProto *func, int pos, stringbufext_t &buf) { const SQObjectPtr &val = func->_literals[pos]; switch (sq_type(val)) { case OT_INTEGER: case OT_FLOAT: case OT_BOOL: case OT_NULL: case OT_STRING: { string_t str = GetValue(val); if (str.len > 64) str.len = 64; buf.Puts(str); break; } case OT_CLASS: case OT_INSTANCE: { const classdef_t *def = FindClassDef(sq_type(val) == OT_CLASS ? _class(val) : _instance(val)->_class); if (def && def->name.ptr) { buf.Puts(GetType(val)); buf.Put(' '); string_t str; str.Assign(def->name.ptr + FMT_PTR_LEN + 1, def->name.len - FMT_PTR_LEN - 1); if (str.len > 64 - 9) str.len = 64 - 9; buf.Puts(str); break; } } default: buf.Puts(GetType(val)); } } void SQDebugServer::PrintStackTarget(const SQFunctionProto *func, const SQInstruction *instr, stringbufext_t &buf) { if (instr->_arg0 != 0xFF) { s_nTargetAssignment = 1; PrintStackVar(func, instr, instr->_arg0, buf); s_nTargetAssignment = 0; buf.Puts(" = "); } } void SQDebugServer::PrintStackTargetVar(const SQFunctionProto *func, const SQInstruction *instr, stringbufext_t &buf) { if (instr->_arg0 != 0xFF) { s_nTargetAssignment = 1; PrintStackVar(func, instr, instr->_arg0, buf); s_nTargetAssignment = 0; } } void SQDebugServer::PrintDeref(const SQFunctionProto *func, const SQInstruction *instr, unsigned int self, unsigned int key, stringbufext_t &buf) { PrintStackVar(func, instr, self, buf); buf.Puts("->"); PrintStackVar(func, instr, key, buf); } void SQDebugServer::DescribeInstruction(const SQInstruction *instr, const SQFunctionProto *func, stringbufext_t &buf) { #if SQUIRREL_VERSION_NUMBER < 212 return; #else buf.Puts(g_InstructionName[instr->op]); buf.Put(' '); switch (instr->op) { case _OP_LOADNULLS: { if (instr->_arg1 == 1) { PrintStackVar(func, instr, instr->_arg0, buf); } else { buf.Put('['); buf.PutInt((int)instr->_arg0); buf.Put(']'); buf.Put(' '); buf.PutInt(instr->_arg1); } break; } case _OP_LOADINT: { PrintStackTarget(func, instr, buf); buf.PutInt(instr->_arg1); break; } case _OP_LOADFLOAT: { PrintStackTarget(func, instr, buf); #if SQUIRREL_VERSION_NUMBER >= 300 LFLOAT: #endif string_t val = SpecialFloatValue(*(SQFloat *)&instr->_arg1); if (!val.ptr) { int l = snprintf(buf.ptr + buf.len, buf.BytesLeft(), "%g", *(SQFloat *)&instr->_arg1); if (l < 0 || l > buf.BytesLeft()) l = buf.BytesLeft(); buf.len += l; } else { buf.Puts(val); } break; } case _OP_LOADBOOL: { PrintStackTarget(func, instr, buf); #if SQUIRREL_VERSION_NUMBER >= 300 LBOOL: #endif if (instr->_arg1) { buf.Puts("true"); } else { buf.Puts("false"); } break; } case _OP_FOREACH: { PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(", "); PrintStackVar(func, instr, instr->_arg2 + 1, buf); buf.Puts(" in "); PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" jmp "); buf.PutInt(instr->_arg1); break; } case _OP_POSTFOREACH: { PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" jmp "); buf.PutInt(instr->_arg1 - 1); break; } case _OP_PUSHTRAP: { PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" jmp "); buf.PutInt(instr->_arg1); break; } case _OP_TAILCALL: case _OP_CALL: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg1, buf); buf.Put(' '); buf.PutInt(instr->_arg3); break; } case _OP_RETURN: case _OP_YIELD: { if (instr->_arg0 != 0xFF) { PrintStackVar(func, instr, instr->_arg1, buf); } else { buf.len--; } break; } case _OP_MOVE: case _OP_NEG: case _OP_NOT: case _OP_BWNOT: case _OP_TYPEOF: case _OP_RESUME: case _OP_CLONE: { PrintStackTarget(func, instr, buf); switch (instr->op) { case _OP_MOVE: break; case _OP_NEG: buf.Put('-'); break; case _OP_NOT: buf.Put('!'); break; case _OP_BWNOT: buf.Put('~'); break; case _OP_TYPEOF: buf.Puts("typeof "); break; case _OP_RESUME: buf.Puts("resume "); break; case _OP_CLONE: buf.Puts("clone "); break; default: UNREACHABLE(); } PrintStackVar(func, instr, instr->_arg1, buf); break; } #if SQUIRREL_VERSION_NUMBER >= 300 case _OP_LOADROOT: #else case _OP_LOADROOTTABLE: #endif case _OP_THROW: { PrintStackVar(func, instr, instr->_arg0, buf); break; } case _OP_LOAD: { PrintStackTarget(func, instr, buf); PrintLiteral(func, instr->_arg1, buf); break; } case _OP_DLOAD: { PrintStackTarget(func, instr, buf); PrintLiteral(func, instr->_arg1, buf); buf.Puts(", "); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" = "); PrintLiteral(func, instr->_arg3, buf); break; } case _OP_DMOVE: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg1, buf); buf.Puts(", "); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" = "); PrintStackVar(func, instr, instr->_arg3, buf); break; } case _OP_GETK: case _OP_PREPCALLK: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts("->"); PrintLiteral(func, instr->_arg1, buf); break; } case _OP_PREPCALL: { PrintStackTarget(func, instr, buf); PrintDeref(func, instr, instr->_arg2, instr->_arg1, buf); break; } case _OP_DELETE: case _OP_GET: case _OP_SET: { PrintStackTarget(func, instr, buf); PrintDeref(func, instr, instr->_arg1, instr->_arg2, buf); if (instr->op == _OP_SET) { buf.Puts(" = "); PrintStackVar(func, instr, instr->_arg3, buf); } break; } case _OP_NEWSLOT: { PrintStackTarget(func, instr, buf); PrintDeref(func, instr, instr->_arg1, instr->_arg2, buf); buf.Puts(" = "); PrintStackVar(func, instr, instr->_arg3, buf); break; } case _OP_NEWSLOTA: { if (instr->_arg0 & NEW_SLOT_STATIC_FLAG) buf.Puts("static "); PrintDeref(func, instr, instr->_arg1, instr->_arg2, buf); buf.Puts(" = "); PrintStackVar(func, instr, instr->_arg3, buf); if (instr->_arg0 & NEW_SLOT_ATTRIBUTES_FLAG) { buf.Puts(", "); PrintStackVar(func, instr, instr->_arg2 - 1, buf); } break; } case _OP_EXISTS: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" in "); PrintStackVar(func, instr, instr->_arg1, buf); break; } case _OP_INSTANCEOF: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" instanceof "); PrintStackVar(func, instr, instr->_arg1, buf); break; } case _OP_AND: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" jz "); buf.PutInt(DeduceJumpCount(instr)); break; } case _OP_OR: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Puts(" jnz "); buf.PutInt(DeduceJumpCount(instr)); break; } case _OP_JZ: #if SQUIRREL_VERSION_NUMBER < 300 case _OP_JNZ: #endif { PrintStackVar(func, instr, instr->_arg0, buf); buf.Put(' '); case _OP_JMP: buf.PutInt(DeduceJumpCount(instr)); break; } case _OP_EQ: case _OP_NE: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); if (instr->op == _OP_EQ) { buf.Puts(" == "); } else { buf.Puts(" != "); } if (instr->_arg3 == 0) { PrintStackVar(func, instr, instr->_arg1, buf); } else { PrintLiteral(func, instr->_arg1, buf); } break; } #if SQUIRREL_VERSION_NUMBER >= 300 case _OP_JCMP: case _OP_CMP: { if (instr->op == _OP_CMP) #else case _OP_CMP: { #endif PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); switch (instr->_arg3) { case CMP_G: buf.Puts(" > "); break; case CMP_GE: buf.Puts(" >= "); break; case CMP_L: buf.Puts(" < "); break; case CMP_LE: buf.Puts(" <= "); break; #if SQUIRREL_VERSION_NUMBER >= 300 case CMP_3W: buf.Puts(" <=> "); break; #endif default: UNREACHABLE(); } #if SQUIRREL_VERSION_NUMBER >= 300 if (instr->op == _OP_JCMP) { PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" jz "); buf.PutInt(DeduceJumpCount(instr)); } else #endif { PrintStackVar(func, instr, instr->_arg1, buf); } break; } case _OP_BITW: { unsigned char lhs = instr->_arg2; unsigned char rhs = (unsigned char)instr->_arg1; if (instr->_arg0 != lhs) PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, lhs, buf); buf.Put(' '); switch (instr->_arg3) { case BW_AND: buf.Put('&'); break; case BW_OR: buf.Put('|'); break; case BW_XOR: buf.Put('^'); break; case BW_SHIFTL: buf.Puts("<<"); break; case BW_SHIFTR: buf.Puts(">>"); break; case BW_USHIFTR: buf.Puts(">>>"); break; default: UNREACHABLE(); } if (instr->_arg0 == lhs) buf.Put('='); buf.Put(' '); PrintStackVar(func, instr, rhs, buf); break; } #if SQUIRREL_VERSION_NUMBER >= 300 case _OP_ADD: case _OP_SUB: case _OP_MUL: case _OP_DIV: case _OP_MOD: { unsigned char lhs = instr->_arg2; unsigned char rhs = (unsigned char)instr->_arg1; if (instr->_arg0 != lhs) PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, lhs, buf); buf.Put(' '); switch (instr->op) { case _OP_ADD: buf.Put('+'); break; case _OP_SUB: buf.Put('-'); break; case _OP_MUL: buf.Put('*'); break; case _OP_DIV: buf.Put('/'); break; case _OP_MOD: buf.Put('%'); break; default: UNREACHABLE(); } if (instr->_arg0 == lhs) buf.Put('='); buf.Put(' '); PrintStackVar(func, instr, rhs, buf); break; } #else case _OP_ARITH: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg2, buf); buf.Put(' '); buf.Put(instr->_arg3); buf.Put(' '); PrintStackVar(func, instr, instr->_arg1, buf); break; } case _OP_COMPARITHL: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg1, buf); buf.Put(' '); buf.Put(instr->_arg3); buf.Put('='); buf.Put(' '); PrintStackVar(func, instr, instr->_arg2, buf); break; } #endif case _OP_COMPARITH: { PrintStackTarget(func, instr, buf); PrintDeref(func, instr, ((unsigned int)instr->_arg1 & 0xFFFF0000) >> 16, instr->_arg2, buf); buf.Put(' '); buf.Put(instr->_arg3); buf.Put('='); buf.Put(' '); PrintStackVar(func, instr, (instr->_arg1 & 0x0000FFFF), buf); break; } case _OP_INCL: case _OP_INC: { if (instr->_arg0 != instr->_arg1) PrintStackTarget(func, instr, buf); if (instr->_arg3 == (unsigned char)-1) { buf.Puts("--"); } else { buf.Puts("++"); } if (instr->op == _OP_INCL) { PrintStackVar(func, instr, instr->_arg1, buf); } else { PrintDeref(func, instr, instr->_arg1, instr->_arg2, buf); } break; } case _OP_PINCL: case _OP_PINC: { PrintStackTarget(func, instr, buf); if (instr->op == _OP_PINCL) { PrintStackVar(func, instr, instr->_arg1, buf); } else { PrintDeref(func, instr, instr->_arg1, instr->_arg2, buf); } if (instr->_arg3 == (unsigned char)-1) { buf.Puts("--"); } else { buf.Puts("++"); } break; } #if SQUIRREL_VERSION_NUMBER >= 300 case _OP_SETOUTER: case _OP_GETOUTER: #else case _OP_LOADFREEVAR: #endif { PrintStackTarget(func, instr, buf); PrintOuter(func, instr->_arg1, buf); #if SQUIRREL_VERSION_NUMBER >= 300 if (instr->op == _OP_SETOUTER) { buf.Puts(" = "); PrintStackVar(func, instr, instr->_arg2, buf); } #endif break; } case _OP_CLOSURE: { PrintStackTargetVar(func, instr, buf); #if SQUIRREL_VERSION_NUMBER >= 300 if (instr->_arg2 != 0xFF) { buf.Put(' '); PrintStackVar(func, instr, instr->_arg2, buf); } #endif break; } #if SQUIRREL_VERSION_NUMBER >= 300 case _OP_APPENDARRAY: { PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" <- "); switch (instr->_arg2) { case AAT_STACK: PrintStackVar(func, instr, instr->_arg1, buf); break; case AAT_INT: buf.PutInt(instr->_arg1); break; case AAT_FLOAT: goto LFLOAT; case AAT_BOOL: goto LBOOL; case AAT_LITERAL: PrintLiteral(func, instr->_arg1, buf); break; default: UNREACHABLE(); } break; } case _OP_NEWOBJ: { PrintStackTarget(func, instr, buf); switch (instr->_arg3) { case NOT_TABLE: { buf.Puts("TABLE "); buf.PutInt(instr->_arg1); break; } case NOT_ARRAY: { buf.Puts("ARRAY "); buf.PutInt(instr->_arg1); break; } case NOT_CLASS: { buf.Puts("CLASS"); if (instr->_arg1 != -1) { buf.Puts(" extends "); PrintStackVar(func, instr, instr->_arg1, buf); } break; } default: UNREACHABLE(); } break; } #else case _OP_APPENDARRAY: { PrintStackVar(func, instr, instr->_arg0, buf); buf.Puts(" <- "); if (instr->_arg3 != 0) { PrintLiteral(func, instr->_arg1, buf); } else { PrintStackVar(func, instr, instr->_arg1, buf); } break; } case _OP_CLASS: { PrintStackTargetVar(func, instr, buf); if (instr->_arg1 != -1) { buf.Puts(" extends "); PrintStackVar(func, instr, instr->_arg1, buf); } break; } case _OP_NEWTABLE: case _OP_NEWARRAY: { PrintStackTargetVar(func, instr, buf); buf.Put(' '); buf.PutInt(instr->_arg1); break; } case _OP_DELEGATE: { PrintStackTarget(func, instr, buf); PrintStackVar(func, instr, instr->_arg1, buf); buf.Puts(" <- "); PrintStackVar(func, instr, instr->_arg2, buf); break; } case _OP_VARGC: { PrintStackVar(func, instr, instr->_arg0, buf); break; } case _OP_GETVARGV: { PrintStackTarget(func, instr, buf); buf.Puts("vargv"); buf.Puts("->"); PrintStackVar(func, instr, instr->_arg1, buf); break; } #endif default: { buf.len--; } } #endif } bool SQDebugServer::IsValidStackFrame(HSQUIRRELVM vm, int frame) { Assert(!!vm->_callsstacksize == !!vm->ci); return frame >= 0 && frame < vm->_callsstacksize && vm->_callsstacksize != 0; } SQVM::CallInfo *SQDebugServer::GetStackFrame(HSQUIRRELVM vm, int frame) { Assert(!!vm->_callsstacksize == !!vm->ci); Assert(frame == INVALID_FRAME || frame >= 0); if (frame >= 0 && frame < vm->_callsstacksize && vm->_callsstacksize != 0) return &vm->_callsstack[frame]; return NULL; } int SQDebugServer::GetStackBase(HSQUIRRELVM vm, const SQVM::CallInfo *ci) { Assert(ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize); int stackbase = 0; for (; ci >= vm->_callsstack; ci--) stackbase += ci->_prevstkbase; return stackbase; } void SQDebugServer::InitEnv_GetVal(SQObjectPtr &env) { Assert(sq_type(env) == OT_NULL || (sq_type(env) == OT_TABLE && _table(env)->_delegate && !_table(env)->_delegate->_delegate)); SQTable *mt; if (sq_type(env) != OT_TABLE) { SQObjectPtr _null; mt = SQTable::Create(_ss(m_pRootVM), 6); mt->NewSlot(m_sqstrCallFrame, _null); mt->NewSlot(m_sqstrDelegate, _null); Assert(sq_type(env) == OT_NULL); env = SQTable::Create(_ss(m_pRootVM), 0); _table(env)->SetDelegate(mt); } else { mt = _table(env)->_delegate; } #ifdef CLOSURE_ROOT mt->NewSlot(m_sqstrRoot, m_pRootVM->_roottable); #endif mt->NewSlot(CreateSQString(m_pRootVM, _SC("_get")), m_sqfnGet); mt->NewSlot(CreateSQString(m_pRootVM, _SC("_set")), m_sqfnSet); mt->NewSlot(CreateSQString(m_pRootVM, _SC("_newslot")), m_sqfnNewSlot); } void SQDebugServer::SetCallFrame(SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci) { Assert(sq_type(env) == OT_TABLE); Assert(_table(env)->_delegate); Assert(ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize); SQObjectPtr frame = (SQInteger)(ci - vm->_callsstack); _table(env)->_delegate->NewSlot(m_sqstrCallFrame, frame); } void SQDebugServer::SetEnvDelegate(SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci) { Assert(sq_type(env) == OT_TABLE); Assert(_table(env)->_delegate); SQObjectPtr weakref; const SQObjectPtr &delegate = vm->_stack._vals[GetStackBase(vm, ci)]; if (ISREFCOUNTED(sq_type(delegate))) weakref = GetWeakRef(_refcounted(delegate), sq_type(delegate)); _table(env)->_delegate->NewSlot(m_sqstrDelegate, weakref); } void SQDebugServer::ClearEnvDelegate(SQObjectPtr &env) { Assert(sq_type(env) == OT_TABLE); Assert(_table(env)->_delegate); SQObjectPtr _null; _table(env)->_delegate->Set(m_sqstrDelegate, _null); } #ifdef CLOSURE_ROOT void SQDebugServer::SetEnvRoot(SQObjectPtr &env, const SQObjectPtr &root) { Assert(sq_type(env) == OT_TABLE); Assert(_table(env)->_delegate); Assert(sq_type(root) == OT_TABLE || (sq_type(root) == OT_WEAKREF && sq_type(_weakref(root)->_obj) == OT_TABLE)); _table(env)->_delegate->NewSlot(m_sqstrRoot, root); } #endif bool SQDebugServer::RunExpression(const string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &out, bool multiline) { Assert(!ci || (ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize)); // Fallback to root on native stack frame if (!ci || sq_type(ci->_closure) != OT_CLOSURE) #ifdef CLOSURE_ROOT return RunScript(vm, expression, NULL, NULL, out, multiline); #else return RunScript(vm, expression, NULL, out, multiline); #endif #ifdef CLOSURE_ROOT bool bRoot = _closure(ci->_closure)->_root && _table(_closure(ci->_closure)->_root->_obj) != _table(m_pRootVM->_roottable); if (bRoot) SetEnvRoot(m_EnvGetVal, _closure(ci->_closure)->_root); #endif SetCallFrame(m_EnvGetVal, vm, ci); SetEnvDelegate(m_EnvGetVal, vm, ci); #ifdef CLOSURE_ROOT SQWeakRef *root = bRoot ? _closure(ci->_closure)->_root : NULL; bool ret = RunScript(vm, expression, root, &m_EnvGetVal, out, multiline); #else bool ret = RunScript(vm, expression, &m_EnvGetVal, out, multiline); #endif #ifdef CLOSURE_ROOT if (bRoot) SetEnvRoot(m_EnvGetVal, m_pRootVM->_roottable); #endif return ret; } bool SQDebugServer::CompileScript(const string_t &script, SQObjectPtr &out) { const bool multiline = false; unsigned int size; SQChar *buf, *scratch; if (!multiline) { #ifdef SQUNICODE size = sq_rsl(STRLEN("return()") + SQUnicodeLength(script.ptr, script.len) + 1); #else size = STRLEN("return()") + script.len + 1; #endif buf = (SQChar *)ScratchPad(size); scratch = buf; if (!buf) return false; memcpy(buf, _SC("return("), sq_rsl(STRLEN("return("))); // )) buf += STRLEN("return("); } else { #ifdef SQUNICODE size = sq_rsl(SQUnicodeLength(script.ptr, script.len) + 1); #else size = script.len + 1; #endif buf = (SQChar *)ScratchPad(size); scratch = buf; if (!buf) return false; } #ifdef SQUNICODE buf += UTF8ToSQUnicode(buf, size - ((char *)buf - (char *)scratch), script.ptr, script.len); #else memcpy(buf, script.ptr, script.len); buf += script.len; #endif if (!multiline) *buf++ = _SC(')'); *buf = 0; Assert(((char *)buf - (char *)scratch) == (int)(size - sq_rsl(1))); if (SQ_SUCCEEDED(sq_compilebuffer(m_pCurVM, scratch, size, _SC("sqdbg"), SQFalse))) { // Don't create varargs on calls SQFunctionProto *fn = _fp(_closure(m_pCurVM->Top())->_function); if (fn->_varparams) { fn->_varparams = false; #if SQUIRREL_VERSION_NUMBER >= 300 fn->_nparameters--; #endif } out = m_pCurVM->Top(); m_pCurVM->Pop(); return true; } return false; } bool SQDebugServer::RunScript(HSQUIRRELVM vm, const string_t &script, #ifdef CLOSURE_ROOT SQWeakRef *root, #endif const SQObject *env, SQObjectPtr &out, bool multiline) { unsigned int size; SQChar *buf, *scratch; if (!multiline) { #ifdef SQUNICODE size = sq_rsl(STRLEN("return()") + SQUnicodeLength(script.ptr, script.len) + 1); #else size = STRLEN("return()") + script.len + 1; #endif buf = (SQChar *)ScratchPad(size); scratch = buf; if (!buf) return false; memcpy(buf, _SC("return("), sq_rsl(STRLEN("return("))); // )) buf += STRLEN("return("); } else { #ifdef SQUNICODE size = sq_rsl(SQUnicodeLength(script.ptr, script.len) + 1); #else size = script.len + 1; #endif buf = (SQChar *)ScratchPad(size); scratch = buf; if (!buf) return false; } #ifdef SQUNICODE buf += UTF8ToSQUnicode(buf, size - ((char *)buf - (char *)scratch), script.ptr, script.len); #else memcpy(buf, script.ptr, script.len); buf += script.len; #endif if (!multiline) *buf++ = _SC(')'); *buf = 0; Assert(((char *)buf - (char *)scratch) == (int)(size - sq_rsl(1))); if (SQ_SUCCEEDED(sq_compilebuffer(vm, scratch, size, _SC("sqdbg"), SQFalse))) { // Don't create varargs on calls SQFunctionProto *fn = _fp(_closure(vm->Top())->_function); if (fn->_varparams) { fn->_varparams = false; #if SQUIRREL_VERSION_NUMBER >= 300 fn->_nparameters--; #endif } CCallGuard cg(this, vm); // m_pCurVM will incorrectly change if a script is executed on a different thread. // save and restore HSQUIRRELVM curvm = m_pCurVM; #ifdef CLOSURE_ROOT if (root) _closure(vm->Top())->SetRoot(root); #endif vm->Push(env ? *env : vm->_roottable); if (SQ_SUCCEEDED(sq_call(vm, 1, SQTrue, SQFalse))) { m_pCurVM = curvm; out = vm->Top(); vm->Pop(); vm->Pop(); return true; } m_pCurVM = curvm; vm->Pop(); } return false; } bool SQDebugServer::RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, SQObjectPtr &ret) { vm->Push(closure); vm->Push(env ? *env : vm->_roottable); if (SQ_SUCCEEDED(sq_call(vm, 1, SQTrue, SQFalse))) { ret = vm->Top(); vm->Pop(); vm->Pop(); return true; } vm->Pop(); return false; } bool SQDebugServer::RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, SQObjectPtr &ret) { vm->Push(closure); vm->Push(env ? *env : vm->_roottable); vm->Push(p1); if (SQ_SUCCEEDED(sq_call(vm, 2, SQTrue, SQFalse))) { ret = vm->Top(); vm->Pop(); vm->Pop(); return true; } vm->Pop(); return false; } bool SQDebugServer::RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret) { vm->Push(closure); vm->Push(env ? *env : vm->_roottable); vm->Push(p1); vm->Push(p2); if (SQ_SUCCEEDED(sq_call(vm, 3, SQTrue, SQFalse))) { ret = vm->Top(); vm->Pop(); vm->Pop(); return true; } vm->Pop(); return false; } bool SQDebugServer::RunClosure(HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObjectPtr *argv, int argc, SQObjectPtr &ret) { vm->Push(closure); for (int i = 0; i < argc; i++) vm->Push(argv[i]); if (SQ_SUCCEEDED(sq_call(vm, argc, SQTrue, SQFalse))) { ret = vm->Top(); vm->Pop(); vm->Pop(); return true; } vm->Pop(); return false; } SQInteger SQDebugServer::SQMM_Get(HSQUIRRELVM vm) { SQObjectPtr value; HSQOBJECT mtenv, index; sq_getstackobj(vm, -2, &mtenv); sq_getstackobj(vm, -1, &index); Assert(sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate); SQObjectPtr frame; Verify(SQTable_Get(_table(mtenv)->_delegate, _SC(KW_CALLFRAME), frame)); Assert(sq_type(frame) == OT_INTEGER && _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize); if (!(_integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize)) { vm->Raise_Error(_SC("invalid call frame")); return -1; } if (GetVariable(vm, vm->_callsstack + _integer(frame), mtenv, index, value)) { sq_pushobject(vm, value); return 1; } vm->Raise_IdxError(index); return -1; } SQInteger SQDebugServer::SQMM_Set(HSQUIRRELVM vm) { HSQOBJECT mtenv, index, value; sq_getstackobj(vm, -3, &mtenv); sq_getstackobj(vm, -2, &index); sq_getstackobj(vm, -1, &value); Assert(sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate); SQObjectPtr frame; Verify(SQTable_Get(_table(mtenv)->_delegate, _SC(KW_CALLFRAME), frame)); Assert(sq_type(frame) == OT_INTEGER && _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize); if (!(_integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize)) { vm->Raise_Error(_SC("invalid call frame")); return -1; } if (SetVariable(vm, vm->_callsstack + _integer(frame), mtenv, index, value)) return 0; vm->Raise_IdxError(index); return -1; } SQInteger SQDebugServer::SQMM_NewSlot(HSQUIRRELVM vm) { HSQOBJECT mtenv, index, value; sq_getstackobj(vm, -3, &mtenv); sq_getstackobj(vm, -2, &index); sq_getstackobj(vm, -1, &value); if (NewSlot(vm, mtenv, index, value)) return 0; vm->Raise_Error(_SC("could not create new slot")); return -1; } bool SQDebugServer::GetVariable(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQObject &mtenv, const SQObject &index, SQObjectPtr &value) { Assert(!ci || (ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize)); // locals if (ci && sq_type(ci->_closure) == OT_CLOSURE && sq_type(index) == OT_STRING) { SQClosure *pClosure = _closure(ci->_closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci->_ip - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if (var._start_op <= ip && var._end_op + 1 >= ip && _string(index) == _string(var._name)) { int stackbase = GetStackBase(vm, ci); value = vm->_stack._vals[stackbase + var._pos]; return true; } } for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; if (_string(index) == _string(var._name)) { value = *_outervalptr(pClosure->_outervalues[i]); return true; } } // this/vargv keywords compile in the temp env, add custom keywords to redirect // Having locals named __this/__vargv will break this hack if (_string(index)->_len == 6 || _string(index)->_len == 7) { if (IsEqual(_SC(KW_THIS), _string(index))) { int stackbase = GetStackBase(vm, ci); value = vm->_stack._vals[stackbase]; return true; } #if SQUIRREL_VERSION_NUMBER >= 300 else if (func->_varparams && func->_nlocalvarinfos >= 2) { if (IsEqual(_SC(KW_VARGV), _string(index))) { const SQLocalVarInfo &var = func->_localvarinfos[func->_nlocalvarinfos - 2]; if (IsEqual(_SC("vargv"), _string(var._name))) { int stackbase = GetStackBase(vm, ci); value = vm->_stack._vals[stackbase + 1]; return true; } } else if (IsEqual(_SC(KW_VARGC), _string(index))) { const SQLocalVarInfo &var = func->_localvarinfos[func->_nlocalvarinfos - 2]; if (IsEqual(_SC("vargv"), _string(var._name))) { int stackbase = GetStackBase(vm, ci); value = vm->_stack._vals[stackbase + 1]; if (sq_type(value) == OT_ARRAY) { value = _array(value)->Size(); return true; } value.Null(); return false; } } } #else else if (func->_varparams) { if (IsEqual(_SC(KW_VARGV), _string(index))) { // Accessor allows setting of vargv elements, // uses more memory and is a bit slower in general cases #if 0 SQDebugServer *dbg = sqdbg_get_debugger( vm ); Assert( dbg ); if ( sq_type(dbg->m_vargvDelegate) != OT_TABLE ) { SQNativeClosure *fnGet = SQNativeClosure::Create( _ss(vm), SQMM_VA_Get ); SQNativeClosure *fnSet = SQNativeClosure::Create( _ss(vm), SQMM_VA_Set ); fnGet->_nparamscheck = 2; fnSet->_nparamscheck = 3; SQTable *mt = SQTable::Create( _ss(vm), 2 ); mt->NewSlot( CreateSQString( vm, _SC("_get") ), fnGet ); mt->NewSlot( CreateSQString( vm, _SC("_set") ), fnSet ); dbg->m_vargvDelegate = mt; } SQUserData *ud = SQUserData::Create( _ss(vm), sizeof(int) ); ud->SetDelegate( _table(dbg->m_vargvDelegate) ); *(int*)ud->_val = (int)( ci - vm->_callsstack ); value = ud; return true; #else int size = ci->_vargs.size; SQArray *arr = SQArray::Create(_ss(vm), size); Assert(arr->Size() == size); for (int i = 0; i < size; i++) { const SQObjectPtr &val = vm->_vargsstack[ci->_vargs.base + i]; arr->_values[i] = val; } value = arr; return true; #endif } else if (IsEqual(_SC(KW_VARGC), _string(index))) { value = (SQInteger)ci->_vargs.size; return true; } } #endif } } Assert(sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate); // env SQObjectPtr env; Verify(SQTable_Get(_table(mtenv)->_delegate, _SC(KW_DELEGATE), env)); switch (sq_type(env)) { case OT_TABLE: { SQTable *t = _table(env); do { if (t->Get(index, value)) { return true; } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { if (_instance(env)->Get(index, value)) { return true; } break; } case OT_CLASS: { if (_class(env)->Get(index, value)) { return true; } break; } default: break; } // metamethods if (is_delegable(env) && _delegable(env)->_delegate) { SQObjectPtr mm; if (_delegable(env)->GetMetaMethod(vm, MT_GET, mm)) { if (RunClosure(vm, mm, &env, index, value)) { return true; } } } // root #ifdef CLOSURE_ROOT SQObjectPtr root; if (!SQTable_Get(_table(mtenv)->_delegate, _SC(KW_ROOT), root) || sq_type(root) != OT_TABLE) { // the user invalidated it, fix it root = _thread(_ss(vm)->_root_vm)->_roottable; _table(mtenv)->_delegate->NewSlot(CreateSQString(vm, _SC(KW_ROOT)), root); } #else const SQObjectPtr &root = vm->_roottable; #endif if (_refcounted(env) != _refcounted(root)) { SQTable *t = _table(root); do { if (t->Get(index, value)) { return true; } } while ((t = t->_delegate) != NULL); } return false; } bool SQDebugServer::SetVariable(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQObject &mtenv, const SQObject &index, const SQObject &value) { Assert(!ci || (ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize)); // locals if (ci && sq_type(ci->_closure) == OT_CLOSURE && sq_type(index) == OT_STRING) { SQClosure *pClosure = _closure(ci->_closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci->_ip - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if (var._start_op <= ip && var._end_op + 1 >= ip && _string(index) == _string(var._name)) { int stackbase = GetStackBase(vm, ci); vm->_stack._vals[stackbase + var._pos] = value; return true; } } for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; if (_string(index) == _string(var._name)) { *_outervalptr(pClosure->_outervalues[i]) = value; return true; } } } Assert(sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate); // env SQObjectPtr env; Verify(SQTable_Get(_table(mtenv)->_delegate, _SC(KW_DELEGATE), env)); switch (sq_type(env)) { case OT_TABLE: { SQTable *t = _table(env); do { if (t->Set(index, value)) { return true; } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { if (_instance(env)->Set(index, value)) { return true; } break; } case OT_CLASS: { return _class(env)->NewSlot(_ss(vm), index, value, false); } default: break; } // metamethods if (is_delegable(env) && _delegable(env)->_delegate) { SQObjectPtr mm; if (_delegable(env)->GetMetaMethod(vm, MT_SET, mm)) { SQObjectPtr dummy; if (RunClosure(vm, mm, &env, index, value, dummy)) { return true; } } } // root #ifdef CLOSURE_ROOT SQObjectPtr root; if (!SQTable_Get(_table(mtenv)->_delegate, _SC(KW_ROOT), root) || sq_type(root) != OT_TABLE) { // the user invalidated it, fix it root = _thread(_ss(vm)->_root_vm)->_roottable; _table(mtenv)->_delegate->NewSlot(CreateSQString(vm, _SC(KW_ROOT)), root); } #else const SQObjectPtr &root = vm->_roottable; #endif if (_refcounted(env) != _refcounted(root)) { SQTable *t = _table(root); do { if (t->Set(index, value)) { return true; } } while ((t = t->_delegate) != NULL); } return false; } bool SQDebugServer::NewSlot(HSQUIRRELVM vm, const SQObject &mtenv, const SQObject &index, const SQObject &value) { Assert(sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate); // env SQObjectPtr env; Verify(SQTable_Get(_table(mtenv)->_delegate, _SC(KW_DELEGATE), env)); Assert(sq_type(env) != OT_ARRAY); if (is_delegable(env) && _delegable(env)->_delegate) { SQObjectPtr mm; if (_delegable(env)->GetMetaMethod(vm, MT_NEWSLOT, mm)) { SQObjectPtr dummy; if (RunClosure(vm, mm, &env, index, value, dummy)) { return true; } } } switch (sq_type(env)) { case OT_TABLE: _table(env)->NewSlot(index, value); return true; case OT_CLASS: return _class(env)->NewSlot(_ss(vm), index, value, false); default: return false; } } bool SQDebugServer::Get(const objref_t &obj, SQObjectPtr &value) { if (obj.type & objref_t::PTR) { value = *obj.ptr; return true; } Assert(!(obj.type & objref_t::READONLY)); switch (obj.type) { case objref_t::TABLE: { return sq_type(obj.src) == OT_TABLE && _table(obj.src)->Get(obj.key, value); } case objref_t::INSTANCE: { return sq_type(obj.src) == OT_INSTANCE && _instance(obj.src)->Get(obj.key, value); } case objref_t::CLASS: { return sq_type(obj.src) == OT_CLASS && _class(obj.src)->Get(obj.key, value); } case objref_t::DELEGABLE_META: { if (is_delegable(obj.src) && _delegable(obj.src)->_delegate) { SQObjectPtr mm; if (_delegable(obj.src)->GetMetaMethod(m_pRootVM, MT_GET, mm)) { return RunClosure(mm, &obj.src, obj.key, value); } } return false; } case objref_t::ARRAY: { Assert(sq_type(obj.key) == OT_INTEGER); if (sq_type(obj.src) == OT_ARRAY && _integer(obj.key) >= 0 && _integer(obj.key) < _array(obj.src)->Size()) { value = _array(obj.src)->_values[_integer(obj.key)]; return true; } return false; } case objref_t::CUSTOMMEMBER: { if (sq_type(obj.src) == OT_INSTANCE) { const SQObjectPtr *def = GetClassDefCustomMembers(_instance(obj.src)->_class); if (def) { SQObjectPtr custommembers = *def; if (sq_type(custommembers) == OT_CLOSURE) RunClosure(custommembers, &obj.src, custommembers); if (sq_type(custommembers) == OT_ARRAY) { objref_t tmp; SQObjectPtr strName = CreateSQString(m_pRootVM, _SC("name")); SQObjectPtr strGet = CreateSQString(m_pRootVM, _SC("get")); SQObjectPtr name, get; for (unsigned int i = 0; i < _array(custommembers)->_values.size(); i++) { const SQObjectPtr &memdef = _array(custommembers)->_values[i]; if (GetObj_Var(memdef, strName, tmp, name) && IsEqual(obj.key, name)) { return GetObj_Var(memdef, strGet, tmp, get) && CallCustomMembersGetFunc(get, &obj.src, obj.key, value); } } } } } return false; } case objref_t::STACK: { HSQUIRRELVM vm = GetThread(obj.stack.thread); const SQVM::CallInfo *ci = vm->_callsstack + obj.stack.frame; if (vm && obj.stack.frame >= 0 && obj.stack.frame <= (int)(vm->ci - vm->_callsstack) && sq_type(ci->_closure) == OT_CLOSURE) { int ip = ci->_ip - _fp(_closure(ci->_closure)->_function)->_instructions; if (ip >= obj.stack.start && ip <= obj.stack.end && obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size()) { value = vm->_stack._vals[obj.stack.index]; return true; } } return false; } case objref_t::OUTER: { if (sq_type(obj.src) == OT_CLOSURE && _integer(obj.key) >= 0 && _integer(obj.key) < _fp(_closure(obj.src)->_function)->_noutervalues) { value = *_outervalptr(_closure(obj.src)->_outervalues[_integer(obj.key)]); return true; } return false; } #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST case objref_t::FUNCPROTO: { value = *obj.ptr; return true; } #endif case objref_t::INT: { value = (SQInteger)obj.val; return true; } case objref_t::VIRTUAL_REF: { if (ISREFCOUNTED(sq_type(obj.src))) { value = (SQInteger)_refcounted(obj.src)->_uiRef; return true; } return false; } case objref_t::VIRTUAL_SIZE: { switch (sq_type(obj.src)) { case OT_ARRAY: value = _array(obj.src)->Size(); return true; case OT_TABLE: value = _table(obj.src)->CountUsed(); return true; default: return false; } } case objref_t::VIRTUAL_ALLOCATED: { if (sq_type(obj.src) == OT_ARRAY) { value = (SQInteger)_array(obj.src)->_values.capacity(); return true; } return false; } case objref_t::VIRTUAL_STATE: { switch (sq_type(obj.src)) { case OT_THREAD: value = sq_getvmstate(_thread(obj.src)); return true; case OT_GENERATOR: value = (SQInteger)_generator(obj.src)->_state; return true; default: return false; } } default: UNREACHABLE(); } } bool SQDebugServer::Set(const objref_t &obj, const SQObjectPtr &value) { if (obj.type & objref_t::READONLY) return false; if (obj.type & objref_t::PTR) { *obj.ptr = value; return true; } switch (obj.type) { case objref_t::TABLE: { return sq_type(obj.src) == OT_TABLE && _table(obj.src)->Set(obj.key, value); } case objref_t::INSTANCE: { return sq_type(obj.src) == OT_INSTANCE && _instance(obj.src)->Set(obj.key, value); } case objref_t::CLASS: { SQObjectPtr idx; if (sq_type(obj.src) == OT_CLASS && _class(obj.src)->_members->Get(obj.key, idx)) { if (_isfield(idx)) { _class(obj.src)->_defaultvalues[_member_idx(idx)].val = value; } else { _class(obj.src)->_methods[_member_idx(idx)].val = value; } return true; } return false; } case objref_t::DELEGABLE_META: { if (is_delegable(obj.src) && _delegable(obj.src)->_delegate) { SQObjectPtr mm; if (_delegable(obj.src)->GetMetaMethod(m_pRootVM, MT_SET, mm)) { SQObjectPtr dummy; return RunClosure(mm, &obj.src, obj.key, value, dummy); } } return false; } case objref_t::ARRAY: { Assert(sq_type(obj.key) == OT_INTEGER); if (sq_type(obj.src) == OT_ARRAY && _integer(obj.key) >= 0 && _integer(obj.key) < _array(obj.src)->Size()) { _array(obj.src)->_values[_integer(obj.key)] = value; return true; } return false; } case objref_t::CUSTOMMEMBER: { if (sq_type(obj.src) == OT_INSTANCE) { const SQObjectPtr *def = GetClassDefCustomMembers(_instance(obj.src)->_class); if (def) { SQObjectPtr custommembers = *def; if (sq_type(custommembers) == OT_CLOSURE) RunClosure(custommembers, &obj.src, custommembers); if (sq_type(custommembers) == OT_ARRAY) { objref_t tmp; SQObjectPtr strName = CreateSQString(m_pRootVM, _SC("name")); SQObjectPtr strSet = CreateSQString(m_pRootVM, _SC("set")); SQObjectPtr name, set; for (unsigned int i = 0; i < _array(custommembers)->_values.size(); i++) { const SQObjectPtr &memdef = _array(custommembers)->_values[i]; if (GetObj_Var(memdef, strName, tmp, name) && IsEqual(obj.key, name)) { return GetObj_Var(memdef, strSet, tmp, set) && CallCustomMembersSetFunc(set, &obj.src, obj.key, value, set); } } } } } return false; } case objref_t::STACK: { HSQUIRRELVM vm = GetThread(obj.stack.thread); const SQVM::CallInfo *ci = vm->_callsstack + obj.stack.frame; if (vm && obj.stack.frame >= 0 && obj.stack.frame <= (int)(vm->ci - vm->_callsstack) && sq_type(ci->_closure) == OT_CLOSURE) { int ip = ci->_ip - _fp(_closure(ci->_closure)->_function)->_instructions; if (ip >= obj.stack.start && ip <= obj.stack.end && obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size()) { vm->_stack._vals[obj.stack.index] = value; return true; } } return false; } case objref_t::OUTER: { if (sq_type(obj.src) == OT_CLOSURE && _integer(obj.key) >= 0 && _integer(obj.key) < _fp(_closure(obj.src)->_function)->_noutervalues) { *_outervalptr(_closure(obj.src)->_outervalues[_integer(obj.key)]) = value; return true; } return false; } #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST case objref_t::FUNCPROTO: { if (sq_type(value) == OT_FUNCPROTO) { *obj.ptr = value; return true; } else if (sq_type(value) == OT_CLOSURE) { *obj.ptr = _closure(value)->_function; return true; } return false; } #endif default: UNREACHABLE(); } } #ifndef SQDBG_DISABLE_COMPILER bool SQDebugServer::NewSlot(const objref_t &obj, const SQObjectPtr &value) { if (obj.type & objref_t::READONLY) return false; if (is_delegable(obj.src) && _delegable(obj.src)->_delegate) { SQObjectPtr mm; if (_delegable(obj.src)->GetMetaMethod(m_pRootVM, MT_NEWSLOT, mm)) { SQObjectPtr dummy; if (RunClosure(mm, &obj.src, obj.key, value, dummy)) return CompileReturnCode_Success; } } switch (obj.type) { case objref_t::TABLE: { if (sq_type(obj.src) == OT_TABLE) { _table(obj.src)->NewSlot(obj.key, value); return true; } return false; } case objref_t::CLASS: { return sq_type(obj.src) == OT_CLASS && _class(obj.src)->NewSlot(_ss(m_pRootVM), obj.key, value, false); } default: return false; } } bool SQDebugServer::Delete(const objref_t &obj, SQObjectPtr &value) { if (obj.type & objref_t::READONLY) return false; if (is_delegable(obj.src) && _delegable(obj.src)->_delegate) { SQObjectPtr mm; if (_delegable(obj.src)->GetMetaMethod(m_pRootVM, MT_DELSLOT, mm)) { return RunClosure(mm, &obj.src, obj.key, value); } } switch (obj.type) { case objref_t::TABLE: { if (sq_type(obj.src) == OT_TABLE && _table(obj.src)->Get(obj.key, value)) { _table(obj.src)->Remove(obj.key); return true; } } default: return false; } } bool SQDebugServer::Increment(const objref_t &obj, int amt) { #define _check(var) \ switch (sq_type(var)) \ { \ case OT_INTEGER: \ _integer(var) += (SQInteger)amt; \ break; \ case OT_FLOAT: \ _float(var) += (SQFloat)amt; \ break; \ default: \ return false; \ } if (obj.type & objref_t::READONLY) return false; if (obj.type & objref_t::PTR) { _check(*obj.ptr); return true; } switch (obj.type) { case objref_t::TABLE: { if (sq_type(obj.src) == OT_TABLE) { SQObjectPtr value; if (_table(obj.src)->Get(obj.key, value)) { _check(value); return _table(obj.src)->Set(obj.key, value); } } return false; } case objref_t::INSTANCE: { if (sq_type(obj.src) == OT_INSTANCE) { SQObjectPtr value; if (_instance(obj.src)->Get(obj.key, value)) { _check(value); return _instance(obj.src)->Set(obj.key, value); } } return false; } case objref_t::ARRAY: { Assert(sq_type(obj.key) == OT_INTEGER); if (sq_type(obj.src) == OT_ARRAY && _integer(obj.key) >= 0 && _integer(obj.key) < _array(obj.src)->Size()) { SQObjectPtr &value = _array(obj.src)->_values[_integer(obj.key)]; _check(value); return true; } return false; } case objref_t::STACK: { HSQUIRRELVM vm = GetThread(obj.stack.thread); const SQVM::CallInfo *ci = vm->_callsstack + obj.stack.frame; if (vm && obj.stack.frame >= 0 && obj.stack.frame <= (int)(vm->ci - vm->_callsstack) && sq_type(ci->_closure) == OT_CLOSURE) { int ip = ci->_ip - _fp(_closure(ci->_closure)->_function)->_instructions; if (ip >= obj.stack.start && ip <= obj.stack.end && obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size()) { SQObjectPtr &value = vm->_stack._vals[obj.stack.index]; _check(value); return true; } } return false; } default: return false; } #undef _check } // // A very basic compiler that parses strings, characters, numbers (dec, hex, oct, bin, flt), identifiers, // block comments (/**/), // keywords (this, null, true, false), // unary operators (-, ~, !, typeof, delete, clone, *, &), // binary operators (+, -, *, /, %, <<, >>, >>>, &, |, ^, <, >, <=, >=, <=>, ==, !=, &&, ||, in, !in, instanceof), // ternary operator (a ? b : c), // prefix/postfix increment/decrement operators, // newslot and (compound) assignment operators, // root (::) and identifier access (a.b, a[b]), // function calls and grouping parantheses, // new array and table constructor ([1, 2, 3], {a = 1, [b] = 2}) // // Variable evaluation is done in given stack frame as they are parsed, state is not kept // class SQDebugServer::CCompiler { public: enum { Err_InvalidToken = -50, Err_UnfinishedComment, Err_UnfinishedString, Err_UnfinishedChar, Err_InvalidEscape, Err_InvalidOctalEscape, Err_InvalidXEscape, Err_InvalidU16Escape, Err_InvalidU32Escape, Err_InvalidDecimal, Err_InvalidOctal, Err_InvalidHexadecimal, Err_InvalidBinary, Err_InvalidFloat, Token_End = 1, Token_Ref, Token_Identifier, Token_String, Token_Integer, Token_Float, Token_Null, Token_False, Token_True, Token_This, Token_DoubleColon, Token_Delete, #if SQUIRREL_VERSION_NUMBER < 300 Token_Parent, Token_Vargv, Token_Vargc, #endif Token_File, Token_Line, Token_NewSlot, Token_PendingKey, Token_Operator, Token_Value, _op = 0xff00, // upper 4 bits : precedence // lower 4 bits : unique id Token_Not = 0x00 | _op, Token_BwNot = 0x01 | _op, Token_Typeof = 0x02 | _op, Token_Clone = 0x03 | _op, Token_Increment = 0x10 | _op, Token_Decrement = 0x11 | _op, Token_Mul = 0x20 | _op, Token_Div = 0x21 | _op, Token_Mod = 0x22 | _op, Token_Add = 0x30 | _op, Token_Sub = 0x31 | _op, Token_LShift = 0x40 | _op, Token_RShift = 0x41 | _op, Token_URShift = 0x42 | _op, Token_Cmp3W = 0x50 | _op, Token_Less = 0x60 | _op, Token_LessEq = 0x61 | _op, Token_Greater = 0x62 | _op, Token_GreaterEq = 0x63 | _op, Token_Eq = 0x70 | _op, Token_NotEq = 0x71 | _op, Token_BwAnd = 0x80 | _op, Token_BwXor = 0x90 | _op, Token_BwOr = 0xA0 | _op, Token_In = 0xB0 | _op, Token_NotIn = 0xB1 | _op, Token_InstanceOf = 0xB2 | _op, Token_LogicalAnd = 0xC0 | _op, Token_LogicalOr = 0xD0 | _op, _assign = 0xff0000, Token_Assign, Token_AssignAdd = _assign | Token_Add, Token_AssignSub = _assign | Token_Sub, Token_AssignMul = _assign | Token_Mul, Token_AssignDiv = _assign | Token_Div, Token_AssignMod = _assign | Token_Mod, Token_AssignAnd = _assign | Token_BwAnd, Token_AssignXor = _assign | Token_BwXor, Token_AssignOr = _assign | Token_BwOr, Token_AssignLS = _assign | Token_LShift, Token_AssignRS = _assign | Token_RShift, Token_AssignURS = _assign | Token_URShift, }; struct token_t { token_t() : type(0) {} int type; union { string_t _string; SQInteger _integer; SQFloat _float; }; }; #ifndef SQDBG_COMPILER_MAX_PARAMETER_COUNT #define SQDBG_COMPILER_MAX_PARAMETER_COUNT 8 #endif #ifndef SQDBG_COMPILER_MAX_UNARY_STACK #define SQDBG_COMPILER_MAX_UNARY_STACK 4 #endif private: struct callparams_t { SQObjectPtr params[SQDBG_COMPILER_MAX_PARAMETER_COUNT]; SQObjectPtr func; int paramCount; }; string_t &m_expr; char *m_cur; char *m_end; int m_prevToken; public: // To get partial matches for completions request token_t m_lastToken; // To add expression watchpoints // Only returns the last checked ref regardless of any other operations that come after // This is fine because adding illogical watchpoints doesn't have significant side effects - // prevention is at the user's discretion, // and the ability to add on expressions is useful objref_t m_lastRef; public: // Expression has to be NUL terminated CCompiler(string_t &expression) : m_expr(expression), m_cur(expression.ptr), m_end(expression.ptr + expression.len), m_prevToken(0), m_lastToken(), m_lastRef() { } CCompiler(const CCompiler &); CCompiler &operator=(const CCompiler &); ECompileReturnCode Evaluate(SQDebugServer *dbg, HSQUIRRELVM vm, int frame, SQObjectPtr &val, int closer = Token_End) { Assert(frame == INVALID_FRAME || IsValidStackFrame(vm, frame)); token_t token; int prevtoken = 0; int opbufidx = -1; int unaryidx = -1; unsigned char unarybuf[SQDBG_COMPILER_MAX_UNARY_STACK]; unsigned char opbuf[2]; char deleteop = 0; char incrop = 0; SQObjectPtr callenv; callenv._type = (SQObjectType)0; SQObjectPtr valbuf[2]; objref_t obj; obj.type = objref_t::INVALID; for (;; m_prevToken = token.type) { token = Lex(); switch (token.type) { case Token_Identifier: { if (!ExpectsIdentifier(prevtoken)) return CompileReturnCode_Unsupported; m_lastToken = token; if (!dbg->GetObj_Frame(vm, frame, token._string, obj, val)) { // allow non-existent key if (Next() != Token_NewSlot) return CompileReturnCode_DoesNotExist; // implicit this. val = frame != INVALID_FRAME ? vm->_stack._vals[GetStackBase(vm, frame)] : vm->_roottable; switch (sq_type(val)) { case OT_TABLE: obj.type = objref_t::TABLE; break; case OT_CLASS: obj.type = objref_t::CLASS; break; default: if (is_delegable(val) && _delegable(val)->_delegate) { obj.type = objref_t::DELEGABLE_META; break; } return CompileReturnCode_DoesNotExist; } obj.src = val; obj.key = CreateSQString(dbg, token._string); prevtoken = Token_PendingKey; } else { ConvertPtr(obj); m_lastRef = obj; prevtoken = Token_Ref; } break; } case Token_String: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; val = CreateSQString(dbg, token._string); prevtoken = Token_Value; break; } case Token_Integer: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; val = (SQInteger)token._integer; prevtoken = Token_Integer; break; } case Token_Float: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; val = (SQFloat)token._float; prevtoken = Token_Float; break; } case Token_Null: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; val.Null(); prevtoken = Token_Value; break; } case Token_False: case Token_True: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; val.Null(); val._type = OT_BOOL; val._unVal.nInteger = token._integer; prevtoken = Token_Value; break; } case Token_This: { if (!ExpectsIdentifier(prevtoken)) return CompileReturnCode_Unsupported; val = frame != INVALID_FRAME ? vm->_stack._vals[GetStackBase(vm, frame)] : vm->_roottable; prevtoken = Token_Value; break; } case Token_BwNot: case Token_Not: case Token_Typeof: case Token_Clone: { unary: // identifier boundary if (prevtoken == Token_Ref) { if (deleteop) { if (!dbg->Delete(obj, val)) return CompileReturnCode_OpFailure; deleteop = 0; prevtoken = Token_Value; } else if (incrop) { if (!dbg->Increment(obj, (incrop & 0x1) ? 1 : -1)) return CompileReturnCode_OpFailure; if (incrop & 0x8) // prefix dbg->Get(obj, val); incrop = 0; prevtoken = Token_Value; } } else if (deleteop || incrop) { return CompileReturnCode_OpFailure; } if (IsValue(prevtoken)) { while (unaryidx != -1) { if (!UnaryOp(dbg, unarybuf[unaryidx] | _op, val)) return CompileReturnCode_OpFailure; unaryidx--; } } if (!ExpectsValue(prevtoken)) { token = Lex(); if (token.type == Token_In) { token.type = Token_NotIn; goto binary; } return CompileReturnCode_Unsupported; } if (unaryidx + 1 >= (int)sizeof(unarybuf)) return CompileReturnCode_OpBufferFull; unaryidx++; unarybuf[unaryidx] = (unsigned char)(token.type & ~_op); prevtoken = Token_Operator; break; } case Token_Sub: #ifdef SUPPORTS_DEREF_OP case Token_Mul: case Token_BwAnd: #endif { if (ExpectsValue(prevtoken)) goto unary; } case Token_In: case Token_InstanceOf: case Token_Add: #ifndef SUPPORTS_DEREF_OP case Token_Mul: #endif case Token_Div: case Token_Mod: case Token_LShift: case Token_RShift: case Token_URShift: #ifndef SUPPORTS_DEREF_OP case Token_BwAnd: #endif case Token_BwXor: case Token_BwOr: case Token_LogicalAnd: case Token_LogicalOr: case Token_Greater: case Token_GreaterEq: case Token_Less: case Token_LessEq: case Token_Cmp3W: case Token_Eq: case Token_NotEq: { binary: // identifier boundary if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; if (prevtoken == Token_Ref) { if (deleteop) { if (!dbg->Delete(obj, val)) return CompileReturnCode_OpFailure; deleteop = 0; prevtoken = Token_Value; } else if (incrop) { if (!dbg->Increment(obj, (incrop & 0x1) ? 1 : -1)) return CompileReturnCode_OpFailure; if (incrop & 0x8) // prefix dbg->Get(obj, val); incrop = 0; prevtoken = Token_Value; } } else if (deleteop || incrop) { return CompileReturnCode_OpFailure; } while (unaryidx != -1) { if (!UnaryOp(dbg, unarybuf[unaryidx] | _op, val)) return CompileReturnCode_OpFailure; unaryidx--; } while (opbufidx != -1) { int prev = opbuf[opbufidx] | _op; // Higher or equal precedence if ((prev & 0xf0) <= (token.type & 0xf0)) { SQObjectPtr &lhs = valbuf[opbufidx]; if (!BinaryOp(dbg, prev, lhs, val, val)) return CompileReturnCode_OpFailure; lhs.Null(); opbufidx--; } else { break; } } // Don't evaluate both sides // In both shortcuts, lhs is returned if ((token.type == Token_LogicalAnd && IsFalse(val)) || (token.type == Token_LogicalOr && !IsFalse(val))) { ECompileReturnCode res = LexAll(closer, true); if (res != CompileReturnCode_Success) return res; if (m_prevToken != '?' && closer != Token_End) m_cur--; prevtoken = Token_Value; break; } Assert(opbufidx + 1 < 2); if (opbufidx + 1 >= 2) return CompileReturnCode_Unsupported; opbufidx++; opbuf[opbufidx] = (unsigned char)(token.type & ~_op); valbuf[opbufidx] = val; prevtoken = Token_Operator; break; } case Token_Increment: case Token_Decrement: { // decrement x000 // increment x001 // prefix 100x // postfix 000x if (prevtoken == Token_Ref) { incrop = (token.type == Token_Increment ? 0x11 : 0x10); } else if (ExpectsIdentifier(prevtoken)) { incrop = (token.type == Token_Increment ? 0x19 : 0x18); } else { return CompileReturnCode_Unsupported; } break; } case '?': { // identifier boundary if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; if (prevtoken == Token_Ref) { if (deleteop) { if (!dbg->Delete(obj, val)) return CompileReturnCode_OpFailure; deleteop = 0; prevtoken = Token_Value; } else if (incrop) { if (!dbg->Increment(obj, (incrop & 0x1) ? 1 : -1)) return CompileReturnCode_OpFailure; if (incrop & 0x8) // prefix dbg->Get(obj, val); incrop = 0; prevtoken = Token_Value; } } else if (deleteop || incrop) { return CompileReturnCode_OpFailure; } while (unaryidx != -1) { if (!UnaryOp(dbg, unarybuf[unaryidx] | _op, val)) return CompileReturnCode_OpFailure; unaryidx--; } while (opbufidx != -1) { if (!BinaryOp(dbg, opbuf[opbufidx] | _op, valbuf[opbufidx], val, val)) return CompileReturnCode_OpFailure; opbufidx--; } if (!IsFalse(val)) { ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ':'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ':') return CompileReturnCode_Unsupported; res = LexAll(closer); if (res != CompileReturnCode_Success) return res; if (closer != Token_End) m_cur--; } else { ECompileReturnCode res = LexAll(':'); if (res != CompileReturnCode_Success) return res; res = Evaluate(dbg, vm, frame, val, closer); if (res != CompileReturnCode_Success) return res; if (closer != Token_End) m_cur--; } prevtoken = Token_Value; break; } case Token_DoubleColon: { if (!ExpectsIdentifier(prevtoken)) return CompileReturnCode_Unsupported; token = Lex(); #ifdef CLOSURE_ROOT const SQVM::CallInfo *ci = vm->_callsstack + frame; const SQObjectPtr &root = (frame != INVALID_FRAME && sq_type(ci->_closure) == OT_CLOSURE && _closure(ci->_closure)->_root) ? _closure(ci->_closure)->_root->_obj : vm->_roottable; #else const SQObjectPtr &root = vm->_roottable; #endif if (token.type != Token_Identifier) { m_lastToken.type = 0; val = root; return CompileReturnCode_Unsupported; } m_lastToken = token; if (!dbg->GetObj_Var(root, token._string, true, obj, val)) { // allow non-existent key if (Next() != Token_NewSlot) return CompileReturnCode_DoesNotExist; Assert(sq_type(root) == OT_TABLE); obj.type = objref_t::TABLE; obj.src = root; obj.key = CreateSQString(dbg, token._string); prevtoken = Token_PendingKey; } else { ConvertPtr(obj); m_lastRef = obj; prevtoken = Token_Ref; } break; } case '.': { if (!IsValue(prevtoken) || prevtoken == Token_Integer || prevtoken == Token_Float) return CompileReturnCode_Unsupported; token_t next = Lex(); if (next.type == INTERNAL_TAG_PREFIX) { next = Lex(); if (next.type == Token_Identifier) { if (next._string.IsEqualTo("refs")) { if (ISREFCOUNTED(sq_type(val))) { SQInteger refs = (SQInteger)_refcounted(val)->_uiRef - 1; obj.type = objref_t::VIRTUAL_REF; obj.src = val; val = refs; m_lastRef = obj; prevtoken = Token_Value; break; } } else if (next._string.IsEqualTo("size")) { if (sq_type(val) == OT_ARRAY) { obj.type = objref_t::VIRTUAL_SIZE; obj.src = val; val = _array(val)->Size(); m_lastRef = obj; prevtoken = Token_Value; break; } else if (sq_type(val) == OT_TABLE) { obj.type = objref_t::VIRTUAL_SIZE; obj.src = val; val = _table(val)->CountUsed(); m_lastRef = obj; prevtoken = Token_Value; break; } } else if (next._string.IsEqualTo("allocated")) { if (sq_type(val) == OT_ARRAY) { obj.type = objref_t::VIRTUAL_ALLOCATED; obj.src = val; val = (SQInteger)_array(val)->_values.capacity(); m_lastRef = obj; prevtoken = Token_Value; break; } } else if (next._string.IsEqualTo("state")) { if (sq_type(val) == OT_THREAD) { obj.type = objref_t::VIRTUAL_STATE; obj.src = val; val = sq_getvmstate(_thread(val)); m_lastRef = obj; prevtoken = Token_Value; break; } else if (sq_type(val) == OT_GENERATOR) { obj.type = objref_t::VIRTUAL_STATE; obj.src = val; val = (SQInteger)_generator(val)->_state; m_lastRef = obj; prevtoken = Token_Value; break; } } } m_lastToken.type = '.'; return CompileReturnCode_Unsupported; } else if (next.type != Token_Identifier) { m_lastToken.type = '.'; return CompileReturnCode_Unsupported; } m_lastToken = next; int nexttoken = Next(); // cur.next() - save 'cur' as the call env if (nexttoken == '(') callenv = val; SQObjectPtr tmp; if (!dbg->GetObj_Var(val, next._string, true, obj, tmp)) { SQTable *del = GetDefaultDelegate(vm, sq_type(val)); if (!del || !SQTable_Get(dbg, del, next._string, tmp)) { // allow non-existent key if (nexttoken != Token_NewSlot) return CompileReturnCode_DoesNotExist; switch (sq_type(val)) { case OT_TABLE: obj.type = objref_t::TABLE; break; case OT_CLASS: obj.type = objref_t::CLASS; break; default: if (is_delegable(val) && _delegable(val)->_delegate) { obj.type = objref_t::DELEGABLE_META; break; } return CompileReturnCode_DoesNotExist; } obj.src = val; obj.key = CreateSQString(dbg, next._string); prevtoken = Token_PendingKey; } else { callenv = val; val = tmp; prevtoken = Token_Value; } } else { ConvertPtr(obj); m_lastRef = obj; val = tmp; prevtoken = Token_Ref; } break; } case '{': { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; // new table SQObjectPtr key; SQTable *tbl = SQTable::Create(_ss(vm), 0); for (;;) { token_t next = Lex(); if (next.type == '[') { ECompileReturnCode res = Evaluate(dbg, vm, frame, key, ']'); if (res != CompileReturnCode_Success) { val = key; tbl->Release(); return res; } if (m_prevToken != ']') { val = key; tbl->Release(); return CompileReturnCode_Unsupported; } } else if (next.type == Token_Identifier) { key = CreateSQString(dbg, next._string); } else if (next.type == '}') { break; } else { tbl->Release(); return CompileReturnCode_Unsupported; } next = Lex(); if (next.type != Token_Assign) { tbl->Release(); return CompileReturnCode_Unsupported; } ECompileReturnCode res = Evaluate(dbg, vm, frame, val, '}'); if (res != CompileReturnCode_Success) { tbl->Release(); return res; } tbl->NewSlot(key, val); val.Null(); if (m_prevToken == ',') continue; if (m_prevToken == '}') break; tbl->Release(); return CompileReturnCode_Unsupported; } val = tbl; prevtoken = Token_Value; break; } case '[': { // new array if (ExpectsValue(prevtoken)) { SQArray *arr = SQArray::Create(_ss(vm), 0); for (;;) { ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ']'); if (res == CompileReturnCode_Success) { arr->Append(val); val.Null(); } else if (res != CompileReturnCode_NoValue) { arr->Release(); return res; } if (m_prevToken == ',') continue; if (m_prevToken == ']') break; arr->Release(); return CompileReturnCode_Unsupported; } val = arr; prevtoken = Token_Value; break; } if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; SQObjectPtr tmp, self = val; ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ']'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ']') return CompileReturnCode_Unsupported; if (!dbg->GetObj_Var(self, val, obj, tmp)) { SQTable *del = GetDefaultDelegate(vm, sq_type(self)); if (!del || !del->Get(val, val)) { // allow non-existent key if (Next() != Token_NewSlot) return CompileReturnCode_DoesNotExist; switch (sq_type(self)) { case OT_TABLE: obj.type = objref_t::TABLE; break; case OT_CLASS: obj.type = objref_t::CLASS; break; default: if (is_delegable(self) && _delegable(self)->_delegate) { obj.type = objref_t::DELEGABLE_META; break; } return CompileReturnCode_DoesNotExist; } obj.src = self; obj.key = val; prevtoken = Token_PendingKey; } else { callenv = self; prevtoken = Token_Value; } } else { ConvertPtr(obj); m_lastRef = obj; val = tmp; prevtoken = Token_Ref; } break; } case '(': { if (ExpectsValue(prevtoken)) { ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ')'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ')') return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } m_lastToken.type = 0; callparams_t cp{}; switch (sq_type(val)) { case OT_CLOSURE: case OT_NATIVECLOSURE: case OT_CLASS: { cp.func = val; break; } default: if (is_delegable(val)) { if (!_delegable(val)->_delegate || !_delegable(val)->GetMetaMethod(vm, MT_CALL, cp.func)) return CompileReturnCode_DoesNotExist; // in MT_CALL the object itself is the env, // and the env is passed as an extra parameter cp.params[cp.paramCount++] = val; break; } return CompileReturnCode_Unsupported; } // Check against 0 to make 'null' env valid if (sq_type(callenv) == 0) { const SQObjectPtr &env = frame != INVALID_FRAME ? vm->_stack._vals[GetStackBase(vm, frame)] : vm->_roottable; cp.params[cp.paramCount++] = env; } else { cp.params[cp.paramCount++] = callenv; callenv.Null(); } // call parameters for (;;) { ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ')'); if (res == CompileReturnCode_Success) { if (cp.paramCount >= (int)_ArraySize(cp.params)) return CompileReturnCode_CallBufferFull; cp.params[cp.paramCount++] = val; val.Null(); } else if (res != CompileReturnCode_NoValue) { // For completions to make more sense if (res == CompileReturnCode_DoesNotExist && m_lastToken.type != Token_Identifier) val.Null(); return res; } if (m_prevToken == ',') continue; if (m_prevToken == ')') break; m_lastToken.type = 0; return CompileReturnCode_Unsupported; } if (!dbg->RunClosure(cp.func, cp.params, cp.paramCount, val)) return CompileReturnCode_CallError; prevtoken = Token_Value; break; } case Token_Delete: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; deleteop = 1; prevtoken = Token_Delete; break; } #if SQUIRREL_VERSION_NUMBER < 300 case Token_Parent: { if (!IsValue(prevtoken)) { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; // implicit this. val = frame != INVALID_FRAME ? vm->_stack._vals[GetStackBase(vm, frame)] : vm->_roottable; } switch (sq_type(val)) { case OT_TABLE: { if (_table(val)->_delegate) { val = _table(val)->_delegate; } else { val.Null(); } break; } case OT_CLASS: { if (_class(val)->_base) { val = _class(val)->_base; } else { val.Null(); } break; } default: return CompileReturnCode_DoesNotExist; } prevtoken = Token_Value; break; } case Token_Vargv: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; if (frame == INVALID_FRAME) return CompileReturnCode_DoesNotExist; token_t next = Lex(); if (next.type != '[') return CompileReturnCode_DoesNotExist; ECompileReturnCode res = Evaluate(dbg, vm, frame, val, ']'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ']') return CompileReturnCode_Unsupported; if (sq_type(val) != OT_INTEGER) return CompileReturnCode_DoesNotExist; const SQVM::CallInfo *ci = vm->_callsstack + frame; if (_integer(val) < 0 || _integer(val) >= ci->_vargs.size) return CompileReturnCode_DoesNotExist; val = vm->_vargsstack[ci->_vargs.base + _integer(val)]; prevtoken = Token_Value; break; } case Token_Vargc: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; if (frame == INVALID_FRAME) return CompileReturnCode_DoesNotExist; const SQVM::CallInfo *ci = vm->_callsstack + frame; val = (SQInteger)ci->_vargs.size; prevtoken = Token_Value; break; } #endif case Token_File: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; const SQVM::CallInfo *ci = vm->_callsstack + frame; if (frame != INVALID_FRAME && sq_type(ci->_closure) == OT_CLOSURE) { val = _fp(_closure(ci->_closure)->_function)->_sourcename; } else { val = CreateSQString(vm, _SC("")); } prevtoken = Token_Value; break; } case Token_Line: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; const SQVM::CallInfo *ci = vm->_callsstack + frame; if (frame != INVALID_FRAME && sq_type(ci->_closure) == OT_CLOSURE) { Assert(ci->_ip >= _fp(_closure(ci->_closure)->_function)->_instructions && ci->_ip < _fp(_closure(ci->_closure)->_function)->_instructions + _fp(_closure(ci->_closure)->_function)->_ninstructions); val = (SQInteger)_fp(_closure(ci->_closure)->_function)->GetLine(ci->_ip); } else { val = (SQInteger)0; } prevtoken = Token_Value; break; } case Token_Assign: case Token_AssignAdd: case Token_AssignSub: case Token_AssignMul: case Token_AssignDiv: case Token_AssignMod: case Token_AssignAnd: case Token_AssignXor: case Token_AssignOr: case Token_AssignLS: case Token_AssignRS: case Token_AssignURS: case Token_NewSlot: { if (prevtoken != Token_Ref && !(token.type == Token_NewSlot && prevtoken == Token_PendingKey)) return CompileReturnCode_Unsupported; if (unaryidx != -1 || opbufidx != -1) return CompileReturnCode_Unsupported; SQObjectPtr target = val; ECompileReturnCode res = Evaluate(dbg, vm, frame, val, closer); if (res != CompileReturnCode_Success) return res; switch (token.type) { case Token_AssignAdd: case Token_AssignSub: case Token_AssignMul: case Token_AssignDiv: case Token_AssignMod: case Token_AssignAnd: case Token_AssignXor: case Token_AssignOr: case Token_AssignLS: case Token_AssignRS: case Token_AssignURS: { if (!BinaryOp(dbg, token.type & ~_assign, target, val, val)) return CompileReturnCode_OpFailure; } case Token_Assign: { if (!dbg->Set(obj, val)) return CompileReturnCode_OpFailure; return CompileReturnCode_Success; } case Token_NewSlot: { if (!dbg->NewSlot(obj, val)) return CompileReturnCode_OpFailure; return CompileReturnCode_Success; } default: UNREACHABLE(); } } default: { // identifier boundary if (token.type > 0) { m_prevToken = token.type; if (ExpectsValue(prevtoken)) return CompileReturnCode_NoValue; if (prevtoken == Token_PendingKey) return CompileReturnCode_Unsupported; if (prevtoken == Token_Ref) { if (deleteop) { if (!dbg->Delete(obj, val)) return CompileReturnCode_OpFailure; deleteop = 0; prevtoken = Token_Value; } else if (incrop) { if (!dbg->Increment(obj, (incrop & 0x1) ? 1 : -1)) return CompileReturnCode_OpFailure; if (incrop & 0x8) // prefix dbg->Get(obj, val); incrop = 0; prevtoken = Token_Value; } } else if (deleteop || incrop) { return CompileReturnCode_OpFailure; } while (unaryidx != -1) { if (!UnaryOp(dbg, unarybuf[unaryidx] | _op, val)) return CompileReturnCode_OpFailure; unaryidx--; } while (opbufidx != -1) { if (!BinaryOp(dbg, opbuf[opbufidx] | _op, valbuf[opbufidx], val, val)) return CompileReturnCode_OpFailure; opbufidx--; } switch (token.type) { case ']': { if (closer == ']') return CompileReturnCode_Success; break; } case ')': { if (closer == ')') return CompileReturnCode_Success; break; } case '}': { if (closer == '}') return CompileReturnCode_Success; break; } case ',': { if (closer == ')' || closer == ']' || closer == '}') return CompileReturnCode_Success; break; } case ':': { if (closer == ':') return CompileReturnCode_Success; break; } case Token_End: { if (closer == Token_End) return CompileReturnCode_Success; break; } } return CompileReturnCode_Unsupported; } else if (token.type == Err_InvalidToken) { return CompileReturnCode_Unsupported; } else { m_prevToken = token.type; return CompileReturnCode_SyntaxError; } } } } } sqstring_t LastError() { switch (m_prevToken) { case Err_InvalidFloat: return _SC("invalid float literal"); case Err_InvalidBinary: return _SC("invalid binary literal"); case Err_InvalidHexadecimal: return _SC("invalid hexadecimal literal"); case Err_InvalidOctal: return _SC("invalid octal literal"); case Err_InvalidDecimal: return _SC("invalid decimal literal"); case Err_InvalidU32Escape: return _SC("invalid UTF32 escape"); case Err_InvalidU16Escape: return _SC("invalid UTF16 escape"); case Err_InvalidXEscape: return _SC("invalid hexadecimal escape"); case Err_InvalidOctalEscape: return _SC("invalid octal escape"); case Err_InvalidEscape: return _SC("invalid escape char"); case Err_UnfinishedChar: return _SC("unfinished char literal"); case Err_UnfinishedString: return _SC("unfinished string literal"); case Err_UnfinishedComment: return _SC("unfinished block comment"); default: return _SC(""); } } private: ECompileReturnCode LexAll(int closer, bool jump = false) { token_t token; int prevtoken = 0; for (;; m_prevToken = token.type) { token = Lex(); switch (token.type) { case Token_Identifier: { if (!ExpectsIdentifier(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Ref; break; } case Token_String: case Token_Integer: case Token_Float: case Token_Null: case Token_False: case Token_True: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } case Token_This: { if (!ExpectsIdentifier(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } case Token_BwNot: case Token_Not: case Token_Typeof: case Token_Clone: { unary: // identifier boundary if (!ExpectsValue(prevtoken)) { token = Lex(); if (token.type == Token_In) { token.type = Token_NotIn; goto binary; } return CompileReturnCode_Unsupported; } prevtoken = Token_Operator; break; } case Token_Sub: #ifdef SUPPORTS_DEREF_OP case Token_Mul: case Token_BwAnd: #endif { if (ExpectsValue(prevtoken)) goto unary; } case Token_In: case Token_InstanceOf: case Token_Add: #ifndef SUPPORTS_DEREF_OP case Token_Mul: #endif case Token_Div: case Token_Mod: case Token_LShift: case Token_RShift: case Token_URShift: #ifndef SUPPORTS_DEREF_OP case Token_BwAnd: #endif case Token_BwXor: case Token_BwOr: case Token_LogicalAnd: case Token_LogicalOr: case Token_Greater: case Token_GreaterEq: case Token_Less: case Token_LessEq: case Token_Cmp3W: case Token_Eq: case Token_NotEq: { binary: // identifier boundary if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Operator; break; } case Token_Increment: case Token_Decrement: { if (!(prevtoken == Token_Ref || ExpectsIdentifier(prevtoken))) return CompileReturnCode_Unsupported; break; } case '?': { // identifier boundary if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; // HACKHACK: Exit if the previous statement is to be evaluated // Breaks easily, but makes valid syntax work correctly if (jump) { m_prevToken = token.type; m_cur--; return CompileReturnCode_Success; } ECompileReturnCode res = LexAll(':'); if (res != CompileReturnCode_Success) return res; res = LexAll(closer); if (res != CompileReturnCode_Success) return res; if (closer != Token_End) m_cur--; break; } case Token_DoubleColon: { token = Lex(); if (token.type != Token_Identifier) return CompileReturnCode_Unsupported; prevtoken = Token_Ref; break; } case '.': { if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; token_t next = Lex(); if (next.type == INTERNAL_TAG_PREFIX) { next = Lex(); if (next.type == Token_Identifier) prevtoken = Token_Value; m_lastToken.type = '.'; return CompileReturnCode_Unsupported; } else if (next.type != Token_Identifier) { m_lastToken.type = '.'; return CompileReturnCode_Unsupported; } m_lastToken = next; prevtoken = Token_Ref; break; } case '{': { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; // new table for (;;) { token_t next = Lex(); if (next.type == '[') { ECompileReturnCode res = LexAll(']'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ']') return CompileReturnCode_Unsupported; } else if (next.type == Token_Identifier) { } else if (next.type == '}') { break; } else { return CompileReturnCode_Unsupported; } next = Lex(); if (next.type != Token_Assign) return CompileReturnCode_Unsupported; ECompileReturnCode res = LexAll('}'); if (res != CompileReturnCode_Success) return res; if (m_prevToken == ',') continue; if (m_prevToken == '}') break; return CompileReturnCode_Unsupported; } prevtoken = Token_Value; break; } case '[': { // new array if (ExpectsValue(prevtoken)) { for (;;) { ECompileReturnCode res = LexAll(']'); if (res == CompileReturnCode_Success) { } else if (res != CompileReturnCode_NoValue) { return res; } if (m_prevToken == ',') continue; if (m_prevToken == ']') break; return CompileReturnCode_Unsupported; } prevtoken = Token_Value; break; } if (!IsValue(prevtoken)) return CompileReturnCode_Unsupported; ECompileReturnCode res = LexAll(']'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ']') return CompileReturnCode_Unsupported; prevtoken = Token_Ref; break; } case '(': { if (ExpectsValue(prevtoken)) { ECompileReturnCode res = LexAll(')'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ')') return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } // call parameters for (;;) { ECompileReturnCode res = LexAll(')'); if (res == CompileReturnCode_Success) { } else if (res != CompileReturnCode_NoValue) { return res; } if (m_prevToken == ',') continue; if (m_prevToken == ')') break; return CompileReturnCode_Unsupported; } prevtoken = Token_Value; break; } case Token_Delete: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Delete; break; } #if SQUIRREL_VERSION_NUMBER < 300 case Token_Parent: { if (!IsValue(prevtoken) && !ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } case Token_Vargv: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; token_t next = Lex(); if (next.type != '[') return CompileReturnCode_DoesNotExist; ECompileReturnCode res = LexAll(']'); if (res != CompileReturnCode_Success) return res; if (m_prevToken != ']') return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } case Token_Vargc: #endif case Token_File: case Token_Line: { if (!ExpectsValue(prevtoken)) return CompileReturnCode_Unsupported; prevtoken = Token_Value; break; } case Token_Assign: case Token_AssignAdd: case Token_AssignSub: case Token_AssignMul: case Token_AssignDiv: case Token_AssignMod: case Token_AssignAnd: case Token_AssignXor: case Token_AssignOr: case Token_AssignLS: case Token_AssignRS: case Token_AssignURS: case Token_NewSlot: { if (prevtoken != Token_Ref) return CompileReturnCode_Unsupported; ECompileReturnCode res = LexAll(closer); return res; } default: { // identifier boundary if (token.type > 0) { m_prevToken = token.type; if (ExpectsValue(prevtoken)) return CompileReturnCode_NoValue; switch (token.type) { case ']': { if (closer == ']') return CompileReturnCode_Success; break; } case ')': { if (closer == ')') return CompileReturnCode_Success; break; } case '}': { if (closer == '}') return CompileReturnCode_Success; break; } case ',': { if (closer == ')' || closer == ']' || closer == '}') return CompileReturnCode_Success; break; } case ':': { if (closer == ':') return CompileReturnCode_Success; break; } case Token_End: { if (closer == Token_End) return CompileReturnCode_Success; break; } } return CompileReturnCode_Unsupported; } else if (token.type == Err_InvalidToken) { return CompileReturnCode_Unsupported; } else { m_prevToken = token.type; return CompileReturnCode_SyntaxError; } } } } } private: static bool IsValue(int token) { switch (token) { case Token_Ref: case Token_Value: case Token_Integer: case Token_Float: return true; default: return false; } } static bool ExpectsValue(int token) { switch (token) { case 0: case '(': case ',': case Token_Operator: return true; default: return false; } } static bool ExpectsIdentifier(int token) { switch (token) { case 0: case '(': case ',': case Token_Operator: case Token_Delete: return true; default: return false; } } static bool BinaryOp(SQDebugServer *dbg, int op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out) { Assert((op & _op) == _op); switch (op) { case Token_Add: return dbg->ArithOp('+', lhs, rhs, out); case Token_Sub: return dbg->ArithOp('-', lhs, rhs, out); case Token_Mul: return dbg->ArithOp('*', lhs, rhs, out); case Token_Div: return dbg->ArithOp('/', lhs, rhs, out); case Token_Mod: return dbg->ArithOp('%', lhs, rhs, out); case Token_In: case Token_NotIn: { switch (sq_type(rhs)) { case OT_TABLE: SetBool(out, _table(rhs)->Get(lhs, out)); break; case OT_CLASS: SetBool(out, _class(rhs)->Get(lhs, out)); break; case OT_INSTANCE: Assert(_instance(rhs)->_class); SetBool(out, _instance(rhs)->_class->Get(lhs, out)); break; case OT_ARRAY: SetBool(out, sq_type(lhs) == OT_INTEGER && _integer(lhs) >= 0 && _integer(lhs) < _array(rhs)->Size()); break; case OT_STRING: SetBool(out, sq_type(lhs) == OT_INTEGER && _integer(lhs) >= 0 && _integer(lhs) < _string(rhs)->_len); break; default: SetBool(out, false); } if (op == Token_NotIn) _integer(out) = !_integer(out); return true; } case Token_InstanceOf: { if (sq_type(rhs) == OT_CLASS) { Assert(sq_type(lhs) != OT_INSTANCE || _instance(lhs)->_class); SetBool(out, sq_type(lhs) == OT_INSTANCE && _instance(lhs)->InstanceOf(_class(rhs))); return true; } return false; } case Token_LShift: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(_integer(lhs) << _integer(rhs)); return true; } return false; } case Token_RShift: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(_integer(lhs) >> _integer(rhs)); return true; } return false; } case Token_URShift: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(*(SQUnsignedInteger *)&_integer(lhs) >> _integer(rhs)); return true; } return false; } case Token_BwAnd: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(_integer(lhs) & _integer(rhs)); return true; } return false; } case Token_BwXor: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(_integer(lhs) ^ _integer(rhs)); return true; } return false; } case Token_BwOr: { if ((sq_type(lhs) | sq_type(rhs)) == OT_INTEGER) { out = (SQInteger)(_integer(lhs) | _integer(rhs)); return true; } return false; } case Token_LogicalAnd: { out = IsFalse(lhs) ? lhs : rhs; return true; } case Token_LogicalOr: { out = !IsFalse(lhs) ? lhs : rhs; return true; } case Token_Greater: { int res = dbg->CompareObj(lhs, rhs); if (res != ECMP_NONE) { SetBool(out, (res & ECMP_G) != 0); return true; } return false; } case Token_GreaterEq: { int res = dbg->CompareObj(lhs, rhs); if (res != ECMP_NONE) { SetBool(out, (res & ECMP_GE) != 0); return true; } return false; } case Token_Less: { int res = dbg->CompareObj(lhs, rhs); if (res != ECMP_NONE) { SetBool(out, (res & ECMP_L) != 0); return true; } return false; } case Token_LessEq: { int res = dbg->CompareObj(lhs, rhs); if (res != ECMP_NONE) { SetBool(out, (res & ECMP_LE) != 0); return true; } return false; } case Token_Cmp3W: { int res = dbg->CompareObj(lhs, rhs); if (res != ECMP_NONE) { switch (res) { case ECMP_EQ: res = 0; break; case ECMP_G: res = 1; break; case ECMP_L: res = -1; break; default: UNREACHABLE(); } out = (SQInteger)res; return true; } return false; } case Token_Eq: { bool res; #if SQUIRREL_VERSION_NUMBER >= 300 dbg->m_pRootVM->IsEqual(lhs, rhs, res); #else dbg->m_pRootVM->IsEqual((SQObjectPtr &)lhs, (SQObjectPtr &)rhs, res); #endif SetBool(out, res); return true; } case Token_NotEq: { bool res; #if SQUIRREL_VERSION_NUMBER >= 300 dbg->m_pRootVM->IsEqual(lhs, rhs, res); #else dbg->m_pRootVM->IsEqual((SQObjectPtr &)lhs, (SQObjectPtr &)rhs, res); #endif SetBool(out, !res); return true; } default: UNREACHABLE(); } } static bool UnaryOp(SQDebugServer *dbg, int op, SQObjectPtr &val) { Assert((op & _op) == _op); switch (op) { case Token_Sub: { if (sq_type(val) == OT_INTEGER) { _integer(val) = -_integer(val); return true; } else if (sq_type(val) == OT_FLOAT) { _float(val) = -_float(val); return true; } else if (is_delegable(val) && _delegable(val)->_delegate) { SQObjectPtr mm; if (_delegable(val)->GetMetaMethod(dbg->m_pRootVM, MT_UNM, mm)) { return dbg->RunClosure(mm, &val, val); } } return false; } case Token_BwNot: { if (sq_type(val) == OT_INTEGER) { _integer(val) = ~_integer(val); return true; } return false; } case Token_Not: { SetBool(val, IsFalse(val)); return true; } case Token_Typeof: { if (is_delegable(val) && _delegable(val)->_delegate) { SQObjectPtr mm; if (_delegable(val)->GetMetaMethod(dbg->m_pRootVM, MT_TYPEOF, mm)) { return dbg->RunClosure(mm, &val, val); } } extern const SQChar *GetTypeName(const SQObjectPtr &); const SQChar *tname = GetTypeName(val); if (tname) { val = SQString::Create(_ss(dbg->m_pRootVM), tname, scstrlen(tname)); return true; } return false; } case Token_Clone: { switch (sq_type(val)) { case OT_TABLE: case OT_INSTANCE: case OT_ARRAY: return dbg->m_pRootVM->Clone(val, val); default: return false; } } #ifdef SUPPORTS_DEREF_OP case Token_Mul: { if (sq_type(val) == OT_INTEGER) { // Find address in gc chain for (SQCollectable *t = _ss(dbg->m_pRootVM)->_gc_chain; t; t = t->_next) { if ((uintptr_t)t == (uintptr_t)_integer(val)) { #if SQUIRREL_VERSION_NUMBER >= 300 if (t->GetType() == OT_OUTER) break; SQObject o; o._type = t->GetType(); #else #define _check(_c, _t) \ if (dynamic_cast<_c *>(t)) \ { \ o._type = _t; \ } SQObject o; _check(SQTable, OT_TABLE) else _check(SQArray, OT_ARRAY) else _check(SQInstance, OT_INSTANCE) else _check(SQClass, OT_CLASS) else _check(SQClosure, OT_CLOSURE) else _check(SQNativeClosure, OT_NATIVECLOSURE) else _check(SQGenerator, OT_GENERATOR) else _check(SQVM, OT_THREAD) else _check(SQUserData, OT_USERDATA) else break; #undef _check #endif o._unVal.pRefCounted = t; val = o; return true; } } val.Null(); return true; } return false; } case Token_BwAnd: { if (ISREFCOUNTED(sq_type(val)) && sq_type(val) != OT_STRING) { val = (SQInteger)(uintptr_t)_refcounted(val); return true; } else if (sq_type(val) == OT_NULL) { val = (SQInteger)0; return true; } return false; } #endif default: UNREACHABLE(); } } private: int Next() { while (m_cur < m_end) { switch (*m_cur) { case ' ': case '\n': case '\r': case '\t': { m_cur++; continue; } case '\"': { return *m_cur; } case '\'': { return *m_cur; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { return *m_cur; } case '.': case ',': case '[': case ']': case '(': case ')': case '{': case '}': case INTERNAL_TAG_PREFIX: case '?': { return *m_cur; } case '+': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignAdd; } else if (m_cur + 1 < m_end && m_cur[1] == '+') { return Token_Increment; } else { return Token_Add; } } case '-': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignSub; } else if (m_cur + 1 < m_end && m_cur[1] == '-') { return Token_Decrement; } else { return Token_Sub; } } case '*': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignMul; } else { return Token_Mul; } } case '/': { if (m_cur + 1 < m_end && m_cur[1] == '*') { m_cur += 2; if (LexBlockComment()) continue; return Err_UnfinishedComment; } else if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignDiv; } else { return Token_Div; } } case '%': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignMod; } else { return Token_Mod; } } case '~': { return Token_BwNot; } case '^': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignXor; } else { return Token_BwXor; } } case '=': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_Eq; } else { return Token_Assign; } } case '!': { if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_NotEq; } else { return Token_Not; } } case '<': { if (m_cur + 1 < m_end && m_cur[1] == '<') { if (m_cur + 2 < m_end && m_cur[2] == '=') { return Token_AssignLS; } else { return Token_LShift; } } else if (m_cur + 1 < m_end && m_cur[1] == '=') { if (m_cur + 2 < m_end && m_cur[2] == '>') { return Token_Cmp3W; } else { return Token_LessEq; } } else if (m_cur + 1 < m_end && m_cur[1] == '-') { return Token_NewSlot; } else { return Token_Less; } } case '>': { if (m_cur + 1 < m_end && m_cur[1] == '>') { if (m_cur + 2 < m_end && m_cur[2] == '>') { if (m_cur + 3 < m_end && m_cur[3] == '=') { return Token_AssignURS; } else { return Token_URShift; } } else if (m_cur + 2 < m_end && m_cur[2] == '=') { return Token_AssignRS; } else { return Token_RShift; } } else if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_GreaterEq; } else { return Token_Greater; } } case '&': { if (m_cur + 1 < m_end && m_cur[1] == '&') { return Token_LogicalAnd; } else if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignAnd; } else { return Token_BwAnd; } } case '|': { if (m_cur + 1 < m_end && m_cur[1] == '|') { return Token_LogicalOr; } else if (m_cur + 1 < m_end && m_cur[1] == '=') { return Token_AssignOr; } else { return Token_BwOr; } } case ':': { if (m_cur + 1 < m_end && m_cur[1] == ':') { return Token_DoubleColon; } else { return ':'; } } default: { if (_isalpha(*m_cur) || *m_cur == '_') { return *m_cur; } else { return Err_InvalidToken; } } } } return Token_End; } token_t Lex() { token_t token; while (m_cur < m_end) { switch (*m_cur) { case ' ': case '\n': case '\r': case '\t': { m_cur++; continue; } case '\"': { ParseString(token); return token; } case '\'': { ParseChar(token); return token; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { ParseNumber(token); return token; } case '.': case ',': case '[': case ']': case '(': case ')': case '{': case '}': case INTERNAL_TAG_PREFIX: case '?': { token.type = *m_cur++; return token; } case '+': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignAdd; } else if (m_cur < m_end && *m_cur == '+') { m_cur++; token.type = Token_Increment; } else { token.type = Token_Add; } return token; } case '-': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignSub; } else if (m_cur < m_end && *m_cur == '-') { m_cur++; token.type = Token_Decrement; } else { token.type = Token_Sub; } return token; } case '*': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignMul; } else { token.type = Token_Mul; } return token; } case '/': { m_cur++; if (m_cur < m_end && *m_cur == '*') { m_cur++; if (LexBlockComment()) continue; token.type = Err_UnfinishedComment; } else if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignDiv; } else { token.type = Token_Div; } return token; } case '%': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignMod; } else { token.type = Token_Mod; } return token; } case '~': { m_cur++; token.type = Token_BwNot; return token; } case '^': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignXor; } else { token.type = Token_BwXor; } return token; } case '=': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_Eq; } else { token.type = Token_Assign; } return token; } case '!': { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_NotEq; } else { token.type = Token_Not; } return token; } case '<': { m_cur++; if (m_cur < m_end && *m_cur == '<') { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignLS; } else { token.type = Token_LShift; } } else if (m_cur < m_end && *m_cur == '=') { m_cur++; if (m_cur < m_end && *m_cur == '>') { m_cur++; token.type = Token_Cmp3W; } else { token.type = Token_LessEq; } } else if (m_cur < m_end && *m_cur == '-') { m_cur++; token.type = Token_NewSlot; } else { token.type = Token_Less; } return token; } case '>': { m_cur++; if (m_cur < m_end && *m_cur == '>') { m_cur++; if (m_cur < m_end && *m_cur == '>') { m_cur++; if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignURS; } else { token.type = Token_URShift; } } else if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignRS; } else { token.type = Token_RShift; } } else if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_GreaterEq; } else { token.type = Token_Greater; } return token; } case '&': { m_cur++; if (m_cur < m_end && *m_cur == '&') { m_cur++; token.type = Token_LogicalAnd; } else if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignAnd; } else { token.type = Token_BwAnd; } return token; } case '|': { m_cur++; if (m_cur < m_end && *m_cur == '|') { m_cur++; token.type = Token_LogicalOr; } else if (m_cur < m_end && *m_cur == '=') { m_cur++; token.type = Token_AssignOr; } else { token.type = Token_BwOr; } return token; } case ':': { m_cur++; if (m_cur < m_end && *m_cur == ':') { m_cur++; token.type = Token_DoubleColon; } else { token.type = ':'; } return token; } default: { if (_isalpha(*m_cur) || *m_cur == '_') { char *pStart = m_cur++; while (m_cur < m_end && (_isalnum(*m_cur) || *m_cur == '_')) m_cur++; token.type = Token_Identifier; token._string.Assign(pStart, m_cur - pStart); if (token._string.len >= 2 && token._string.len <= 10) { if (token._string.IsEqualTo("in")) { token.type = Token_In; } else if (token._string.IsEqualTo("instanceof")) { token.type = Token_InstanceOf; } else if (token._string.IsEqualTo("this")) { token.type = Token_This; } else if (token._string.IsEqualTo("null")) { token.type = Token_Null; } else if (token._string.IsEqualTo("true")) { token.type = Token_True; token._integer = 1; } else if (token._string.IsEqualTo("false")) { token.type = Token_False; token._integer = 0; } else if (token._string.IsEqualTo("typeof")) { token.type = Token_Typeof; } else if (token._string.IsEqualTo("clone")) { token.type = Token_Clone; } else if (token._string.IsEqualTo("delete")) { token.type = Token_Delete; } #if SQUIRREL_VERSION_NUMBER < 300 else if (token._string.IsEqualTo("parent")) { token.type = Token_Parent; } else if (token._string.IsEqualTo("vargv")) { token.type = Token_Vargv; } else if (token._string.IsEqualTo("vargc")) { token.type = Token_Vargc; } #endif else if (token._string.IsEqualTo("__FILE__")) { token.type = Token_File; } else if (token._string.IsEqualTo("__LINE__")) { token.type = Token_Line; } else if (token._string.IsEqualTo("if") || token._string.IsEqualTo("do") || token._string.IsEqualTo("for") || token._string.IsEqualTo("while") || token._string.IsEqualTo("local") || token._string.IsEqualTo("const") || token._string.IsEqualTo("throw") || token._string.IsEqualTo("static") || token._string.IsEqualTo("return") || token._string.IsEqualTo("resume") || token._string.IsEqualTo("foreach") || token._string.IsEqualTo("function")) { token.type = Err_InvalidToken; } } } else { token.type = Err_InvalidToken; } return token; } } } token.type = Token_End; return token; } bool LexBlockComment() { for (;;) { if (m_cur + 1 >= m_end) return false; if (*m_cur++ == '*' && *m_cur++ == '/') return true; } } void ParseString(token_t &token) { char *pStart = ++m_cur; for (;;) { if (m_cur >= m_end) { token.type = Err_UnfinishedString; return; } if (*m_cur == '\"') { token.type = Token_String; token._string.Assign(pStart, m_cur - pStart); m_cur++; return; } if (*m_cur != '\\') { m_cur++; continue; } if (m_cur + 1 >= m_end) { token.type = Err_UnfinishedString; return; } #define _shift(bytesWritten, bytesRead) \ Assert((bytesWritten) < (bytesRead)); \ memmove(m_cur + (bytesWritten), m_cur + (bytesRead), m_end - (m_cur + (bytesRead))); \ m_cur += (bytesWritten); \ m_end -= (bytesRead) - (bytesWritten); \ m_expr.len -= (bytesRead) - (bytesWritten); switch (m_cur[1]) { case '\\': shift_one: _shift(1, 2); break; case '\"': *m_cur = '\"'; goto shift_one; case '\'': *m_cur = '\''; goto shift_one; case 'a': *m_cur = '\a'; goto shift_one; case 'b': *m_cur = '\b'; goto shift_one; case 'f': *m_cur = '\f'; goto shift_one; case 'n': *m_cur = '\n'; goto shift_one; case 'r': *m_cur = '\r'; goto shift_one; case 't': *m_cur = '\t'; goto shift_one; case 'v': *m_cur = '\v'; goto shift_one; case 'x': #ifndef SQUNICODE { if (m_cur + 2 >= m_end) { token.type = Err_InvalidXEscape; return; } unsigned int len = 0; for (char *x = min(m_cur + 2, m_end), *end = min(x + 2, m_end); x < end && _isxdigit(*x); x++) { len++; } if (len == 0) { token.type = Err_InvalidXEscape; return; } unsigned int val; if (!atox({m_cur + 2, len}, &val)) { token.type = Err_InvalidXEscape; return; } m_cur[0] = (char)val; _shift(1, len + 2); break; } #endif case 'u': { if (m_cur + 2 >= m_end) { token.type = Err_InvalidU16Escape; return; } unsigned int len = 0; for (char *x = min(m_cur + 2, m_end), *end = min(x + 4, m_end); x < end && _isxdigit(*x); x++) { len++; } if (len == 0) { token.type = Err_InvalidU16Escape; return; } unsigned int val; if (!atox({m_cur + 2, len}, &val)) { token.type = Err_InvalidU16Escape; return; } if (val <= 0x7F) { m_cur[0] = (char)val; _shift(1, len + 2); break; } else if (val <= 0x7FF) { UTF8_2_FROM_UTF32((unsigned char *)m_cur, val); _shift(2, len + 2); break; } else if (UTF_SURROGATE(val)) { Assert(len == 4); if (UTF_SURROGATE_LEAD(val)) { if (m_cur + 11 < m_end && m_cur[6] == '\\' && m_cur[7] == m_cur[1] && _isxdigit(m_cur[8]) && _isxdigit(m_cur[9]) && _isxdigit(m_cur[10]) && _isxdigit(m_cur[11])) { unsigned int low; Verify(atox({m_cur + 8, 4}, &low)); if (UTF_SURROGATE_TRAIL(low)) { val = UTF32_FROM_UTF16_SURROGATE(val, low); UTF8_4_FROM_UTF32((unsigned char *)m_cur, val); _shift(4, len + 2 + 6); break; } } } } UTF8_3_FROM_UTF32((unsigned char *)m_cur, val); _shift(3, len + 2); break; } case 'U': { if (m_cur + 2 >= m_end) { token.type = Err_InvalidU32Escape; return; } unsigned int len = 0; for (char *x = min(m_cur + 2, m_end), *end = min(x + 8, m_end); x < end && _isxdigit(*x); x++) { len++; } if (len == 0) { token.type = Err_InvalidU32Escape; return; } unsigned int val; if (!atox({m_cur + 2, len}, &val)) { token.type = Err_InvalidU32Escape; return; } if (val <= 0x7F) { m_cur[0] = (char)val; _shift(1, len + 2); break; } else if (val <= 0x7FF) { UTF8_2_FROM_UTF32((unsigned char *)m_cur, val); _shift(2, len + 2); break; } else if (val <= 0xFFFF) { if (UTF_SURROGATE(val)) { // next could be \u or \U with differing lengths, // just ignore surrogates token.type = Err_InvalidToken; return; } UTF8_3_FROM_UTF32((unsigned char *)m_cur, val); _shift(3, len + 2); break; } else { UTF8_4_FROM_UTF32((unsigned char *)m_cur, val); _shift(4, len + 2); break; } } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { unsigned int len = 0; for (char *x = m_cur + 1, *end = min(x + 3, m_end); x < end && IN_RANGE_CHAR(*x, '0', '7'); x++) { len++; } Assert(len >= 1); unsigned int val; if (!atoo({m_cur + 1, len}, &val) || val > 255) { token.type = Err_InvalidOctalEscape; return; } m_cur[0] = (char)val; _shift(1, len + 1); break; } default: token.type = Err_InvalidEscape; return; } #undef _shift } } void ParseChar(token_t &token) { m_cur++; if (m_cur + 1 >= m_end) { token.type = Err_UnfinishedChar; return; } unsigned char c = *m_cur; if (c == '\\') { m_cur++; if (m_cur + 1 >= m_end) { token.type = Err_UnfinishedChar; return; } switch (*m_cur) { case '\\': break; case '\"': c = '\"'; break; case '\'': c = '\''; break; case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'x': { char *pStart = ++m_cur; while (m_cur < m_end && _isxdigit(*m_cur)) m_cur++; if (m_cur == pStart || (int)(m_cur - pStart) > (int)sizeof(SQChar) * 2 || m_cur >= m_end || *m_cur != '\'') { token.type = Err_InvalidXEscape; return; } unsigned int val; if (!atox({pStart, (unsigned int)(m_cur - pStart)}, &val)) { token.type = Err_InvalidXEscape; return; } token.type = Token_Integer; token._integer = val; m_cur++; return; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { char *pStart = m_cur++; while (m_cur < m_end && IN_RANGE_CHAR(*m_cur, '0', '7')) m_cur++; if (m_cur == pStart || (int)(m_cur - pStart) > 3 || m_cur >= m_end || *m_cur != '\'') { token.type = Err_InvalidOctalEscape; return; } unsigned int val; if (!atoo({pStart, (unsigned int)(m_cur - pStart)}, &val) || val > 255) { token.type = Err_InvalidOctalEscape; return; } token.type = Token_Integer; token._integer = val; m_cur++; return; } default: token.type = Err_InvalidEscape; return; } } m_cur++; if (*m_cur == '\'') { token.type = Token_Integer; token._integer = c; m_cur++; } else { token.type = Err_UnfinishedChar; } } void ParseNumber(token_t &token) { const char *pStart = m_cur; static const int DECIMAL = 0; static const int OCTAL = 1; static const int FLOAT = 2; char type = DECIMAL; if (*m_cur == '0') { m_cur++; switch (*m_cur) { case 'x': { m_cur++; while (m_cur < m_end) { if (_isxdigit(*m_cur)) { m_cur++; } else if (!_isalnum(*m_cur)) { break; } else { token.type = Err_InvalidHexadecimal; return; } } string_t str; str.Assign(pStart, m_cur - pStart); if (str.len < 3 || str.len > sizeof(SQInteger) * 2 + 2) { token.type = Err_InvalidHexadecimal; return; } Verify(atox(str, &token._integer)); token.type = Token_Integer; return; } case 'b': { m_cur++; while (m_cur < m_end) { if (IN_RANGE_CHAR(*m_cur, '0', '1')) { m_cur++; } else if (!_isalnum(*m_cur)) { break; } else { token.type = Err_InvalidBinary; return; } } string_t str; str.Assign(pStart, m_cur - pStart); if (str.len < 3 || str.len > sizeof(SQInteger) * 8 + 2) { token.type = Err_InvalidBinary; return; } token.type = Token_Integer; token._integer = 0; int inputbitlen = str.len - 2; for (int i = str.len - 1; i >= 2; i--) { switch (str.ptr[i]) { case '1': token._integer |= ((SQUnsignedInteger)1 << ((inputbitlen - 1) - (i - 2))); case '0': continue; default: UNREACHABLE(); } } return; } default: { while (m_cur < m_end) { if (IN_RANGE_CHAR(*m_cur, '0', '7')) { m_cur++; } else if (!_isalnum(*m_cur)) { break; } else { token.type = Err_InvalidOctal; return; } } type = OCTAL; break; } } } else { Assert(IN_RANGE_CHAR(*m_cur, '1', '9')); do { m_cur++; } while (IN_RANGE_CHAR(*m_cur, '0', '9')); type = DECIMAL; } if (*m_cur == '.') { type = FLOAT; m_cur++; while (m_cur < m_end && IN_RANGE_CHAR(*m_cur, '0', '9')) m_cur++; } if (m_cur < m_end && (*m_cur == 'e' || *m_cur == 'E')) { type = FLOAT; m_cur++; if (m_cur >= m_end) { token.type = Err_InvalidFloat; return; } if (*m_cur == '-' || *m_cur == '+') { m_cur++; if (m_cur >= m_end) { token.type = Err_InvalidFloat; return; } } while (m_cur < m_end && IN_RANGE_CHAR(*m_cur, '0', '9')) m_cur++; } string_t str; str.Assign(pStart, m_cur - pStart); switch (type) { case DECIMAL: { if (str.len > FMT_INT_LEN - 1) { token.type = Err_InvalidDecimal; return; } Verify(atoi(str, &token._integer)); token.type = Token_Integer; return; } case OCTAL: { if (str.len > FMT_OCT_LEN) { token.type = Err_InvalidOctal; return; } Verify(atoo(str, &token._integer)); token.type = Token_Integer; return; } case FLOAT: { char c = str.ptr[str.len]; str.ptr[str.len] = 0; token.type = Token_Float; token._float = (SQFloat)strtod(str.ptr, NULL); str.ptr[str.len] = c; return; } default: UNREACHABLE(); } } }; SQDebugServer::ECompileReturnCode SQDebugServer::Evaluate(string_t &expression, HSQUIRRELVM vm, int frame, SQObjectPtr &ret) { CCompiler c(expression); ECompileReturnCode r = c.Evaluate(this, vm, frame, ret); return r; } SQDebugServer::ECompileReturnCode SQDebugServer::Evaluate(string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret, objref_t &obj) { CCompiler c(expression); ECompileReturnCode r = c.Evaluate(this, vm, ci ? ci - vm->_callsstack : INVALID_FRAME, ret); obj = c.m_lastRef; return r; } SQTable *SQDebugServer::GetDefaultDelegate(HSQUIRRELVM vm, SQObjectType type) { SQTable *del; switch (type) { case OT_TABLE: del = _table(_ss(vm)->_table_default_delegate); break; case OT_ARRAY: del = _table(_ss(vm)->_array_default_delegate); break; case OT_STRING: del = _table(_ss(vm)->_string_default_delegate); break; case OT_INTEGER: case OT_FLOAT: case OT_BOOL: del = _table(_ss(vm)->_number_default_delegate); break; case OT_GENERATOR: del = _table(_ss(vm)->_generator_default_delegate); break; case OT_NATIVECLOSURE: case OT_CLOSURE: del = _table(_ss(vm)->_closure_default_delegate); break; case OT_THREAD: del = _table(_ss(vm)->_thread_default_delegate); break; case OT_CLASS: del = _table(_ss(vm)->_class_default_delegate); break; case OT_INSTANCE: del = _table(_ss(vm)->_instance_default_delegate); break; case OT_WEAKREF: del = _table(_ss(vm)->_weakref_default_delegate); break; default: del = NULL; } return del; } bool SQDebugServer::ArithOp(char op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out) { SQObjectType tl = sq_type(lhs); SQObjectType tr = sq_type(rhs); if ((tl | tr) == OT_INTEGER) { switch (op) { case '+': out = _integer(lhs) + _integer(rhs); return true; case '-': out = _integer(lhs) - _integer(rhs); return true; case '*': out = _integer(lhs) * _integer(rhs); return true; case '/': if (_integer(rhs) == 0 || (_integer(rhs) == -1 && _integer(lhs) == INT_MIN)) return false; out = _integer(lhs) / _integer(rhs); return true; case '%': if (_integer(rhs) == 0 || (_integer(rhs) == -1 && _integer(lhs) == INT_MIN)) return false; out = _integer(lhs) % _integer(rhs); return true; default: UNREACHABLE(); } } if ((tl | tr) == OT_FLOAT) { switch (op) { case '+': out = _float(lhs) + _float(rhs); return true; case '-': out = _float(lhs) - _float(rhs); return true; case '*': out = _float(lhs) * _float(rhs); return true; case '/': out = _float(lhs) / _float(rhs); return true; case '%': out = (SQFloat)fmod(_float(lhs), _float(rhs)); return true; default: UNREACHABLE(); } } if ((tl | tr) == (OT_INTEGER | OT_FLOAT)) { SQFloat fl, fr; if (tl == OT_INTEGER) { fl = (SQFloat)_integer(lhs); fr = _float(rhs); } else if (tr == OT_INTEGER) { fl = _float(lhs); fr = (SQFloat)_integer(rhs); } else UNREACHABLE(); switch (op) { case '+': out = fl + fr; return true; case '-': out = fl - fr; return true; case '*': out = fl * fr; return true; case '/': out = fl / fr; return true; case '%': out = (SQFloat)fmod(fl, fr); return true; default: UNREACHABLE(); } } if (((tl | tr) & OT_STRING) == OT_STRING && op == '+') { // NOTE: calls MT_TOSTRING return m_pRootVM->StringCat(lhs, rhs, out); } if (is_delegable(lhs) && _delegable(lhs)->_delegate) { SQMetaMethod emm; switch (op) { case '+': emm = MT_ADD; break; case '-': emm = MT_SUB; break; case '*': emm = MT_MUL; break; case '/': emm = MT_DIV; break; case '%': emm = MT_MODULO; break; default: UNREACHABLE(); } SQObjectPtr mm; if (_delegable(lhs)->GetMetaMethod(m_pRootVM, emm, mm)) { return RunClosure(mm, &lhs, rhs, out); } } // String multiplication if (tl == OT_STRING && tr == OT_INTEGER) { int len = _string(lhs)->_len * _integer(rhs); if (len == 1) { out = lhs; return true; } if (len <= 0) return false; if ((unsigned int)sq_rsl(len) > (unsigned int)INT_MAX) { _OutputDebugStringFmt(_SC("**(sqdbg) Truncated string multiplication (%u)\n"), sq_rsl(_string(lhs)->_len * _integer(rhs))); len = INT_MAX / sizeof(SQChar); } SQChar *tmp = (SQChar *)ScratchPad(sq_rsl(len)); if (!tmp) { out = CreateSQString(_ss(m_pRootVM), _SC(STR_NOMEM)); return _string(out) != NULL; } for (int i = 0; i < len; i += (int)_string(lhs)->_len) { memcpy(tmp + i, _string(lhs)->_val, sq_rsl(_string(lhs)->_len)); } out = SQString::Create(_ss(m_pRootVM), tmp, len); return true; } return false; } #endif void SQDebugServer::ConvertPtr(objref_t &obj) { if (obj.type & ~objref_t::PTR) obj.type = (objref_t::EOBJREF)(obj.type & ~objref_t::PTR); } bool SQDebugServer::GetObj_Var(const SQObjectPtr &var, const SQObjectPtr &key, objref_t &out, SQObjectPtr &value) { Assert(&key != &value); switch (sq_type(var)) { case OT_TABLE: { SQTable *t = _table(var); do { if (t->Get(key, value)) { out.type = objref_t::TABLE; out.src = t; out.key = key; return true; } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { if (_instance(var)->Get(key, value)) { out.type = objref_t::INSTANCE; out.src = var; out.key = key; return true; } break; } case OT_CLASS: { if (_class(var)->_members->Get(key, value)) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::CLASS); if (_isfield(value)) { out.ptr = &_class(var)->_defaultvalues[_member_idx(value)].val; } else { out.ptr = &_class(var)->_methods[_member_idx(value)].val; } out.src = var; out.key = key; value = *out.ptr; return true; } return false; } case OT_ARRAY: { if (sq_type(key) == OT_INTEGER && _integer(key) >= 0 && _integer(key) < _array(var)->Size()) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::ARRAY); out.ptr = &_array(var)->_values[_integer(key)]; out.src = var; out.key = _integer(key); value = *out.ptr; return true; } return false; } case OT_STRING: { if (sq_type(key) == OT_INTEGER && _integer(key) >= 0 && _integer(key) < _string(var)->_len) { out.type = (objref_t::EOBJREF)(objref_t::INT | objref_t::READONLY); // Sign will be extended out.val = (int)_string(var)->_val[_integer(key)]; value = (SQInteger)out.val; return true; } return false; } case OT_USERDATA: break; default: return false; } // metamethods switch (sq_type(var)) { case OT_INSTANCE: case OT_TABLE: case OT_USERDATA: { if (_delegable(var)->_delegate) { SQObjectPtr mm; if (_delegable(var)->GetMetaMethod(m_pRootVM, MT_GET, mm)) { if (RunClosure(mm, &var, key, value)) { out.type = objref_t::DELEGABLE_META; out.src = var; out.key = key; return true; } } } break; } default: UNREACHABLE(); } // user defined if (sq_type(var) == OT_INSTANCE) { const SQObjectPtr *def = GetClassDefCustomMembers(_instance(var)->_class); if (def) { SQObjectPtr custommembers = *def; if (sq_type(custommembers) == OT_CLOSURE) RunClosure(custommembers, &var, custommembers); if (sq_type(custommembers) == OT_ARRAY) { objref_t tmp; SQObjectPtr strName = CreateSQString(m_pRootVM, _SC("name")); SQObjectPtr strGet = CreateSQString(m_pRootVM, _SC("get")); SQObjectPtr name; for (unsigned int i = 0; i < _array(custommembers)->_values.size(); i++) { const SQObjectPtr &memdef = _array(custommembers)->_values[i]; if (GetObj_Var(memdef, strName, tmp, name) && IsEqual(key, name)) { if (GetObj_Var(memdef, strGet, tmp, value) && CallCustomMembersGetFunc(value, &var, key, value)) { out.type = objref_t::CUSTOMMEMBER; out.src = var; out.key = key; return true; } return false; } } } } } // could be a default delegate func return false; } bool SQDebugServer::GetObj_Var(const SQObjectPtr &var, string_t &expression, bool identifierIsString, objref_t &out, SQObjectPtr &value) { switch (sq_type(var)) { case OT_ARRAY: { if (expression.len <= 2 || !(expression.ptr[0] == '[' && expression.ptr[expression.len - 1] == ']')) return false; expression.ptr++; expression.len -= 2; SQInteger idx; if (strtoint(expression, &idx) && idx >= 0 && idx < _array(var)->Size()) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::ARRAY); out.ptr = &_array(var)->_values[idx]; out.src = var; out.key = idx; value = *out.ptr; return true; } return false; } case OT_TABLE: case OT_INSTANCE: case OT_CLASS: case OT_USERDATA: break; default: return false; } SQObjectPtr identifier; if (identifierIsString) { identifier = CreateSQString(this, expression); } else { switch (expression.ptr[0]) { // string case '\"': { AssertClient(expression.len >= 2); if (expression.len < 2) return false; expression.ptr++; expression.len -= 2; #ifdef SQUNICODE int len = sq_rsl(SQUnicodeLength(expression.ptr, expression.len) + 1); SQChar *tmp = (SQChar *)ScratchPad(len); if (!tmp) return false; len = UTF8ToSQUnicode(tmp, len, expression.ptr, expression.len); tmp[len] = 0; identifier = SQString::Create(_ss(m_pRootVM), tmp, len); #else UndoEscape(expression.ptr, &expression.len); identifier = CreateSQString(this, expression); #endif break; } // integer case '[': { AssertClient(expression.len > 2); if (expression.len <= 2) return false; expression.ptr++; expression.len -= 2; SQInteger val; if (!strtoint(expression, &val)) return false; identifier = val; break; } #if 0 // raw bytes case 'R': { AssertClient( expression.len > 4 ); if ( expression.len <= 4 ) return false; AssertClient( expression.ptr[1] == '\"' ); expression.ptr += 2; expression.len -= 3; if ( !ReadStringifiedBytes( expression.ptr, &expression.len ) ) return false; identifier = SQString::Create( _ss(m_pRootVM), (SQChar*)expression.ptr, expression.len / sizeof(SQChar) ); break; } #endif default: { // object, check every member if (expression.StartsWith("0x")) { AssertClient(expression.len >= FMT_PTR_LEN); if (expression.len < FMT_PTR_LEN) return false; expression.len = FMT_PTR_LEN; uintptr_t pKey; if (!atox(expression, &pKey)) return false; SQObjectPtr obj = var, key, val; SQ_FOREACH_OP(obj, key, val) { if (_rawval(key) == (SQRawObjectVal)pKey) { switch (sq_type(var)) { case OT_TABLE: out.type = objref_t::TABLE; break; case OT_INSTANCE: out.type = objref_t::INSTANCE; break; case OT_CLASS: { if (_class(var)->_members->Get(key, val)) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::CLASS); if (_isfield(val)) { out.ptr = &_class(var)->_defaultvalues[_member_idx(val)].val; } else { out.ptr = &_class(var)->_methods[_member_idx(val)].val; } out.src = var; out.key = key; value = *out.ptr; return true; } return false; } default: UNREACHABLE(); } out.src = var; out.key = key; value = val; return true; } } return false; } // float // NOTE: float keys are broken in pre 225 if sizeof(SQInteger) != sizeof(SQFloat), // this is fixed in SQ 225 with SQ_OBJECT_RAWINIT in SQObjectPtr::operator=(SQFloat) else { identifier = (SQFloat)strtod(expression.ptr, NULL); } } } } return GetObj_Var(var, identifier, out, value); } bool SQDebugServer::GetObj_Frame(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &expression, objref_t &out, SQObjectPtr &value) { Assert(expression.len); Assert(!ci || (ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize)); if (ci && sq_type(ci->_closure) == OT_CLOSURE) { SQClosure *pClosure = _closure(ci->_closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci->_ip - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if (var._start_op <= ip && var._end_op + 1 >= ip && expression.IsEqualTo(_string(var._name))) { int stackbase = GetStackBase(vm, ci); out.type = objref_t::STACK; out.stack.thread = GetWeakRef(vm); out.stack.frame = ci - vm->_callsstack; Assert(var._start_op < INT_MAX && var._end_op < INT_MAX); out.stack.start = var._start_op; out.stack.end = var._end_op + 1; out.stack.index = stackbase + var._pos; value = vm->_stack._vals[out.stack.index]; return true; } } for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; if (expression.IsEqualTo(_string(var._name))) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::OUTER); out.ptr = _outervalptr(pClosure->_outervalues[i]); out.src = pClosure; out.key = (SQInteger)i; value = *out.ptr; return true; } } if (expression.len == 6 || expression.len == 7) { if (expression.IsEqualTo(KW_THIS)) { int stackbase = GetStackBase(vm, ci); out.type = objref_t::PTR; out.ptr = &vm->_stack._vals[stackbase]; value = *out.ptr; return true; } #if SQUIRREL_VERSION_NUMBER >= 300 else if (func->_varparams && func->_nlocalvarinfos >= 2) { if (expression.IsEqualTo(KW_VARGV)) { const SQLocalVarInfo &var = func->_localvarinfos[func->_nlocalvarinfos - 2]; if (IsEqual(_SC("vargv"), _string(var._name))) { int stackbase = GetStackBase(vm, ci); out.type = objref_t::PTR; out.ptr = &vm->_stack._vals[stackbase + 1]; value = *out.ptr; return true; } } else if (expression.IsEqualTo(KW_VARGC)) { const SQLocalVarInfo &var = func->_localvarinfos[func->_nlocalvarinfos - 2]; if (IsEqual(_SC("vargv"), _string(var._name))) { int stackbase = GetStackBase(vm, ci); const SQObjectPtr &val = vm->_stack._vals[stackbase + 1]; if (sq_type(val) == OT_ARRAY) { out.type = (objref_t::EOBJREF)(objref_t::INT | objref_t::READONLY); out.val = (int)_array(val)->Size(); value = _array(val)->Size(); return true; } return false; } } } #else else if (func->_varparams) { // Returning a temporary vargv array here would be pointless in general, // and extra work for completions if (expression.IsEqualTo(KW_VARGC)) { out.type = (objref_t::EOBJREF)(objref_t::INT | objref_t::READONLY); out.val = (int)ci->_vargs.size; value = (SQInteger)ci->_vargs.size; return true; } } #endif } } else { if (expression.IsEqualTo("this")) { out.type = (objref_t::EOBJREF)(objref_t::PTR | objref_t::READONLY); out.ptr = &vm->_roottable; value = *out.ptr; return true; } } #if SQUIRREL_VERSION_NUMBER >= 220 SQTable *pConstTable = _table(_ss(vm)->_consts); if (SQTable_Get(this, pConstTable, expression, value)) { out.type = (objref_t::EOBJREF)(objref_t::TABLE | objref_t::READONLY); out.src = pConstTable; out.key = CreateSQString(this, expression); return true; } #endif SQTable *pEnvTable = NULL; if (ci) { const SQObjectPtr &env = vm->_stack._vals[GetStackBase(vm, ci)]; switch (sq_type(env)) { case OT_TABLE: { pEnvTable = _table(env); SQTable *t = _table(env); do { if (SQTable_Get(this, t, expression, value)) { out.type = objref_t::TABLE; out.src = t; out.key = CreateSQString(this, expression); return true; } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { SQObjectPtr strExpression = CreateSQString(this, expression); if (_instance(env)->Get(strExpression, value)) { out.type = objref_t::INSTANCE; out.src = env; out.key = strExpression; return true; } break; } default: break; } // metamethods if (is_delegable(env) && _delegable(env)->_delegate) { SQObjectPtr mm; if (_delegable(env)->GetMetaMethod(vm, MT_GET, mm)) { SQObjectPtr strExpression = CreateSQString(this, expression); // Function call can reallocate call stack int frame = ci - vm->_callsstack; if (RunClosure(vm, mm, &env, strExpression, value)) { out.type = objref_t::DELEGABLE_META; out.src = env; out.key = strExpression; return true; } ci = vm->_callsstack + frame; } } } SQTable *root; #ifdef CLOSURE_ROOT if (ci && sq_type(ci->_closure) == OT_CLOSURE && _closure(ci->_closure)->_root) { Assert(sq_type(_closure(ci->_closure)->_root->_obj) == OT_TABLE); root = _table(_closure(ci->_closure)->_root->_obj); } else { root = _table(vm->_roottable); } #else root = _table(vm->_roottable); #endif if (root != pEnvTable) { do { if (SQTable_Get(this, root, expression, value)) { out.type = objref_t::TABLE; out.src = root; out.key = CreateSQString(this, expression); return true; } } while ((root = root->_delegate) != NULL); } return false; } bool SQDebugServer::GetObj_VarRef(const varref_t *ref, string_t &expression, objref_t &out, SQObjectPtr &value) { switch (ref->type) { case VARREF_OBJ: { return GetObj_Var(ref->GetVar(), expression, !ref->obj.hasNonStringMembers, out, value); } case VARREF_SCOPE_LOCALS: case VARREF_SCOPE_OUTERS: { return GetObj_Frame(ref->GetThread(), ref->scope.frame, expression, out, value); } case VARREF_OUTERS: { SQObject target = ref->GetVar(); AssertClient(sq_type(target) == OT_CLOSURE); if (sq_type(target) == OT_CLOSURE) { SQClosure *pClosure = _closure(target); SQFunctionProto *func = _fp(pClosure->_function); for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; if (expression.IsEqualTo(_string(var._name))) { out.type = objref_t::PTR; out.ptr = _outervalptr(pClosure->_outervalues[i]); value = *out.ptr; return true; } } } return false; } case VARREF_LITERALS: { SQObject target = ref->GetVar(); AssertClient(sq_type(target) == OT_FUNCPROTO); if (sq_type(target) == OT_FUNCPROTO) { int idx; if (atoi(expression, &idx) && idx >= 0 && idx < (int)_funcproto(target)->_nliterals) { out.type = objref_t::PTR; out.ptr = &_funcproto(target)->_literals[idx]; value = *out.ptr; return true; } } return false; } #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST case VARREF_FUNCTIONS: { SQObject target = ref->GetVar(); AssertClient(sq_type(target) == OT_FUNCPROTO); if (sq_type(target) == OT_FUNCPROTO) { int idx; if (atoi(expression, &idx) && idx >= 0 && idx < (int)_funcproto(target)->_nfunctions) { out.type = objref_t::FUNCPROTO; out.ptr = &_funcproto(target)->_functions[idx]; value = *out.ptr; return true; } } return false; } #endif case VARREF_METAMETHODS: { int mm = -1; for (int i = 0; i < MT_LAST; i++) { if (expression.IsEqualTo(g_MetaMethodName[i])) { mm = i; break; } } AssertClient(mm != -1); if (mm != -1) { switch (sq_type(ref->GetVar())) { case OT_CLASS: { out.type = objref_t::PTR; out.ptr = &_class(ref->GetVar())->_metamethods[mm]; value = *out.ptr; return true; } case OT_TABLE: { // metamethods are regular members of tables Assert(_table(ref->GetVar())->_delegate); out.type = objref_t::TABLE; out.src = _table(ref->GetVar())->_delegate; out.key = CreateSQString(this, expression); return _table(out.src)->Get(out.key, value); } default: AssertClient(!"invalid object"); } } return false; } case VARREF_STACK: { AssertClient(expression.len > 5); if (expression.len <= 5) return false; while (expression.len > 5 && expression.ptr[expression.len - 1] != ']') expression.len--; expression.ptr++; expression.len -= 2; AssertClient(expression.ptr[-1] == '['); char *pIdx = strchr(expression.ptr, ']'); string_t strFrame(expression.ptr, pIdx - expression.ptr); HSQUIRRELVM vm = ref->GetThread(); int frame; if (!pIdx || !strtoint(strFrame, &frame) || frame < 0 || frame >= (int)vm->_callsstacksize) return false; int stackbase = GetStackBase(vm, frame); expression.ptr += strFrame.len + 2; expression.len -= strFrame.len + 2; AssertClient(expression.ptr[-1] == '['); int idx; if (!strtoint(expression, &idx) || stackbase + idx < 0 || stackbase + idx >= (int)vm->_stack.size()) return false; out.type = objref_t::PTR; out.ptr = &vm->_stack._vals[stackbase + idx]; value = *out.ptr; return true; } case VARREF_INSTRUCTIONS: case VARREF_CALLSTACK: { return false; } default: { PrintError(_SC("(sqdbg) Invalid varref requested (%d)\n"), ref->type); AssertClientMsg1(0, "Invalid varref requested (%d)", ref->type); return false; } } } static inline conststring_t GetPresentationHintKind(const SQObjectPtr &obj) { switch (sq_type(obj)) { case OT_CLOSURE: case OT_NATIVECLOSURE: return "method"; case OT_CLASS: return "class"; default: return "property"; } } bool SQDebugServer::ShouldPageArray(const SQObject &obj, unsigned int limit) { return (sq_type(obj) == OT_ARRAY && _array(obj)->_values.size() > limit); } bool SQDebugServer::ShouldParseEvaluateName(const string_t &expression) { return (expression.len >= 4 && expression.ptr[0] == '@' && expression.ptr[2] == '@'); } bool SQDebugServer::ParseEvaluateName(const string_t &expression, HSQUIRRELVM vm, int frame, objref_t &out, SQObjectPtr &value) { Assert(ShouldParseEvaluateName(expression)); if (expression.ptr[1] == 'L') { int idx; if (!atoi({expression.ptr + 3, expression.len - 3}, &idx)) return false; if (!IsValidStackFrame(vm, frame)) return false; const SQVM::CallInfo &ci = vm->_callsstack[frame]; if (sq_type(ci._closure) != OT_CLOSURE) return false; SQClosure *pClosure = _closure(ci._closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci._ip - func->_instructions; idx = func->_nlocalvarinfos - idx - 1; if (idx < 0 || idx >= func->_nlocalvarinfos) return false; const SQLocalVarInfo &var = func->_localvarinfos[idx]; if (var._start_op <= ip && var._end_op + 1 >= ip) { int stackbase = GetStackBase(vm, &ci); out.type = objref_t::PTR; out.ptr = &vm->_stack._vals[stackbase + var._pos]; value = *out.ptr; return true; } } return false; } bool SQDebugServer::ParseBinaryNumber(const string_t &value, SQObject &out) { const int maxbitlen = max(sizeof(SQInteger), sizeof(SQFloat)) * 8; // expect 0b prefix if (value.len <= 2 || value.len > maxbitlen + 2 || value.ptr[0] != '0' || value.ptr[1] != 'b') return false; out._type = OT_INTEGER; out._unVal.nInteger = 0; int inputbitlen = value.len - 2; Assert(inputbitlen > 0 && inputbitlen <= maxbitlen); for (int i = value.len - 1; i >= 2; i--) { switch (value.ptr[i]) { case '1': out._unVal.nInteger |= ((SQUnsignedInteger)1 << ((inputbitlen - 1) - (i - 2))); case '0': continue; default: return false; } } return true; } int SQDebugServer::ParseFormatSpecifiers(string_t &expression, char **ppComma) { if (expression.len <= 2) return 0; int flags = 0; // 7 flags at most ",*lnax0b\0" char *start = expression.ptr + expression.len - 9; char *end = expression.ptr + expression.len; char *c = end - 1; char *comma = NULL; if (start < expression.ptr) start = expression.ptr; for (; c > start; c--) { if (*c == ',') { comma = c; c++; break; } } // have flags if (comma) { // has to be the first flag if (*c == '*') { flags |= kFS_Lock; c++; } if (c < end) { check: switch (*c++) { case 'x': flags |= kFS_Hexadecimal; break; case 'X': flags |= kFS_Hexadecimal | kFS_Uppercase; break; case 'b': flags |= kFS_Binary; break; case 'd': flags |= kFS_Decimal; break; case 'o': flags |= kFS_Octal; break; case 'c': flags |= kFS_Character; break; case 'f': flags |= kFS_Float; break; case 'e': flags |= kFS_FloatE; break; case 'g': flags |= kFS_FloatG; break; case 'l': if (flags & kFS_ListMembers) return 0; flags |= kFS_ListMembers; if (c < end) goto check; break; case 'n': if (flags & kFS_NoAddr) return 0; if (c < end && *c++ == 'a') { flags |= kFS_NoAddr; if (c < end) goto check; break; } default: return 0; // Invalid flag } // modifier if ((flags & (kFS_Hexadecimal | kFS_Binary)) && c < end) { switch (*c++) { case '0': flags |= kFS_Padding; break; case 'b': flags |= kFS_NoPrefix; break; default: return 0; } if ((flags & kFS_Padding) && c < end) { switch (*c++) { case 'b': flags |= kFS_NoPrefix; break; default: return 0; } } } // there should be no more flags if (c < end) return 0; } if (flags) { expression.len = comma - expression.ptr; // Terminate here, this expression might be passed to SQTable::Get through GetObj, // which compares strings disregarding length *comma = 0; if (ppComma) *ppComma = comma; } } return flags; } void SQDebugServer::OnRequest_Evaluate(const json_table_t &arguments, int seq) { HSQUIRRELVM vm; int frame; string_t context, expression; arguments.GetString("context", &context); arguments.GetString("expression", &expression); arguments.GetInt("frameId", &frame, -1); if (expression.IsEmpty()) { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "empty expression"); DAP_SEND(); return; } if (!TranslateFrameID(frame, &vm, &frame)) { vm = m_pCurVM; frame = INVALID_FRAME; } int flags = 0; { json_table_t *format; if (arguments.GetTable("format", &format)) { bool hex; format->GetBool("hex", &hex); if (hex) flags |= kFS_Hexadecimal; } } if (IS_INTERNAL_TAG(expression.ptr)) { if (expression.IsEqualTo(INTERNAL_TAG("stack"))) { DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); body.SetIntBrackets("result", (SQInteger)vm->_stack.size(), (flags & kFS_Hexadecimal) != 0); body.SetInt("variablesReference", ToVarRef(VARREF_STACK, vm, frame)); wjson_table_t hint = body.SetTable("presentationHint"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); DAP_SEND(); return; } else if (expression.IsEqualTo(INTERNAL_TAG("function"))) { if (IsValidStackFrame(vm, frame)) { const SQObjectPtr &res = vm->_callsstack[frame]._closure; DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetString("type", GetType(res)); body.SetInt("variablesReference", ToVarRef(res)); wjson_table_t hint = body.SetTable("presentationHint"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "could not evaluate expression"); DAP_SEND(); } return; } else if (expression.IsEqualTo(INTERNAL_TAG("caller"))) { if (IsValidStackFrame(vm, frame - 1)) { const SQObjectPtr &res = vm->_callsstack[frame - 1]._closure; DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetString("type", GetType(res)); body.SetInt("variablesReference", ToVarRef(res)); wjson_table_t hint = body.SetTable("presentationHint"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "could not evaluate expression"); DAP_SEND(); } return; } } if (context.IsEqualTo("repl")) { // Don't hit breakpoints unless it's repl m_bInREPL = true; // Don't print quotes in repl flags |= kFS_NoQuote; } else { m_bDebugHookGuardAlways = true; } SQObjectPtr res; if (context.IsEqualTo("watch") || context.IsEqualTo("clipboard") || context.IsEqualTo("hover")) { flags |= ParseFormatSpecifiers(expression); objref_t obj; if (ShouldParseEvaluateName(expression)) { if (ParseEvaluateName(expression, vm, frame, obj, res)) { DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetString("type", GetType(res)); body.SetInt("variablesReference", ToVarRef(res)); if (ShouldPageArray(res, ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(res)->_values.size()); wjson_table_t hint = body.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(res)); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "could not evaluate expression"); DAP_SEND(); } return; } if (flags & kFS_Lock) { bool foundWatch = false; for (unsigned int i = 0; i < m_LockedWatches.Size(); i++) { const watch_t &w = m_LockedWatches[i]; if (w.expression.IsEqualTo(expression)) { vm = GetThread(w.thread); frame = IsValidStackFrame(vm, w.frame) ? w.frame : INVALID_FRAME; foundWatch = true; break; } } if (!foundWatch) { watch_t &w = m_LockedWatches.Append(); CopyString(&m_Strings, expression, &w.expression); w.thread = GetWeakRef(vm); w.frame = frame; } } #ifndef SQDBG_DISABLE_COMPILER ECompileReturnCode cres = Evaluate(expression, vm, frame, res); if (cres == CompileReturnCode_Success) { DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetString("type", GetType(res)); body.SetInt("variablesReference", ToVarRef(res)); if (ShouldPageArray(res, ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(res)->_values.size()); wjson_table_t hint = body.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(res)); DAP_SEND(); } #else int success = GetObj_Frame(vm, frame, expression, obj, res); if (!success && (RunExpression(expression, vm, frame, res) || ParseBinaryNumber(expression, res))) success = 2; if (success) { DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetString("type", GetType(res)); body.SetInt("variablesReference", ToVarRef(res)); if (ShouldPageArray(res, ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(res)->_values.size()); wjson_table_t hint = body.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(res)); if (success == 2) { wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); } DAP_SEND(); } #endif else { if (flags & kFS_Lock) { for (unsigned int i = 0; i < m_LockedWatches.Size(); i++) { watch_t &w = m_LockedWatches[i]; if (w.expression.IsEqualTo(expression)) { FreeString(&m_Strings, &w.expression); m_LockedWatches.Remove(i); break; } } } DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "could not evaluate expression"); DAP_SEND(); } } else { AssertClient(context.IsEqualTo("repl") || context.IsEqualTo("variables")); #if defined(SQDBG_USE_COMPILER_FOR_REPL) && !defined(SQDBG_DISABLE_COMPILER) bool success; if (!expression.Contains('\n')) { if (context.IsEqualTo("repl")) flags |= ParseFormatSpecifiers(expression); CCompiler c(expression); ECompileReturnCode cres = c.Evaluate(this, vm, frame, res); switch (cres) { case CompileReturnCode_Success: success = true; break; case CompileReturnCode_DoesNotExist: vm->_lasterror = CreateSQString(vm, _SC("index does not exist")); success = false; break; case CompileReturnCode_SyntaxError: vm->_lasterror = CreateSQString(vm, c.LastError()); case CompileReturnCode_CallError: success = false; break; default: Assert(cres > CompileReturnCode_Fallback); success = RunExpression(expression, vm, frame, res); } } else { success = RunExpression(expression, vm, frame, res, true); } if (success) #else if (RunExpression(expression, vm, frame, res, expression.Contains('\n')) || ParseBinaryNumber(expression, res)) #endif { DAP_START_RESPONSE(seq, "evaluate"); DAP_SET_TABLE(body); JSONSetString(body, "result", res, flags); body.SetInt("variablesReference", ToVarRef(res)); if (ShouldPageArray(res, ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(res)->_values.size()); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "{reason}"); wjson_table_t variables = error.SetTable("variables"); JSONSetString(variables, "reason", vm->_lasterror, kFS_NoQuote); DAP_SEND(); } } if (context.IsEqualTo("repl")) { m_bInREPL = false; } else { m_bDebugHookGuardAlways = false; } } #ifndef SQDBG_DISABLE_COMPILER // A very simple and incomplete completions implementation // using the extra information from the sqdbg compiler. // Easily breaks within parantheses and brackets, works best with simple expressions void SQDebugServer::OnRequest_Completions(const json_table_t &arguments, int seq) { HSQUIRRELVM vm; int frame; string_t text; int column; arguments.GetString("text", &text); arguments.GetInt("frameId", &frame, -1); arguments.GetInt("column", &column); column -= (int)m_bClientColumnOffset; if (column < 0 || column > (int)text.len) { DAP_ERROR_RESPONSE(seq, "completions"); DAP_ERROR_BODY(0, "invalid column"); DAP_SEND(); return; } if (!TranslateFrameID(frame, &vm, &frame)) { vm = m_pCurVM; frame = INVALID_FRAME; } SQObjectPtr target; string_t expr(text.ptr, column); CCompiler c(expr); ECompileReturnCode r = c.Evaluate(this, vm, frame, target); CCompiler::token_t token = c.m_lastToken; if ((r != CompileReturnCode_Success && r != CompileReturnCode_DoesNotExist && r != CompileReturnCode_NoValue) && token.type != 0 && token.type != CCompiler::Token_End && token.type != '.' && token.type != CCompiler::Token_Identifier) { DAP_ERROR_RESPONSE(seq, "completions"); DAP_ERROR_BODY(0, ""); DAP_SEND(); return; } if (token.type != '.' && token.type != CCompiler::Token_Identifier) { token.type = 0; target.Null(); } int start = column; int length = 0; if (token.type == CCompiler::Token_Identifier) { start = token._string.ptr - text.ptr; length = token._string.len; } DAP_START_RESPONSE(seq, "completions"); DAP_SET_TABLE(body); wjson_array_t targets = body.SetArray("targets"); FillCompletions(target, vm, frame, token.type, token._string, start == column ? -1 : start, length, targets); DAP_SEND(); } void SQDebugServer::FillCompletions(const SQObjectPtr &target, HSQUIRRELVM vm, int frame, int token, const string_t &partial, int start, int length, wjson_array_t &targets) { #define _check(key) \ (token == '.' || token == 0 || \ (token == CCompiler::Token_Identifier && \ sqstring_t((key)).StartsWith(partial))) #define _set(priority, label, val) \ wjson_table_t elem = targets.AppendTable(); \ elem.SetString("label", label); \ { \ jstringbuf_t sortbuf = elem.SetStringAsBuf("sortText"); \ sortbuf.PutInt(priority); \ sortbuf.Puts(label); \ } \ switch (sq_type(val)) \ { \ case OT_CLOSURE: \ case OT_NATIVECLOSURE: \ elem.SetString("type", "function"); \ elem.SetString("detail", "function"); \ break; \ case OT_CLASS: \ { \ elem.SetString("type", "class"); \ const classdef_t *valdef = FindClassDef(_class(val)); \ elem.SetString("detail", valdef && valdef->name.ptr ? string_t(valdef->name.ptr + FMT_PTR_LEN + 1, valdef->name.len - FMT_PTR_LEN - 1) : string_t("class")); \ break; \ } \ case OT_INSTANCE: \ elem.SetString("type", "field"); \ elem.SetString("detail", "instance"); \ break; \ default: \ elem.SetString("type", "variable"); \ elem.SetString("detail", GetType(val)); \ } \ if (start != -1) \ elem.SetInt("start", start); \ if (length) \ elem.SetInt("length", length); switch (sq_type(target)) { case OT_TABLE: { SQTable *t = _table(target); SQObjectPtr key, val; do { FOREACH_SQTABLE(t, key, val) { if (sq_type(key) == OT_STRING && _check(_string(key))) { _set(0, _string(key), val); } } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { SQClass *base = _instance(target)->_class; Assert(base); // metamembers SQObjectPtr mm; const classdef_t *def = FindClassDef(base); if (def && sq_type(def->metamembers) != OT_NULL && _instance(target)->GetMetaMethod(vm, MT_GET, mm)) { for (unsigned int i = 0; i < _array(def->metamembers)->_values.size(); i++) { const SQObjectPtr &key = _array(def->metamembers)->_values[i]; SQObjectPtr val; if (sq_type(key) == OT_STRING && _check(_string(key))) { RunClosure(mm, &target, key, val); _set(0, _string(key), val); } } } SQObjectPtr key, val; // values FOREACH_SQTABLE(base->_members, key, val) { if (_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _instance(target)->Get(key, val); _set(0, _string(key), val); } } // methods FOREACH_SQTABLE(base->_members, key, val) { if (!_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _instance(target)->Get(key, val); _set(1, _string(key), val); } } break; } case OT_CLASS: { SQObjectPtr key, val; // values FOREACH_SQTABLE(_class(target)->_members, key, val) { if (_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _class(target)->Get(key, val); _set(0, _string(key), val); } } // methods FOREACH_SQTABLE(_class(target)->_members, key, val) { if (!_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _class(target)->Get(key, val); _set(1, _string(key), val); } } break; } default: break; } SQTable *del = GetDefaultDelegate(vm, sq_type(target)); if (del) { SQObjectPtr key, val; FOREACH_SQTABLE(del, key, val) { if (sq_type(key) == OT_STRING && _check(_string(key))) { _set(2, _string(key), val); } } } if (sq_type(target) == OT_NULL) { SQTable *pEnvTable = NULL; if (frame != INVALID_FRAME) { const SQVM::CallInfo *ci = vm->_callsstack + frame; int stackbase = GetStackBase(vm, ci); // locals if (sq_type(ci->_closure) == OT_CLOSURE) { SQClosure *pClosure = _closure(ci->_closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci->_ip - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if (var._start_op <= ip && var._end_op + 1 >= ip && _check(_string(var._name))) { _set(0, _string(var._name), vm->_stack._vals[stackbase + var._pos]); if (IsEqual(_SC("this"), _string(var._name))) { elem.SetString("text", KW_THIS); } #if SQUIRREL_VERSION_NUMBER >= 300 else if (IsEqual(_SC("vargv"), _string(var._name))) { elem.SetString("text", KW_VARGV); } #endif } } for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; if (_check(_string(var._name))) { _set(0, _string(var._name), *_outervalptr(pClosure->_outervalues[i])); } } #if SQUIRREL_VERSION_NUMBER < 300 if (func->_varparams) { if (token == CCompiler::Token_Identifier && string_t("vargv").StartsWith(partial)) { _set(0, "vargv", SQObjectPtr()); elem.SetString("text", KW_VARGV); } if (token == CCompiler::Token_Identifier && string_t("vargc").StartsWith(partial)) { _set(0, "vargc", SQObjectPtr((SQInteger)0)); elem.SetString("text", KW_VARGC); } } #endif } const SQObjectPtr &env = vm->_stack._vals[stackbase]; switch (sq_type(env)) { case OT_TABLE: { pEnvTable = _table(env); SQTable *t = _table(env); SQObjectPtr key, val; do { FOREACH_SQTABLE(t, key, val) { if (sq_type(key) == OT_STRING && _check(_string(key))) { _set(1, _string(key), val); } } } while ((t = t->_delegate) != NULL); break; } case OT_INSTANCE: { SQClass *base = _instance(env)->_class; Assert(base); // metamembers SQObjectPtr mm; const classdef_t *def = FindClassDef(base); if (def && sq_type(def->metamembers) != OT_NULL && _instance(env)->GetMetaMethod(vm, MT_GET, mm)) { for (unsigned int i = 0; i < _array(def->metamembers)->_values.size(); i++) { const SQObjectPtr &key = _array(def->metamembers)->_values[i]; SQObjectPtr val; if (sq_type(key) == OT_STRING && _check(_string(key))) { RunClosure(mm, &env, key, val); _set(1, _string(key), val); } } } SQObjectPtr key, val; // values FOREACH_SQTABLE(base->_members, key, val) { if (_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _instance(env)->Get(key, val); _set(1, _string(key), val); } } // methods FOREACH_SQTABLE(base->_members, key, val) { if (!_isfield(val) && sq_type(key) == OT_STRING && _check(_string(key))) { _instance(env)->Get(key, val); _set(2, _string(key), val); } } break; } default: break; } } SQTable *root; #ifdef CLOSURE_ROOT const SQVM::CallInfo *ci = vm->_callsstack + frame; if (frame != INVALID_FRAME && sq_type(ci->_closure) == OT_CLOSURE && _closure(ci->_closure)->_root) { Assert(sq_type(_closure(ci->_closure)->_root->_obj) == OT_TABLE); root = _table(_closure(ci->_closure)->_root->_obj); } else { root = _table(vm->_roottable); } #else root = _table(vm->_roottable); #endif if (root != pEnvTable) { SQObjectPtr key, val; do { FOREACH_SQTABLE(root, key, val) { if (sq_type(key) == OT_STRING && _check(_string(key))) { _set(3, _string(key), val); } } } while ((root = root->_delegate) != NULL); } } #undef _set #undef _check } #endif void SQDebugServer::OnRequest_Scopes(const json_table_t &arguments, int seq) { HSQUIRRELVM vm; int frame; arguments.GetInt("frameId", &frame, -1); if (!TranslateFrameID(frame, &vm, &frame)) { DAP_ERROR_RESPONSE(seq, "scopes"); DAP_ERROR_BODY(0, "invalid stack frame {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", frame); DAP_SEND(); return; } const SQVM::CallInfo &ci = vm->_callsstack[frame]; if (sq_type(ci._closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "scopes"); DAP_ERROR_BODY(0, "native call frame"); DAP_SEND(); return; } SQClosure *pClosure = _closure(ci._closure); SQFunctionProto *func = _fp(pClosure->_function); DAP_START_RESPONSE(seq, "scopes"); DAP_SET_TABLE(body); wjson_array_t scopes = body.SetArray("scopes"); { wjson_table_t locals = scopes.AppendTable(); locals.SetString("name", "Locals"); locals.SetString("presentationHint", "locals"); locals.SetBool("expensive", false); locals.SetInt("variablesReference", ToVarRef(VARREF_SCOPE_LOCALS, vm, frame)); } if (func->_noutervalues) { wjson_table_t outers = scopes.AppendTable(); outers.SetString("name", "Outers"); outers.SetString("presentationHint", "locals"); outers.SetBool("expensive", false); outers.SetInt("variablesReference", ToVarRef(VARREF_SCOPE_OUTERS, vm, frame)); } DAP_SEND(); } int SQDebugServer::ThreadToID(HSQUIRRELVM vm) { if (m_Threads.Size() >= INT_MAX - 1) { RemoveThreads(); ThreadToID(m_pRootVM); } for (int i = m_Threads.Size(); i--;) { SQWeakRef *wr = m_Threads[i]; if (wr && sq_type(wr->_obj) == OT_THREAD) { if (_thread(wr->_obj) == vm) return i; } else { __ObjRelease(wr); m_Threads.Remove(i); } } SQWeakRef *wr = GetWeakRef(vm); __ObjAddRef(wr); int i = m_Threads.Size(); m_Threads.Append(wr); return i; } HSQUIRRELVM SQDebugServer::ThreadFromID(int id) { if (id >= 0 && id < (int)m_Threads.Size()) { SQWeakRef *wr = m_Threads[id]; if (wr && sq_type(wr->_obj) == OT_THREAD) return _thread(wr->_obj); __ObjRelease(wr); m_Threads.Remove(id); } return NULL; } void SQDebugServer::RemoveThreads() { for (int i = m_Threads.Size(); i--;) { SQWeakRef *wr = m_Threads[i]; __ObjRelease(wr); } m_Threads.Purge(); } void SQDebugServer::OnRequest_Threads(int seq) { DAP_START_RESPONSE(seq, "threads"); DAP_SET_TABLE(body); wjson_array_t threads = body.SetArray("threads"); for (int i = 0; i < (int)m_Threads.Size(); i++) { SQWeakRef *wr = m_Threads[i]; if (wr && sq_type(wr->_obj) == OT_THREAD) { wjson_table_t thread = threads.AppendTable(); thread.SetInt("id", i); if (_thread(wr->_obj) == m_pRootVM) { thread.SetString("name", "MainThread"); } else { jstringbuf_t name = thread.SetStringAsBuf("name"); name.Puts("Thread "); name.PutHex((uintptr_t)_thread(wr->_obj)); } } else { __ObjRelease(wr); m_Threads.Remove(i); i--; } } DAP_SEND(); } bool SQDebugServer::ShouldIgnoreStackFrame(HSQUIRRELVM vm, const SQVM::CallInfo &ci) { // Don't ignore debugger functions // if they were somehow stepped into, to prevent confusion if (vm->_callsstacksize == 1) return false; // Ignore RunScript (first frame) if (sq_type(ci._closure) == OT_CLOSURE && sq_type(_fp(_closure(ci._closure)->_function)->_sourcename) == OT_STRING && IsEqual(_SC("sqdbg"), _string(_fp(_closure(ci._closure)->_function)->_sourcename))) return true; // Ignore error handler / debug hook (last frame) if (sq_type(ci._closure) == OT_NATIVECLOSURE && (_nativeclosure(ci._closure)->_function == &SQDebugServer::SQErrorHandler #ifndef NATIVE_DEBUG_HOOK || _nativeclosure(ci._closure)->_function == &SQDebugServer::SQDebugHook #endif )) return true; return false; } int SQDebugServer::ConvertToFrameID(int threadId, int stackFrame) { for (int i = 0; i < (int)m_FrameIDs.Size(); i++) { const frameid_t &v = m_FrameIDs[i]; if (v.threadId == threadId && v.frame == stackFrame) return i; } int i = m_FrameIDs.Size(); frameid_t &v = m_FrameIDs.Append(); v.threadId = threadId; v.frame = stackFrame; return i; } bool SQDebugServer::TranslateFrameID(int frameId, HSQUIRRELVM *thread, int *stackFrame) { if (frameId >= 0 && frameId < (int)m_FrameIDs.Size()) { frameid_t &v = m_FrameIDs[frameId]; *thread = ThreadFromID(v.threadId); *stackFrame = v.frame; return thread && *thread && IsValidStackFrame(*thread, *stackFrame); } return false; } void SQDebugServer::OnRequest_StackTrace(const json_table_t &arguments, int seq) { int threadId, startFrame, levels; json_table_t *format; bool parameters = true, parameterTypes = false, parameterNames = true, parameterValues = false; arguments.GetInt("threadId", &threadId, -1); arguments.GetInt("startFrame", &startFrame); arguments.GetInt("levels", &levels); if (arguments.GetTable("format", &format)) { format->GetBool("parameters", ¶meters); format->GetBool("parameterTypes", ¶meterTypes); format->GetBool("parameterNames", ¶meterNames); format->GetBool("parameterValues", ¶meterValues); } HSQUIRRELVM vm = ThreadFromID(threadId); if (!vm) { DAP_ERROR_RESPONSE(seq, "stackTrace"); DAP_ERROR_BODY(0, "invalid thread {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", threadId); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK int lastFrame = vm->_callsstacksize - 1; #else int lastFrame = vm->_callsstacksize - 1 - 1; #endif if (startFrame > lastFrame) { DAP_ERROR_RESPONSE(seq, "stackTrace"); DAP_ERROR_BODY(0, ""); DAP_SEND(); return; } if (startFrame < 0) startFrame = 0; // reverse startFrame = lastFrame - startFrame; if (levels <= 0 || levels > lastFrame) levels = lastFrame; int targetFrame = startFrame - levels; if (targetFrame < 0) targetFrame = 0; DAP_START_RESPONSE(seq, "stackTrace"); DAP_SET_TABLE(body); { wjson_array_t stackFrames = body.SetArray("stackFrames"); for (int i = startFrame; i >= targetFrame; i--) { const SQVM::CallInfo &ci = vm->_callsstack[i]; if (ShouldIgnoreStackFrame(vm, ci)) continue; if (sq_type(ci._closure) == OT_CLOSURE) { SQFunctionProto *func = _fp(_closure(ci._closure)->_function); wjson_table_t frame = stackFrames.AppendTable(); frame.SetInt("id", ConvertToFrameID(threadId, i)); { jstringbuf_t buf = frame.SetStringAsBuf("name"); if (sq_type(func->_name) == OT_STRING) { buf.Puts(_string(func->_name)); } else { buf.PutHex((uintptr_t)func); } if (parameters) { buf.Put('('); Assert(func->_nparameters); int nparams = func->_nparameters; #if SQUIRREL_VERSION_NUMBER >= 300 if (nparams > 1) #else if (nparams > 1 || func->_varparams) #endif { #if SQUIRREL_VERSION_NUMBER >= 300 if (func->_varparams) nparams--; #endif for (int j = 1; j < nparams; j++) { const SQObjectPtr ¶m = func->_parameters[j]; Assert(sq_type(param) == OT_STRING); if (parameterTypes) { const SQObjectPtr &val = vm->_stack._vals[GetStackBase(vm, &ci) + j]; buf.Puts(GetType(val)); buf.Put(' '); } if (parameterNames) buf.Puts(_string(param)); if (parameterValues) { if (parameterNames) buf.Puts(" = "); const SQObjectPtr &val = vm->_stack._vals[GetStackBase(vm, &ci) + j]; switch (sq_type(val)) { case OT_INTEGER: case OT_FLOAT: case OT_BOOL: case OT_NULL: case OT_STRING: case OT_TABLE: case OT_ARRAY: case OT_CLASS: { string_t str = GetValue(val); if (str.len > 32 && sq_type(val) == OT_STRING) str.len = 32; buf.Puts(str); break; } default: Assert(ISREFCOUNTED(sq_type(val))); buf.PutHex((uintptr_t)_refcounted(val)); } } buf.Put(','); buf.Put(' '); } if (!func->_varparams) { buf.Seek(-2); } else { buf.Put('.'); buf.Put('.'); buf.Put('.'); } } buf.Put(')'); } } if (sq_type(func->_sourcename) == OT_STRING) { wjson_table_t source = frame.SetTable("source"); SetSource(source, _string(func->_sourcename)); } frame.SetInt("line", (int)func->GetLine(ci._ip)); frame.SetInt("column", 1); { jstringbuf_t buf = frame.SetStringAsBuf("instructionPointerReference"); buf.PutHex((uintptr_t)ci._ip); } } else if (sq_type(ci._closure) == OT_NATIVECLOSURE) { SQNativeClosure *closure = _nativeclosure(ci._closure); wjson_table_t frame = stackFrames.AppendTable(); frame.SetInt("id", ConvertToFrameID(threadId, i)); { wjson_table_t source = frame.SetTable("source"); source.SetString("name", "NATIVE"); } { jstringbuf_t buf = frame.SetStringAsBuf("name"); if (sq_type(closure->_name) == OT_STRING) { buf.Puts(_string(closure->_name)); } else { buf.PutHex((uintptr_t)closure); } if (parameters) { buf.Put('('); buf.Put(')'); } } frame.SetInt("line", -1); frame.SetInt("column", 1); frame.SetString("presentationHint", "subtle"); } else UNREACHABLE(); } } DAP_SET("totalFrames", lastFrame + 1); DAP_SEND(); } static bool HasMetaMethods(HSQUIRRELVM vm, const SQObject &obj) { switch (sq_type(obj)) { case OT_CLASS: { for (unsigned int i = 0; i < MT_LAST; i++) { if (sq_type(_class(obj)->_metamethods[i]) != OT_NULL) { return true; } } return false; } default: { if (is_delegable(obj) && _delegable(obj)->_delegate) { SQObjectPtr dummy; for (unsigned int i = 0; i < MT_LAST; i++) { if (_delegable(obj)->GetMetaMethod(vm, (SQMetaMethod)i, dummy)) { return true; } } } return false; } } } static inline void SetVirtualHint(wjson_table_t &elem) { wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", "virtual"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); } static int _sortkeys(const SQObjectPtr *a, const SQObjectPtr *b) { if (sq_type(*a) == OT_STRING) { if (sq_type(*b) == OT_STRING) { return scstricmp(_string(*a)->_val, _string(*b)->_val); } else { return 1; } } else { if (sq_type(*b) == OT_STRING) { return -1; } else { return (_integer(*a) >= _integer(*b)); } } } #define _checkNonStringMembers(key) \ (sq_type(key) != OT_STRING || \ _string(key)->_len == 0 || \ HasEscapes(_string(key)->_val, _string(key)->_len)) static inline void SortKeys(SQTable *table, vector *values, bool *hasNonStringMembers) { bool nsm = false; SQObjectPtr key, val; FOREACH_SQTABLE(table, key, val) { values->Append(key); if (!nsm) nsm = _checkNonStringMembers(key); } values->Sort(_sortkeys); *hasNonStringMembers = nsm; } static inline void SortKeys(SQClass *pClass, int *nAttributes, vector *values, vector *methods, bool *hasNonStringMembers) { bool nsm = false; *nAttributes = 0; SQObjectPtr key, idx; FOREACH_SQTABLE(pClass->_members, key, idx) { // Ignore inherited fields if (pClass->_base) { SQObjectPtr baseval; if (pClass->_base->Get(key, baseval)) { const SQObjectPtr &val = _isfield(idx) ? pClass->_defaultvalues[_member_idx(idx)].val : pClass->_methods[_member_idx(idx)].val; if (IsEqual(val, baseval)) continue; } } if (_isfield(idx)) { values->Append(key); } else { methods->Append(key); } const SQObjectPtr &attr = _isfield(idx) ? pClass->_defaultvalues[_member_idx(idx)].attrs : pClass->_methods[_member_idx(idx)].attrs; if (sq_type(attr) != OT_NULL) (*nAttributes)++; if (!nsm) nsm = _checkNonStringMembers(key); } if (values->Size()) values->Sort(_sortkeys); if (methods->Size()) methods->Sort(_sortkeys); *hasNonStringMembers = nsm; } static inline void SortKeys(SQClass *pClass, vector *values, bool *hasNonStringMembers) { bool nsm = false; SQObjectPtr key, idx; FOREACH_SQTABLE(pClass->_members, key, idx) { if (_isfield(idx)) { values->Append(key); if (!nsm) nsm = _checkNonStringMembers(key); } } if (values->Size()) values->Sort(_sortkeys); *hasNonStringMembers = nsm; } #undef _checkNonStringMembers void SQDebugServer::OnRequest_Variables(const json_table_t &arguments, int seq) { int variablesReference; arguments.GetInt("variablesReference", &variablesReference); varref_t *ref = FromVarRef(variablesReference); if (!ref) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "failed to find variable"); DAP_SEND(); return; } int flags = 0; { json_table_t *format; if (arguments.GetTable("format", &format)) { bool hex; format->GetBool("hex", &hex); if (hex) flags |= kFS_Hexadecimal; } } switch (ref->type) { case VARREF_SCOPE_LOCALS: { HSQUIRRELVM vm = ref->GetThread(); int frame = ref->scope.frame; if (!IsValidStackFrame(vm, frame) || sq_type(vm->_callsstack[frame]._closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid stack frame {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", frame); DAP_SEND(); break; } const SQVM::CallInfo &ci = vm->_callsstack[frame]; int stackbase = GetStackBase(vm, &ci); SQClosure *pClosure = _closure(ci._closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci._ip - func->_instructions; DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); for (unsigned int i = 0; i < m_ReturnValues.Size(); i++) { const returnvalue_t &rv = m_ReturnValues[i]; wjson_table_t elem = variables.AppendTable(); { jstringbuf_t buf = elem.SetStringAsBuf("name"); if (!(m_iYieldValues & (1 << (i + 1)))) { buf.Puts("return@"); } else { buf.Puts("yield@"); } if (rv.funcname) { buf.Puts(rv.funcname); } else { buf.PutHex(rv.funcptr); } } JSONSetString(elem, "value", rv.value, flags); elem.SetString("type", GetType(rv.value)); elem.SetInt("variablesReference", ToVarRef(rv.value)); if (ShouldPageArray(rv.value, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(rv.value)->_values.size()); SetVirtualHint(elem); } for (int i = func->_nlocalvarinfos; i--;) { const SQLocalVarInfo &var = func->_localvarinfos[i]; Assert(sq_type(var._name) == OT_STRING); if (var._start_op <= ip && var._end_op + 1 >= ip) { const SQObjectPtr &val = vm->_stack._vals[stackbase + var._pos]; wjson_table_t elem = variables.AppendTable(); elem.SetString("name", _string(var._name)); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); { jstringbuf_t buf = elem.SetStringAsBuf("evaluateName"); buf.Put('@'); buf.Put('L'); buf.Put('@'); buf.PutInt((int)func->_nlocalvarinfos - i - 1); } elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); } } DAP_SEND(); break; } case VARREF_SCOPE_OUTERS: { HSQUIRRELVM vm = ref->GetThread(); int frame = ref->scope.frame; if (!IsValidStackFrame(vm, frame) || sq_type(vm->_callsstack[frame]._closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid stack frame {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", frame); DAP_SEND(); break; } const SQVM::CallInfo &ci = vm->_callsstack[frame]; SQClosure *pClosure = _closure(ci._closure); SQFunctionProto *func = _fp(pClosure->_function); DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; const SQObjectPtr &val = *_outervalptr(pClosure->_outervalues[i]); Assert(sq_type(var._name) == OT_STRING); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", _string(var._name)); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); } DAP_SEND(); break; } case VARREF_OBJ: { SQObject target = ref->GetVar(); if (!ISREFCOUNTED(sq_type(target))) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } const SQObjectPtr *dataWatchKey = NULL; if (_refcounted(target)->_weakref) { for (int i = m_DataWatches.Size(); i--;) { const datawatch_t &dw = m_DataWatches[i]; if (_refcounted(target)->_weakref == dw.container) { dataWatchKey = &dw.obj.key; break; } } } DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("refs")); if (sq_type(target) != OT_WEAKREF) { elem.SetString("value", GetValue(_refcounted(target)->_uiRef, flags)); } else { jstringbuf_t buf = elem.SetStringAsBuf("value"); buf.PutInt(_refcounted(target)->_uiRef); do { target = _weakref(target)->_obj; buf.Put(' '); buf.Put('>'); buf.Put((sq_type(target) != OT_WEAKREF) ? '*' : ' '); buf.PutInt(_refcounted(target)->_uiRef); } while (sq_type(target) == OT_WEAKREF); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (sq_type(target) == OT_ARRAY) { const SQObjectPtrVec &vals = _array(target)->_values; Assert(vals.size() <= INT_MAX); { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("allocated")); elem.SetString("value", GetValue(((SQObjectPtrVec &)vals).capacity(), flags)); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } int idx, end; string_t filter; if (arguments.GetString("filter", &filter) && filter.IsEqualTo("indexed")) { arguments.GetInt("start", &idx); arguments.GetInt("count", &end); if (idx < 0) idx = 0; if (end <= 0) { end = vals.size(); } else { end += idx; if (end > (int)vals.size()) end = vals.size(); } } else { idx = 0; end = vals.size(); } for (; idx < end; idx++) { const SQObjectPtr &val = vals[idx]; wjson_table_t elem = variables.AppendTable(); elem.SetIntBrackets("name", (SQInteger)idx, (flags & kFS_Hexadecimal) != 0); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); if (dataWatchKey && sq_type(*dataWatchKey) == OT_INTEGER && _integer(*dataWatchKey) == idx) { wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("hasDataBreakpoint"); } } // done with arrays } // delegates switch (sq_type(target)) { case OT_INSTANCE: { if (_instance(target)->_class) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("class")); JSONSetString(elem, "value", ToSQObject(_instance(target)->_class)); elem.SetInt("variablesReference", ToVarRef(ToSQObject(_instance(target)->_class))); SetVirtualHint(elem); } break; } case OT_CLASS: { if (_class(target)->_base) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("base")); JSONSetString(elem, "value", ToSQObject(_class(target)->_base)); elem.SetInt("variablesReference", ToVarRef(ToSQObject(_class(target)->_base))); SetVirtualHint(elem); } break; } case OT_TABLE: { if (_table(target)->_delegate) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("delegate")); JSONSetString(elem, "value", ToSQObject(_table(target)->_delegate)); elem.SetInt("variablesReference", ToVarRef(ToSQObject(_table(target)->_delegate))); SetVirtualHint(elem); } break; } default: break; } // metamethods if (sq_type(target) != OT_INSTANCE && HasMetaMethods(m_pRootVM, target)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("metamethods")); elem.SetString("value", "{...}"); elem.SetInt("variablesReference", ToVarRef(VARREF_METAMETHODS, target)); SetVirtualHint(elem); } bool shouldQuoteKeys; // members switch (sq_type(target)) { case OT_TABLE: { int keyflags = flags | kFS_NoQuote | kFS_KeyVal; Assert(_table(target)->CountUsed() <= INT_MAX); m_VarMemberCache.Ensure(_table(target)->CountUsed() * sizeof(SQObjectPtr)); vector keys(m_VarMemberCache); SortKeys(_table(target), &keys, &shouldQuoteKeys); ref->obj.hasNonStringMembers = shouldQuoteKeys; if (shouldQuoteKeys) keyflags &= ~kFS_NoQuote; for (unsigned int i = 0; i < keys.Size(); i++) { const SQObjectPtr &key = keys[i]; SQObjectPtr val; _table(target)->Get(key, val); wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); if (dataWatchKey && IsEqual(*dataWatchKey, key)) { wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("hasDataBreakpoint"); } } break; } case OT_CLASS: { int keyflags = flags | kFS_NoQuote | kFS_KeyVal; int nAttributes; Assert(_class(target)->_members); Assert(_class(target)->_members->CountUsed() <= INT_MAX); int nMemberCount = _class(target)->_members->CountUsed(); // Lazy way to ensure there is enough memory for values & methods m_VarMemberCache.Ensure(nMemberCount * 2 * sizeof(SQObjectPtr)); CMemory temp = m_VarMemberCache; temp.memory.ptr += nMemberCount * sizeof(SQObjectPtr); vector values(m_VarMemberCache), methods(temp); SortKeys(_class(target), &nAttributes, &values, &methods, &shouldQuoteKeys); ref->obj.hasNonStringMembers = shouldQuoteKeys; if (shouldQuoteKeys) keyflags &= ~kFS_NoQuote; if (sq_type(_class(target)->_attributes) != OT_NULL) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("attributes")); JSONSetString(elem, "value", _class(target)->_attributes, flags); elem.SetInt("variablesReference", ToVarRef(_class(target)->_attributes)); SetVirtualHint(elem); } else if (nAttributes) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("attributes")); elem.SetString("value", "{...}"); elem.SetInt("variablesReference", ToVarRef(VARREF_ATTRIBUTES, target)); SetVirtualHint(elem); } for (unsigned int i = 0; i < values.Size(); i++) { const SQObjectPtr &key = values[i]; SQObjectPtr val; _class(target)->Get(key, val); wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); } for (unsigned int i = 0; i < methods.Size(); i++) { const SQObjectPtr &key = methods[i]; SQObjectPtr val; _class(target)->Get(key, val); wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); } break; } case OT_INSTANCE: { int keyflags = flags | kFS_NoQuote | kFS_KeyVal; SQClass *base = _instance(target)->_class; Assert(base); Assert(base->_members); Assert(base->_members->CountUsed() <= INT_MAX); m_VarMemberCache.Ensure(base->_members->CountUsed() * sizeof(SQObjectPtr)); vector values(m_VarMemberCache); SortKeys(base, &values, &shouldQuoteKeys); ref->obj.hasNonStringMembers = shouldQuoteKeys; if (shouldQuoteKeys) keyflags &= ~kFS_NoQuote; // metamembers SQObjectPtr mm; const SQObjectPtr *def = GetClassDefMetaMembers(base); if (def && _instance(target)->GetMetaMethod(m_pRootVM, MT_GET, mm)) { for (unsigned int i = 0; i < _array(*def)->_values.size(); i++) { const SQObjectPtr &key = _array(*def)->_values[i]; SQObjectPtr val; if (RunClosure(mm, &target, key, val)) { wjson_table_t elem = variables.AppendTable(); JSONSetString(elem, "name", key, keyflags); // NOTE: val can be temporary, keep strong ref for inspection JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val, true)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); if (dataWatchKey && IsEqual(*dataWatchKey, key)) { wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("hasDataBreakpoint"); } } } } // user defined def = GetClassDefCustomMembers(base); if (def) { SQObjectPtr custommembers = *def; if (sq_type(custommembers) == OT_CLOSURE) RunClosure(custommembers, &target, custommembers); if (sq_type(custommembers) == OT_ARRAY) { objref_t tmp; SQObjectPtr strName = CreateSQString(m_pRootVM, _SC("name")); SQObjectPtr strGet = CreateSQString(m_pRootVM, _SC("get")); SQObjectPtr strSet = CreateSQString(m_pRootVM, _SC("set")); SQObjectPtr key, val; for (unsigned int i = 0; i < _array(custommembers)->_values.size(); i++) { const SQObjectPtr &memdef = _array(custommembers)->_values[i]; if (GetObj_Var(memdef, strName, tmp, key) && GetObj_Var(memdef, strGet, tmp, val) && CallCustomMembersGetFunc(val, &target, key, val)) { wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } // NOTE: val can be temporary, keep strong ref for inspection JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val, true)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); wjson_array_t attributes = hint.SetArray("attributes"); if (!GetObj_Var(memdef, strSet, tmp, val) || (sq_type(val) != OT_CLOSURE && sq_type(val) != OT_NATIVECLOSURE)) { attributes.Append("readOnly"); } if (dataWatchKey && IsEqual(*dataWatchKey, key)) { attributes.Append("hasDataBreakpoint"); } } } } } for (unsigned int i = 0; i < values.Size(); i++) { const SQObjectPtr &key = values[i]; SQObjectPtr val; _instance(target)->Get(key, val); wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); if (dataWatchKey && IsEqual(*dataWatchKey, key)) { wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("hasDataBreakpoint"); } } break; } case OT_CLOSURE: #ifdef ACCESSIBLE_FUNCPROTO case OT_FUNCPROTO: #endif { #ifdef ACCESSIBLE_FUNCPROTO SQFunctionProto *func = (sq_type(target) == OT_CLOSURE) ? _fp(_closure(target)->_function) : _funcproto(target); #else SQFunctionProto *func = _fp(_closure(target)->_function); #endif Assert(func->_ninstructions <= INT_MAX); Assert(func->GetLine(&func->_instructions[func->_ninstructions - 1]) <= INT_MAX); Assert(func->_nliterals <= INT_MAX); Assert(func->_noutervalues <= INT_MAX); Assert(func->_nlocalvarinfos <= INT_MAX); Assert(func->_nlineinfos <= INT_MAX); if (sq_type(func->_name) == OT_STRING) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("name")); elem.SetString("value", _string(func->_name)); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (sq_type(func->_sourcename) == OT_STRING) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("source")); int line = GetFunctionDeclarationLine(func); if (line) { jstringbuf_t buf = elem.SetStringAsBuf("value"); buf.Puts(_string(func->_sourcename)); buf.Put(':'); buf.PutInt(line); } else { elem.SetString("value", _string(func->_sourcename)); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (func->_bgenerator) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("generator")); elem.SetString("value", "1"); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } int nparams = func->_nparameters; #if SQUIRREL_VERSION_NUMBER >= 300 if (nparams > 1) #else if (nparams > 1 || func->_varparams) #endif { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("parameters")); if (!func->_varparams) { elem.SetString("value", GetValue(nparams, flags)); } else { #if SQUIRREL_VERSION_NUMBER >= 300 nparams--; #endif jstringbuf_t buf = elem.SetStringAsBuf("value"); if (!(flags & kFS_Hexadecimal)) { buf.PutInt(nparams); } else { buf.PutHex((unsigned int)nparams, false); } buf.Puts("..."); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("stacksize")); elem.SetString("value", GetValue(func->_stacksize, flags)); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } { RestoreCachedInstructions(); // ignore line ops int c = func->_ninstructions; for (int i = c; i--;) if (func->_instructions[i].op == _OP_LINE) c--; UndoRestoreCachedInstructions(); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("instructions")); elem.SetString("value", GetValue(c, flags)); elem.SetInt("variablesReference", ToVarRef(VARREF_INSTRUCTIONS, ToSQObject(func))); SetVirtualHint(elem); } if (func->_nliterals) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("literals")); elem.SetString("value", GetValue(func->_nliterals, flags)); elem.SetInt("variablesReference", ToVarRef(VARREF_LITERALS, ToSQObject(func))); SetVirtualHint(elem); } #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST if (func->_nfunctions) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("functions")); elem.SetString("value", GetValue(func->_nfunctions, flags)); elem.SetInt("variablesReference", ToVarRef(VARREF_FUNCTIONS, ToSQObject(func))); SetVirtualHint(elem); } #endif if (func->_noutervalues) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("outervalues")); elem.SetString("value", GetValue(func->_noutervalues, flags)); #ifdef ACCESSIBLE_FUNCPROTO int v = (sq_type(target) == OT_CLOSURE) ? ToVarRef(VARREF_OUTERS, target) : -1; elem.SetInt("variablesReference", v); #else Assert(sq_type(target) == OT_CLOSURE); elem.SetInt("variablesReference", ToVarRef(VARREF_OUTERS, target)); #endif SetVirtualHint(elem); } #ifdef ACCESSIBLE_FUNCPROTO if (sq_type(target) == OT_CLOSURE) #endif { #ifdef CLOSURE_ENV_ISVALID if (CLOSURE_ENV_ISVALID(_closure(target)->_env)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("env")); JSONSetString(elem, "value", CLOSURE_ENV_OBJ(_closure(target)->_env)); elem.SetInt("variablesReference", ToVarRef( CLOSURE_ENV_OBJ(_closure(target)->_env))); SetVirtualHint(elem); } #endif #ifdef CLOSURE_ROOT if (_closure(target)->_root && _table(_closure(target)->_root->_obj) != _table(m_pRootVM->_roottable)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("root")); JSONSetString(elem, "value", _closure(target)->_root->_obj); elem.SetInt("variablesReference", ToVarRef(_closure(target)->_root->_obj)); SetVirtualHint(elem); } #endif } break; } case OT_NATIVECLOSURE: { if (sq_type(_nativeclosure(target)->_name) == OT_STRING) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("name")); elem.SetString("value", _string(_nativeclosure(target)->_name)); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } Assert(_nativeclosure(target)->_nparamscheck >= 0 || -_nativeclosure(target)->_nparamscheck <= INT_MAX); int nparams = _nativeclosure(target)->_nparamscheck; if (nparams != 0 && // if only parameter has no type check, ignore !(nparams == 1 && _nativeclosure(target)->_typecheck.size() == 0)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("parameters")); { jstringbuf_t buf = elem.SetStringAsBuf("value"); if (!(flags & kFS_Hexadecimal)) { buf.PutInt(nparams < 0 ? -nparams : nparams); } else { buf.PutHex((unsigned int)(nparams < 0 ? -nparams : nparams), false); } if (nparams < 0) buf.Puts("..."); if (_nativeclosure(target)->_typecheck.size()) { buf.Put(' '); buf.Put('('); for (int i = 0; i < (int)_nativeclosure(target)->_typecheck.size(); i++) { int mask = _nativeclosure(target)->_typecheck[i]; Assert(mask); if (mask == -1) { buf.Put('.'); buf.Put(','); buf.Put(' '); continue; } #define _check(t, c) \ if (mask & (t)) \ { \ buf.Put((c)); \ buf.Put('|'); \ } #define _check_match(t, c) \ if ((mask & (t)) == (t)) \ { \ buf.Put((c)); \ buf.Put('|'); \ } _check(_RT_NULL, 'o') _check_match(_RT_INTEGER | _RT_FLOAT, 'n') else { _check(_RT_INTEGER, 'i') else _check(_RT_FLOAT, 'f')} _check(_RT_BOOL, 'b') _check(_RT_STRING, 's') _check(_RT_CLOSURE | _RT_NATIVECLOSURE, 'c') _check(_RT_TABLE, 't') _check(_RT_ARRAY, 'a') _check(_RT_INSTANCE, 'x') _check(_RT_CLASS, 'y') _check(_RT_USERDATA, 'u') _check(_RT_USERPOINTER, 'p') _check(_RT_GENERATOR, 'g') _check(_RT_THREAD, 'v') _check(_RT_WEAKREF, 'r') #undef _check #undef _check_match buf.Seek(-1); buf.Put(','); buf.Put(' '); } buf.Seek(-2); buf.Put(')'); } } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (_nativenoutervalues(_nativeclosure(target))) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("outervalues")); elem.SetString("value", GetValue( _nativenoutervalues(_nativeclosure(target)), flags)); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } #ifdef CLOSURE_ENV_ISVALID if (CLOSURE_ENV_ISVALID(_nativeclosure(target)->_env)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("env")); JSONSetString(elem, "value", CLOSURE_ENV_OBJ(_nativeclosure(target)->_env)); elem.SetInt("variablesReference", ToVarRef( CLOSURE_ENV_OBJ(_nativeclosure(target)->_env))); SetVirtualHint(elem); } #endif break; } case OT_THREAD: { Assert(_thread(_ss(_thread(target))->_root_vm) == m_pRootVM); { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("state")); switch (sq_getvmstate(_thread(target))) { case SQ_VMSTATE_IDLE: elem.SetString("value", "idle"); break; case SQ_VMSTATE_RUNNING: elem.SetString("value", "running"); break; case SQ_VMSTATE_SUSPENDED: elem.SetString("value", "suspended"); break; default: UNREACHABLE(); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (_table(_thread(target)->_roottable) != _table(m_pRootVM->_roottable)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("root")); JSONSetString(elem, "value", _thread(target)->_roottable); elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (_thread(target) != m_pRootVM) { const SQObjectPtr &val = _thread(target)->_stack._vals[0]; Assert(sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("function")); JSONSetString(elem, "value", val); elem.SetInt("variablesReference", ToVarRef(val)); SetVirtualHint(elem); } if (_thread(target)->_callsstacksize != 0) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("callstack")); elem.SetString("value", GetValue(_thread(target)->_callsstacksize)); elem.SetInt("variablesReference", ToVarRef(VARREF_CALLSTACK, target)); SetVirtualHint(elem); } break; } case OT_GENERATOR: { { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("state")); switch (_generator(target)->_state) { case SQGenerator::eSuspended: elem.SetString("value", "suspended"); break; case SQGenerator::eRunning: elem.SetString("value", "running"); break; case SQGenerator::eDead: elem.SetString("value", "dead"); break; default: UNREACHABLE(); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (_generator(target)->_state != SQGenerator::eDead) { const SQVM::CallInfo &ci = _generator(target)->_ci; Assert(sq_type(ci._closure) == OT_CLOSURE); SQFunctionProto *func = _fp(_closure(ci._closure)->_function); sqstring_t source = sq_type(func->_sourcename) == OT_STRING ? sqstring_t(_string(func->_sourcename)) : sqstring_t(_SC("??")); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("frame")); { jstringbuf_t buf = elem.SetStringAsBuf("value"); buf.Puts(source); buf.Put(':'); buf.PutInt((int)func->GetLine(ci._ip)); } elem.SetInt("variablesReference", -1); SetVirtualHint(elem); } if (sq_type(_generator(target)->_closure) != OT_NULL) { const SQObjectPtr &val = _generator(target)->_closure; Assert(sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", INTERNAL_TAG("function")); JSONSetString(elem, "value", val); elem.SetInt("variablesReference", ToVarRef(val)); SetVirtualHint(elem); } break; } case OT_STRING: case OT_ARRAY: case OT_WEAKREF: case OT_USERDATA: break; default: Assert(!"unknown type"); } DAP_SEND(); break; } case VARREF_INSTRUCTIONS: { SQObject target = ref->GetVar(); if (sq_type(target) != OT_FUNCPROTO) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } RestoreCachedInstructions(); SQFunctionProto *func = _funcproto(target); int ninstructions = func->_ninstructions; DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); // ignore line ops int lines = 0; for (int i = 0; i < ninstructions; i++) { SQInstruction *instr = func->_instructions + i; if (instr->op == _OP_LINE) { lines++; continue; } wjson_table_t elem = variables.AppendTable(); { // "0xFF -2147483648 255 255 255" jstringbuf_t instrBytes = elem.SetStringAsBuf("value"); instrBytes.PutHex(instr->op); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg0); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg1); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg2); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg3); } { // index:line jstringbuf_t name = elem.SetStringAsBuf("name"); name.PutInt(i - lines); name.Put(':'); name.PutInt((int)func->GetLine(instr)); } elem.SetInt("variablesReference", -1); #ifndef SQDBG_SUPPORTS_SET_INSTRUCTION wjson_table_t hint = elem.SetTable("presentationHint"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); #endif } DAP_SEND(); UndoRestoreCachedInstructions(); break; } case VARREF_OUTERS: { SQObject target = ref->GetVar(); if (sq_type(target) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } SQClosure *pClosure = _closure(target); SQFunctionProto *func = _fp(pClosure->_function); DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; const SQObjectPtr &val = *_outervalptr(pClosure->_outervalues[i]); Assert(sq_type(var._name) == OT_STRING); wjson_table_t elem = variables.AppendTable(); elem.SetString("name", _string(var._name)); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); } DAP_SEND(); break; } case VARREF_LITERALS: { SQObject target = ref->GetVar(); if (sq_type(target) != OT_FUNCPROTO) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } SQFunctionProto *func = _funcproto(target); DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); for (int i = 0; i < func->_nliterals; i++) { const SQObjectPtr &val = func->_literals[i]; wjson_table_t elem = variables.AppendTable(); elem.SetIntString("name", i); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); } DAP_SEND(); break; } #ifdef SQDBG_SUPPORTS_FUNCPROTO_LIST case VARREF_FUNCTIONS: { SQObject target = ref->GetVar(); if (sq_type(target) != OT_FUNCPROTO) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } SQFunctionProto *func = _funcproto(target); DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); for (int i = 0; i < func->_nfunctions; i++) { const SQObjectPtr &val = func->_functions[i]; wjson_table_t elem = variables.AppendTable(); elem.SetIntString("name", i); JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); } DAP_SEND(); break; } #endif case VARREF_METAMETHODS: { SQObject target = ref->GetVar(); if (!ISREFCOUNTED(sq_type(target))) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); switch (sq_type(target)) { case OT_INSTANCE: _class(target) = _instance(target)->_class; case OT_CLASS: { for (unsigned int i = 0; i < MT_LAST; i++) { const SQObjectPtr &val = _class(target)->_metamethods[i]; if (sq_type(val) != OT_NULL) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", g_MetaMethodName[i]); JSONSetString(elem, "value", val); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); } } break; } default: { Assert(is_delegable(target) && _delegable(target)->_delegate); if (is_delegable(target) && _delegable(target)->_delegate) { SQObjectPtr val; for (unsigned int i = 0; i < MT_LAST; i++) { if (_delegable(target)->GetMetaMethod(m_pRootVM, (SQMetaMethod)i, val)) { wjson_table_t elem = variables.AppendTable(); elem.SetString("name", g_MetaMethodName[i]); JSONSetString(elem, "value", val); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); } } } break; } } DAP_SEND(); break; } case VARREF_ATTRIBUTES: { SQObject target = ref->GetVar(); bool shouldQuoteKeys = ref->obj.hasNonStringMembers; if (sq_type(target) != OT_CLASS) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } int keyflags = flags | kFS_NoQuote | kFS_KeyVal; if (shouldQuoteKeys) keyflags &= ~kFS_NoQuote; DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); SQObjectPtr key, idx; FOREACH_SQTABLE(_class(target)->_members, key, idx) { const SQObjectPtr &val = _isfield(idx) ? _class(target)->_defaultvalues[_member_idx(idx)].attrs : _class(target)->_methods[_member_idx(idx)].attrs; if (sq_type(val) != OT_NULL) { wjson_table_t elem = variables.AppendTable(); if (shouldQuoteKeys && sq_type(key) == OT_INTEGER) { elem.SetIntBrackets("name", _integer(key), (keyflags & kFS_Hexadecimal) != 0); } else { JSONSetString(elem, "name", key, keyflags); } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); wjson_table_t hint = elem.SetTable("presentationHint"); hint.SetString("kind", GetPresentationHintKind(val)); } } DAP_SEND(); break; } case VARREF_CALLSTACK: { SQObject target = ref->GetVar(); if (sq_type(target) != OT_THREAD) { DAP_ERROR_RESPONSE(seq, "variables"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); int i = _thread(target)->_callsstacksize; wjson_array_t variables = body.SetArray("variables"); while (i--) { const SQVM::CallInfo &ci = _thread(target)->_callsstack[i]; if (ShouldIgnoreStackFrame(_thread(target), ci)) continue; wjson_table_t elem = variables.AppendTable(); elem.SetIntBrackets("name", i); { jstringbuf_t buf = elem.SetStringAsBuf("value"); if (sq_type(ci._closure) == OT_CLOSURE) { SQFunctionProto *func = _fp(_closure(ci._closure)->_function); if (sq_type(func->_sourcename) == OT_STRING) { buf.Puts(_string(func->_sourcename)); } else { buf.Puts("??"); } buf.Put(':'); buf.PutInt((int)func->GetLine(ci._ip)); } else if (sq_type(ci._closure) == OT_NATIVECLOSURE) { buf.Puts("NATIVE"); } else UNREACHABLE(); } elem.SetInt("variablesReference", ToVarRef(ci._closure)); wjson_table_t hint = elem.SetTable("presentationHint"); wjson_array_t attributes = hint.SetArray("attributes"); attributes.Append("readOnly"); } DAP_SEND(); break; } case VARREF_STACK: { HSQUIRRELVM vm = ref->GetThread(); int frame = ref->scope.frame; if (!IsValidStackFrame(vm, frame)) frame = INVALID_FRAME; DAP_START_RESPONSE(seq, "variables"); DAP_SET_TABLE(body); wjson_array_t variables = body.SetArray("variables"); int stackbase = 0; int callframe = -1; int next = vm->_callsstacksize ? vm->_callsstack[0]._prevstkbase : 0; int idx = 0; const int max = vm->_stack.size(); for (;;) { callframe++; if (callframe < vm->_callsstacksize) { stackbase = next; if (callframe + 1 < vm->_callsstacksize) { next = stackbase + vm->_callsstack[callframe + 1]._prevstkbase; } else { next = max; } } else { if (callframe) callframe--; next = max; } for (; idx < next; idx++) { const SQObjectPtr &val = vm->_stack._vals[idx]; if (idx > vm->_top && sq_type(val) == OT_NULL) continue; wjson_table_t elem = variables.AppendTable(); { jstringbuf_t name = elem.SetStringAsBuf("name"); name.Put('['); if (!(flags & kFS_Hexadecimal)) { name.PutInt(callframe); } else { name.PutHex((unsigned int)callframe, false); } name.Put(']'); name.Put('['); if (!(flags & kFS_Hexadecimal)) { name.PutInt(idx - stackbase); } else { name.PutHex((unsigned int)(idx - stackbase), false); } name.Put(']'); if (idx == stackbase) { name.Put('*'); if (callframe == frame) { name.Put('~'); } #ifndef NATIVE_DEBUG_HOOK else if (m_State == ThreadState_Suspended && callframe == vm->_callsstacksize - 1) { if (sq_type(vm->_callsstack[callframe]._closure) == OT_NATIVECLOSURE && _nativeclosure(vm->_callsstack[callframe]._closure)->_function == &SQDebugServer::SQDebugHook) { name.Put('d'); } } #endif } else if (vm->_top == idx) { name.Put('_'); } } JSONSetString(elem, "value", val, flags); elem.SetString("type", GetType(val)); elem.SetInt("variablesReference", ToVarRef(val)); if (ShouldPageArray(val, 16 * ARRAY_PAGE_LIMIT)) elem.SetInt("indexedVariables", (int)_array(val)->_values.size()); } if (next == max) break; } DAP_SEND(); break; } default: UNREACHABLE(); } } // // If the client supports SetExpression and the target variable has "evaluateName", // it will send target "evaluateName" and stack frame to SetExpression. // Client can choose to send variable "name" to SetExpression for watch variables. // If the client doesn't support SetExpression or the target variable does not have "evaluateName", // it will send target "name" and container "variableReference" to SetVariable. // // SetExpression needs to parse watch flags and "evaluateName", // SetVariable only gets identifiers. // void SQDebugServer::OnRequest_SetVariable(const json_table_t &arguments, int seq) { int variablesReference; string_t strName, strValue; arguments.GetInt("variablesReference", &variablesReference); arguments.GetString("name", &strName); arguments.GetString("value", &strValue); bool hex = false; json_table_t *format; if (arguments.GetTable("format", &format)) format->GetBool("hex", &hex); varref_t *ref = FromVarRef(variablesReference); if (!ref) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "failed to find variable"); DAP_SEND(); return; } HSQUIRRELVM vm; int frame; if (IsScopeRef(ref->type)) { vm = ref->GetThread(); frame = ref->scope.frame; } else { vm = m_pCurVM; frame = INVALID_FRAME; } // Requires special value parsing switch (ref->type) { case VARREF_INSTRUCTIONS: { #ifdef SQDBG_SUPPORTS_SET_INSTRUCTION OnRequest_SetVariable_Instruction(ref, strName, strValue, seq); #else DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, ""); DAP_SEND(); #endif return; } default: break; } if (strName.IsEmpty() || strValue.IsEmpty()) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "empty expression"); DAP_SEND(); return; } objref_t obj; SQObjectPtr value, dummy; if (!GetObj_VarRef(ref, strName, obj, dummy)) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "identifier '{name}' not found"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); // If the string is escapable, it was undone if (ref->type == VARREF_OBJ && ref->obj.hasNonStringMembers && strName.ptr[-1] == '\"') { // there is enough space to re-escape Escape(strName.ptr, &strName.len, strName.len * (sizeof(SQChar) * 2 + 2)); } variables.SetString("name", strName); DAP_SEND(); return; } #ifndef SQDBG_DISABLE_COMPILER ECompileReturnCode cres = Evaluate(strValue, vm, frame, value); if (cres != CompileReturnCode_Success && !(cres > CompileReturnCode_Fallback && RunExpression(strValue, vm, frame, value))) #else if (!RunExpression(strValue, vm, frame, value)) #endif { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "failed to evaluate value '{name}'\\n[{reason}]"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("name", strValue); JSONSetString(variables, "reason", vm->_lasterror, kFS_NoQuote); DAP_SEND(); return; } if (!Set(obj, value)) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "could not set '{name}'"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("name", strName); DAP_SEND(); return; } // Update data watch if (IsObjectRef(ref->type) && _refcounted(ref->GetVar())->_weakref) { for (int i = m_DataWatches.Size(); i--;) { datawatch_t &dw = m_DataWatches[i]; if (_refcounted(ref->GetVar())->_weakref == dw.container && dw.name.IsEqualTo(strName)) { dw.oldvalue = value; break; } } } DAP_START_RESPONSE(seq, "setVariable"); DAP_SET_TABLE(body); JSONSetString(body, "value", value, hex ? kFS_Hexadecimal : 0); body.SetString("type", GetType(value)); body.SetInt("variablesReference", ToVarRef(value)); if (ShouldPageArray(value, 16 * ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(value)->_values.size()); DAP_SEND(); } #ifdef SQDBG_SUPPORTS_SET_INSTRUCTION void SQDebugServer::OnRequest_SetVariable_Instruction(const varref_t *ref, string_t &strName, string_t &strValue, int seq) { if (sq_type(ref->GetVar()) != OT_FUNCPROTO) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "invalid object"); DAP_SEND(); return; } int index, line; int op, arg0, arg1, arg2, arg3; int c1 = sscanf(strName.ptr, "%d:%d", &index, &line); int c2 = sscanf(strValue.ptr, "0x%02x %d %d %d %d", &op, &arg0, &arg1, &arg2, &arg3); bool fail = (c1 != 2); if (!fail && c2 != 5) { // Check for floats if (strchr(strValue.ptr, '.')) { float farg1; c2 = sscanf(strValue.ptr, "0x%02x %d %f %d %d", &op, &arg0, &farg1, &arg2, &arg3); if (c2 != 5) { fail = true; } else { arg1 = *(int *)&farg1; if (op != _OP_LOADFLOAT) { char buf[96]; unsigned int len = snprintf(buf, sizeof(buf), "Warning: Setting float value (%.2f) to non-float instruction\n", farg1); SendEvent_OutputStdOut(string_t(buf, min(len, sizeof(buf))), NULL); } } } else { fail = true; } } if (fail) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "invalid amount of parameters in input"); error.SetBool("showUser", true); DAP_SEND(); return; } RestoreCachedInstructions(); SQFunctionProto *func = _funcproto(ref->GetVar()); // line ops are ignored in the index for (int c = 0; c < func->_ninstructions; c++) { if (func->_instructions[c].op == _OP_LINE) index++; if (c == index) break; } UndoRestoreCachedInstructions(); // index will be wrong if user manualy set line ops if (index >= func->_ninstructions) { DAP_ERROR_RESPONSE(seq, "setVariable"); DAP_ERROR_BODY(0, "failed to set instruction"); error.SetBool("showUser", true); DAP_SEND(); return; } SQInstruction *instr = func->_instructions + index; instr->op = op & 0xff; instr->_arg0 = arg0 & 0xff; instr->_arg1 = arg1; instr->_arg2 = arg2 & 0xff; instr->_arg3 = arg3 & 0xff; DAP_START_RESPONSE(seq, "setVariable"); DAP_SET_TABLE(body); { jstringbuf_t instrBytes = body.SetStringAsBuf("value"); instrBytes.PutHex(instr->op); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg0); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg1); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg2); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg3); } body.SetInt("variablesReference", -1); DAP_SEND(); } #endif void SQDebugServer::OnRequest_SetExpression(const json_table_t &arguments, int seq) { HSQUIRRELVM vm; int frame; string_t expression, strValue; arguments.GetString("expression", &expression); arguments.GetString("value", &strValue); arguments.GetInt("frameId", &frame, -1); if (expression.IsEmpty() || strValue.IsEmpty()) { DAP_ERROR_RESPONSE(seq, "evaluate"); DAP_ERROR_BODY(0, "empty expression"); DAP_SEND(); return; } if (!TranslateFrameID(frame, &vm, &frame)) { vm = m_pCurVM; frame = INVALID_FRAME; } int flags = ParseFormatSpecifiers(expression); { json_table_t *format; if (arguments.GetTable("format", &format)) { bool hex; format->GetBool("hex", &hex); if (hex) flags |= kFS_Hexadecimal; } } SQObjectPtr value; // Evaluate value in current stack frame even if the expression has frame lock #ifndef SQDBG_DISABLE_COMPILER ECompileReturnCode cres = Evaluate(strValue, vm, frame, value); if (cres != CompileReturnCode_Success && !(cres > CompileReturnCode_Fallback && RunExpression(strValue, vm, frame, value))) #else if (!((flags & kFS_Binary) && ParseBinaryNumber(strValue, value)) && !RunExpression(strValue, vm, frame, value)) #endif { DAP_ERROR_RESPONSE(seq, "setExpression"); DAP_ERROR_BODY(0, "failed to evaluate value '{name}'"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("name", strValue); DAP_SEND(); return; } if (flags & kFS_Lock) { #ifdef _DEBUG bool foundWatch = false; #endif for (unsigned int i = 0; i < m_LockedWatches.Size(); i++) { const watch_t &w = m_LockedWatches[i]; if (w.expression.IsEqualTo(expression)) { vm = GetThread(w.thread); frame = w.frame; #ifdef _DEBUG foundWatch = true; #endif break; } } Assert(foundWatch); } objref_t obj; obj.type = objref_t::INVALID; // Try to get identifier if (ShouldParseEvaluateName(expression)) { SQObjectPtr dummy; if (!ParseEvaluateName(expression, vm, frame, obj, dummy)) { DAP_ERROR_RESPONSE(seq, "setExpression"); DAP_ERROR_BODY(0, "invalid variable reference '{name}'"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("name", expression); DAP_SEND(); return; } } else { SQObjectPtr dummy; GetObj_Frame(vm, frame, expression, obj, dummy); } // Found identifier if (obj.type != objref_t::INVALID) { if (Set(obj, value)) { DAP_START_RESPONSE(seq, "setExpression"); DAP_SET_TABLE(body); JSONSetString(body, "value", value, flags); body.SetString("type", GetType(value)); body.SetInt("variablesReference", ToVarRef(value)); if (ShouldPageArray(value, 16 * ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(value)->_values.size()); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "setExpression"); DAP_ERROR_BODY(0, "could not set '{name}'"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); JSONSetString(variables, "name", obj.key); DAP_SEND(); return; } } // No identifier, run the script ( exp = val ) else { int len = expression.len + 1; #ifndef SQDBG_DISABLE_COMPILER len += strValue.len; #else // Using sq compiler // If value was binary literal, put int if (!(flags & kFS_Binary)) { len += strValue.len; } else { len += 2 + countdigits<16>((SQUnsignedInteger)_integer(value)); } #endif stringbufext_t buf = ScratchPadBuf(len + 1); buf.Puts(expression); buf.Put('='); #ifndef SQDBG_DISABLE_COMPILER buf.Puts(strValue); #else if (!(flags & kFS_Binary)) { buf.Puts(strValue); } else { buf.PutHex((SQUnsignedInteger)_integer(value), false); } #endif buf.Term(); #ifndef SQDBG_DISABLE_COMPILER string_t expr; expr.Assign(buf.ptr, buf.len); cres = Evaluate(expr, vm, frame, value); if (cres == CompileReturnCode_Success || (cres > CompileReturnCode_Fallback && RunExpression(buf, vm, frame, value))) #else if (RunExpression(buf, vm, frame, value)) #endif { DAP_START_RESPONSE(seq, "setExpression"); DAP_SET_TABLE(body); JSONSetString(body, "value", value, flags); body.SetString("type", GetType(value)); body.SetInt("variablesReference", ToVarRef(value)); if (ShouldPageArray(value, 16 * ARRAY_PAGE_LIMIT)) body.SetInt("indexedVariables", (int)_array(value)->_values.size()); DAP_SEND(); } else { DAP_ERROR_RESPONSE(seq, "setExpression"); DAP_ERROR_BODY(0, "failed to evaluate expression: {exp} = {val}\\n[{reason}]"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("exp", expression); variables.SetString("val", strValue); JSONSetString(variables, "reason", vm->_lasterror, kFS_NoQuote); DAP_SEND(); return; } } // Update data watch for (int i = m_DataWatches.Size(); i--;) { datawatch_t &dw = m_DataWatches[i]; if (dw.container && sq_type(dw.container->_obj) == OT_NULL) { FreeDataWatch(dw); m_DataWatches.Remove(i); continue; } if (Get(dw.obj, value) && _rawval(dw.oldvalue) != _rawval(value)) { dw.oldvalue = value; } } } void SQDebugServer::OnRequest_Disassemble(const json_table_t &arguments, int seq) { string_t memoryReference; int instructionOffset, instructionCount; arguments.GetString("memoryReference", &memoryReference); arguments.GetInt("instructionOffset", &instructionOffset); arguments.GetInt("instructionCount", &instructionCount); SQFunctionProto *func = NULL; int instrIdx = -1; SQInstruction *ip; atox(memoryReference, (uintptr_t *)&ip); for (int i = m_pCurVM->_callsstacksize; i--;) { const SQVM::CallInfo &ci = m_pCurVM->_callsstack[i]; if (sq_type(ci._closure) == OT_CLOSURE) { func = _fp(_closure(ci._closure)->_function); if (ip >= func->_instructions && ip < func->_instructions + func->_ninstructions) { instrIdx = ci._ip - func->_instructions; break; } } } if (instrIdx == -1) { DAP_ERROR_RESPONSE(seq, "disassemble"); DAP_ERROR_BODY(0, "invalid instruction pointer"); DAP_SEND(); return; } RestoreCachedInstructions(); int targetStart = instrIdx + instructionOffset; int targetEnd = targetStart + instructionCount; int validStart = max(0, targetStart); int validEnd = min((int)func->_ninstructions - 1, targetEnd); DAP_START_RESPONSE(seq, "disassemble"); DAP_SET_TABLE(body); wjson_array_t instructions = body.SetArray("instructions"); for (int index = targetStart; index < targetEnd; index++) { wjson_table_t elem = instructions.AppendTable(); SQInstruction *instr = func->_instructions + index; { jstringbuf_t addr = elem.SetStringAsBuf("address"); addr.PutHex((uintptr_t)instr); } if (index >= validStart && index <= validEnd) { if (instr->op != _OP_LINE) { { stringbuf_t<128> instrStr; DescribeInstruction(instr, func, instrStr); elem.SetString("instruction", instrStr); } { jstringbuf_t instrBytes = elem.SetStringAsBuf("instructionBytes"); instrBytes.PutHex(instr->op); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg0); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg1); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg2); instrBytes.Put(' '); instrBytes.PutInt(instr->_arg3); } elem.SetInt("line", (int)func->GetLine(instr)); } else { elem.SetString("instruction", ""); } elem.SetString("presentationHint", "normal"); } else { elem.SetString("instruction", "??"); elem.SetString("presentationHint", "invalid"); } if (index == targetStart) { wjson_table_t source = elem.SetTable("location"); SetSource(source, _string(func->_sourcename)); } } Assert(instructions.Size() == instructionCount); DAP_SEND(); UndoRestoreCachedInstructions(); } #ifdef SUPPORTS_RESTART_FRAME void SQDebugServer::OnRequest_RestartFrame(const json_table_t &arguments, int seq) { AssertClient(m_State != ThreadState_Running); HSQUIRRELVM vm; int frame; arguments.GetInt("frameId", &frame, -1); if (!TranslateFrameID(frame, &vm, &frame) || !vm->ci) { DAP_ERROR_RESPONSE(seq, "restartFrame"); DAP_ERROR_BODY(0, "invalid stack frame {id}"); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("id", frame); DAP_SEND(); return; } const SQVM::CallInfo *pCurrent = vm->ci; const SQVM::CallInfo *pTarget = vm->_callsstack + frame; for (const SQVM::CallInfo *f = pCurrent; f >= pTarget; f--) { if (sq_type(f->_closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "restartFrame"); DAP_ERROR_BODY(0, "cannot restart native call frame"); DAP_SEND(); return; } } while (pCurrent > pTarget) { vm->LeaveFrame(); pCurrent = vm->ci; } Assert(sq_type(vm->ci->_closure) == OT_CLOSURE); SQFunctionProto *func = _fp(_closure(vm->ci->_closure)->_function); vm->ci->_ip = func->_instructions; int top = vm->_top; int target = top - func->_stacksize + func->_nparameters; while (top-- > target) { vm->_stack._vals[top].Null(); } DAP_START_RESPONSE(seq, "restartFrame"); DAP_SEND(); Break(vm, breakreason_t::Restart); } #endif static inline int GetOpAtLine(SQFunctionProto *func, int line) { for (int i = 0; i < (int)func->_nlineinfos; i++) { const SQLineInfo &li = func->_lineinfos[i]; if (line <= (int)li._line) return li._op; } return -1; } static inline int GetOpAtNextLine(SQFunctionProto *func, int curop) { Assert(func->_nlineinfos > 0); int hi = func->_nlineinfos - 1; int lo = 0; int mid = 0; while (lo <= hi) { mid = lo + ((hi - lo) >> 1); int op = func->_lineinfos[mid]._op; if (curop > op) { lo = mid + 1; } else if (curop < op) { hi = mid - 1; } else { break; } } while (mid > 0 && func->_lineinfos[mid]._op > curop) mid--; while (++mid < (int)func->_nlineinfos) { int op = func->_lineinfos[mid]._op; if (op != 0 && op != curop) return op; } return -1; } // // Only allow goto if the requested source,line matches current executing function // void SQDebugServer::OnRequest_GotoTargets(const json_table_t &arguments, int seq) { if (m_State != ThreadState_Suspended) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } json_table_t *source; string_t srcname; int line; arguments.GetInt("line", &line); GET_OR_ERROR_RESPONSE("gotoTargets", arguments, source); if ((!source->GetString("name", &srcname) || srcname.IsEmpty()) && source->GetString("path", &srcname)) { StripFileName(&srcname.ptr, &srcname.len); } if (!m_pCurVM->ci) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK const SQVM::CallInfo *ci = m_pCurVM->ci; #else const SQVM::CallInfo *ci = m_pCurVM->ci - 1; #endif if (sq_type(ci->_closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "goto on native call frame"); DAP_SEND(); return; } SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); if (func->_nlineinfos == 0) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "no lineinfos"); DAP_SEND(); return; } if (sq_type(func->_sourcename) == OT_STRING) { sqstring_t wfuncsrc; wfuncsrc.Assign(_string(func->_sourcename)); #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&wfuncsrc.ptr, &wfuncsrc.len); #endif if (!srcname.IsEqualTo(wfuncsrc)) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "requested source '{src1}' does not match executing function '{src2}'"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetString("src1", srcname); variables.SetString("src2", wfuncsrc); DAP_SEND(); return; } } if (line < 0 || line > (int)func->_lineinfos[func->_nlineinfos - 1]._line) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "line {line} is out of range"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("line", line); DAP_SEND(); return; } int op = GetOpAtLine(func, line); if (op == -1) { DAP_ERROR_RESPONSE(seq, "gotoTargets"); DAP_ERROR_BODY(0, "could not find line {line} in function"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("line", line); DAP_SEND(); return; } DAP_START_RESPONSE(seq, "gotoTargets"); DAP_SET_TABLE(body); wjson_array_t targets = body.SetArray("targets"); wjson_table_t elem = targets.AppendTable(); { jstringbuf_t label = elem.SetStringAsBuf("label"); label.Put('L'); label.PutInt(line); } elem.SetInt("line", line); elem.SetInt("id", line); DAP_SEND(); } void SQDebugServer::OnRequest_Goto(const json_table_t &arguments, int seq) { if (m_State != ThreadState_Suspended) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } int threadId, line; arguments.GetInt("threadId", &threadId, -1); arguments.GetInt("targetId", &line); HSQUIRRELVM vm = ThreadFromID(threadId); if (!vm->ci) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK SQVM::CallInfo *ci = vm->ci; #else SQVM::CallInfo *ci = vm->ci - 1; #endif if (sq_type(ci->_closure) != OT_CLOSURE) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "goto on native call frame"); DAP_SEND(); return; } SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); if (func->_nlineinfos == 0) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "no lineinfos"); DAP_SEND(); return; } if (line < 0 || line > (int)func->_lineinfos[func->_nlineinfos - 1]._line) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "line {line} is out of range"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("line", line); DAP_SEND(); return; } int op = GetOpAtLine(func, line); if (op == -1) { DAP_ERROR_RESPONSE(seq, "goto"); DAP_ERROR_BODY(0, "could not find line {line} in function"); error.SetBool("showUser", true); wjson_table_t variables = error.SetTable("variables"); variables.SetIntString("line", line); DAP_SEND(); return; } ci->_ip = func->_instructions + op; while (ci->_ip->op == _OP_LINE && ci->_ip + 1 < func->_instructions + func->_ninstructions) ci->_ip++; DAP_START_RESPONSE(seq, "goto"); DAP_SEND(); Break(vm, breakreason_t::Goto); } void SQDebugServer::OnRequest_Next(const json_table_t &arguments, int seq) { if (m_State == ThreadState_Running) { DAP_START_RESPONSE(seq, "next"); DAP_SEND(); return; } int threadId; arguments.GetInt("threadId", &threadId, -1); HSQUIRRELVM vm = ThreadFromID(threadId); if (vm != m_pCurVM) { DAP_START_RESPONSE(seq, "next"); DAP_SEND(); return; } if (!vm->ci) { DAP_ERROR_RESPONSE(seq, "next"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK SQVM::CallInfo *ci = vm->ci; #else SQVM::CallInfo *ci = vm->ci - 1; #endif string_t granularity; arguments.GetString("granularity", &granularity); if ((granularity.IsEqualTo("instruction") && InstructionStep(vm, ci)) || Step(vm, ci)) { m_State = ThreadState_StepOverInstruction; } else { m_State = ThreadState_StepOver; } m_nStateCalls = m_nCalls; DAP_START_RESPONSE(seq, "next"); DAP_SEND(); } void SQDebugServer::OnRequest_StepIn(const json_table_t &arguments, int seq) { if (m_State == ThreadState_Running) { DAP_START_RESPONSE(seq, "stepIn"); DAP_SEND(); return; } int threadId; arguments.GetInt("threadId", &threadId, -1); HSQUIRRELVM vm = ThreadFromID(threadId); if (vm != m_pCurVM) { DAP_START_RESPONSE(seq, "stepIn"); DAP_SEND(); return; } if (!vm->ci) { DAP_ERROR_RESPONSE(seq, "stepIn"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK SQVM::CallInfo *ci = vm->ci; #else SQVM::CallInfo *ci = vm->ci - 1; #endif string_t granularity; arguments.GetString("granularity", &granularity); if ((granularity.IsEqualTo("instruction") && InstructionStep(vm, ci)) || Step(vm, ci)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_StepIn; } DAP_START_RESPONSE(seq, "stepIn"); DAP_SEND(); } void SQDebugServer::OnRequest_StepOut(const json_table_t &arguments, int seq) { if (m_State == ThreadState_Running) { DAP_START_RESPONSE(seq, "stepOut"); DAP_SEND(); return; } int threadId; arguments.GetInt("threadId", &threadId, -1); HSQUIRRELVM vm = ThreadFromID(threadId); if (vm != m_pCurVM) { DAP_START_RESPONSE(seq, "stepOut"); DAP_SEND(); return; } if (!vm->ci) { DAP_ERROR_RESPONSE(seq, "stepOut"); DAP_ERROR_BODY(0, "thread is not suspended"); DAP_SEND(); return; } #ifdef NATIVE_DEBUG_HOOK SQVM::CallInfo *ci = vm->ci; #else SQVM::CallInfo *ci = vm->ci - 1; #endif string_t granularity; arguments.GetString("granularity", &granularity); if ((granularity.IsEqualTo("instruction") && ci != vm->_callsstack && InstructionStep(vm, ci - 1, 1)) || (ci != vm->_callsstack && Step(vm, ci - 1))) { m_State = ThreadState_StepOutInstruction; } else { m_State = ThreadState_StepOut; } m_nStateCalls = m_nCalls; m_pStateVM = vm; DAP_START_RESPONSE(seq, "stepOut"); DAP_SEND(); } #ifdef SQDBG_WEAK_INSTRUCTION_REF #define _CacheInstruction(_f, _i) CacheInstruction((_f), (_i)) #else #define _CacheInstruction(_f, _i) CacheInstruction((_i)) #endif bool SQDebugServer::InstructionStep(HSQUIRRELVM vm, SQVM::CallInfo *ci, int instrOffset) { Assert(m_State == ThreadState_Suspended || m_State == ThreadState_StepOverInstruction || m_State == ThreadState_StepInInstruction || m_State == ThreadState_StepOutInstruction || m_State == ThreadState_StepOver || m_State == ThreadState_StepIn || m_State == ThreadState_StepOut || m_pPausedThread == vm); Assert(ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize); while (sq_type(ci->_closure) != OT_CLOSURE && ci > vm->_callsstack) --ci; Assert(ci >= vm->_callsstack); if (sq_type(ci->_closure) != OT_CLOSURE) return false; RestoreCachedInstructions(); ClearCachedInstructions(); SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); SQInstruction *instrEnd = func->_instructions + func->_ninstructions; SQInstruction *ip = ci->_ip - instrOffset; Assert(ip >= func->_instructions); { for (;;) { ++ip; if (ip >= instrEnd) { // Step out if (ci != vm->_callsstack) return InstructionStep(vm, ci - 1, 1); return false; } if (ip->op != _OP_LINE) break; } if (ci->_etraps > 0 && ip->op == _OP_POPTRAP) { SQInstruction *trapIp = vm->_etraps.top()._ip; if (trapIp > func->_instructions && trapIp < instrEnd) { for (; trapIp < instrEnd; trapIp++) { if (trapIp->op != _OP_LINE) { _CacheInstruction(func, trapIp); break; } } } } _CacheInstruction(func, ip); } ip = ci->_ip - instrOffset; // Set break point at jump target as well // A simple alternative to evaluating the jump op if (IsJumpOp(ip) && GetJumpCount(ip) != 0) { if (ip->op == _OP_FOREACH) { for (SQInstruction *p = ip + 1; p < instrEnd; p++) { if (p->op != _OP_LINE) { _CacheInstruction(func, p); break; } } } ip += GetJumpCount(ip); for (;;) { ++ip; if (ip >= instrEnd) { // Step out if (ci != vm->_callsstack) return InstructionStep(vm, ci - 1, 1); RestoreCachedInstructions(); ClearCachedInstructions(); return false; } if (ip->op != _OP_LINE) break; } if (ci->_etraps > 0 && ip->op == _OP_POPTRAP) { SQInstruction *trapIp = vm->_etraps.top()._ip; if (trapIp > func->_instructions && trapIp < instrEnd) { for (; trapIp < instrEnd; trapIp++) { if (trapIp->op != _OP_LINE) { _CacheInstruction(func, trapIp); break; } } } } _CacheInstruction(func, ip); } return true; } bool SQDebugServer::Step(HSQUIRRELVM vm, SQVM::CallInfo *ci) { Assert(m_State == ThreadState_Suspended || m_State == ThreadState_StepOverInstruction || m_State == ThreadState_StepInInstruction || m_State == ThreadState_StepOutInstruction || m_State == ThreadState_StepOver || m_State == ThreadState_StepIn || m_State == ThreadState_StepOut || m_pPausedThread == vm); Assert(ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize); while (sq_type(ci->_closure) != OT_CLOSURE && ci > vm->_callsstack) --ci; Assert(ci >= vm->_callsstack); if (sq_type(ci->_closure) != OT_CLOSURE) return false; RestoreCachedInstructions(); ClearCachedInstructions(); SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); Assert(ci->_ip >= func->_instructions); // Check if this function was compiled with debug info // The first op is going to be a line op if (func->_instructions->op == _OP_LINE) { Assert(func->_instructions->_arg1 != 0); return false; } int op = GetOpAtNextLine(func, ci->_ip - func->_instructions); if (op == -1) { // Step out if (ci != vm->_callsstack) return Step(vm, ci - 1); return false; } Assert(op < func->_ninstructions); Assert(func->_instructions + op != ci->_ip); _CacheInstruction(func, func->_instructions + op); // Set break point at every possible jump target for (SQInstruction *ip = ci->_ip; ip <= func->_instructions + op; ip++) { if (IsJumpOp(ip) && GetJumpCount(ip) != 0) { if (ip->op == _OP_FOREACH) _CacheInstruction(func, ip + 1); _CacheInstruction(func, ip + GetJumpCount(ip) + 1); } } return true; } #undef _CacheInstruction #ifdef SQDBG_WEAK_INSTRUCTION_REF void SQDebugServer::CacheInstruction(SQFunctionProto *func, SQInstruction *instr) #else void SQDebugServer::CacheInstruction(SQInstruction *instr) #endif { cachedinstr_t &cached = m_CachedInstructions.Append(); #ifdef SQDBG_WEAK_INSTRUCTION_REF Assert(instr >= func->_instructions && instr < func->_instructions + func->_ninstructions); Assert(instr - func->_instructions <= INT_MAX); cached.func = func->GetWeakRef(OT_FUNCPROTO); __ObjAddRef(cached.func); cached.index = instr - func->_instructions; #else cached.ip = instr; #endif cached.instr = *instr; memzero(instr); Assert(instr->op == _OP_LINE); } void SQDebugServer::ClearCachedInstructions() { #ifdef SQDBG_WEAK_INSTRUCTION_REF for (int i = m_CachedInstructions.Size(); i--;) { cachedinstr_t &cached = m_CachedInstructions[i]; __ObjRelease(cached.func); } #endif m_CachedInstructions.Clear(); } void SQDebugServer::RestoreCachedInstructions() { for (int i = m_CachedInstructions.Size(); i--;) { cachedinstr_t &cached = m_CachedInstructions[i]; #ifdef SQDBG_WEAK_INSTRUCTION_REF if (cached.func && sq_type(cached.func->_obj) == OT_FUNCPROTO) _funcproto(cached.func->_obj)->_instructions[cached.index] = cached.instr; #else if (cached.ip) *cached.ip = cached.instr; #endif } } void SQDebugServer::UndoRestoreCachedInstructions() { for (int i = m_CachedInstructions.Size(); i--;) { cachedinstr_t &cached = m_CachedInstructions[i]; #ifdef SQDBG_WEAK_INSTRUCTION_REF if (cached.func && sq_type(cached.func->_obj) == OT_FUNCPROTO) memzero(&_funcproto(cached.func->_obj)->_instructions[cached.index]); #else if (cached.ip) memzero(cached.ip); #endif } } int SQDebugServer::ToVarRef(EVARREF type, HSQUIRRELVM vm, int frame) { Assert(IsScopeRef(type)); SQWeakRef *thread = GetWeakRef(vm); for (unsigned int i = 0; i < m_Vars.Size(); i++) { varref_t &v = m_Vars[i]; if (v.type == type && v.scope.frame == frame && v.scope.thread == thread) return v.id; } varref_t &var = m_Vars.Append(); var.id = ++m_nVarRefIndex; var.type = type; var.scope.frame = frame; var.scope.thread = thread; return var.id; } void SQDebugServer::ConvertToWeakRef(varref_t &v) { Assert(IsObjectRef(v.type)); if (v.obj.isStrong) { v.obj.isStrong = false; SQObject obj = v.GetVar(); __ObjRelease(_refcounted(obj)); } } int SQDebugServer::ToVarRef(EVARREF type, const SQObject &obj, bool isStrong) { Assert(IsObjectRef(type)); if (!ISREFCOUNTED(sq_type(obj))) return INVALID_ID; if (sq_type(obj) == OT_WEAKREF) { if (sq_type(_weakref(obj)->_obj) == OT_NULL) return INVALID_ID; } for (int i = m_Vars.Size(); i--;) { varref_t &v = m_Vars[i]; if (v.type == type && _rawval(v.GetVar()) == _rawval(obj)) { ConvertToWeakRef(v); return v.id; } } Assert(m_nVarRefIndex < INT_MAX); varref_t *var = &m_Vars.Append(); var->id = ++m_nVarRefIndex; var->type = type; var->obj.isStrong = isStrong; // Clients can request nested and invalidated objects, always use weakrefs var->obj.weakref = GetWeakRef(_refcounted(obj), sq_type(obj)); __ObjAddRef(var->obj.weakref); Assert(sq_type(var->obj.weakref->_obj) != OT_NULL); Assert(_rawval(var->obj.weakref->_obj) == _rawval(obj)); if (isStrong) { __ObjAddRef(_refcounted(obj)); } return var->id; } varref_t *SQDebugServer::FromVarRef(int id) { int hi = m_Vars.Size() - 1; int lo = 0; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); varref_t *var = &m_Vars[mid]; if (id > var->id) { lo = mid + 1; } else if (id < var->id) { hi = mid - 1; } else { Assert(var->type >= 0 && var->type < VARREF_MAX); if (IsScopeRef(var->type) || var->obj.isStrong || sq_type(var->GetVar()) != OT_NULL) return var; Assert(var->obj.weakref); __ObjRelease(var->obj.weakref); m_Vars.Remove(mid); return NULL; } } return NULL; } void SQDebugServer::RemoveVarRefs(bool all) { if (!all) { for (int i = m_Vars.Size(); i--;) { varref_t &v = m_Vars[i]; // Keep living weakrefs, client might refer to them later if (IsScopeRef(v.type)) { m_Vars.Remove(i); } else if (IsObjectRef(v.type)) { bool rem = false; if (v.obj.isStrong) { SQObject obj = v.GetVar(); Assert(ISREFCOUNTED(sq_type(obj))); __ObjRelease(_refcounted(obj)); rem = true; } Assert(v.obj.weakref); if (sq_type(v.obj.weakref->_obj) == OT_NULL) { __ObjRelease(v.obj.weakref); rem = true; } if (rem) m_Vars.Remove(i); } else UNREACHABLE(); } } else { for (int i = m_Vars.Size(); i--;) { varref_t &v = m_Vars[i]; // Release all refs the debugger is holding if (IsObjectRef(v.type)) { if (v.obj.isStrong) { SQObject obj = v.GetVar(); Assert(ISREFCOUNTED(sq_type(obj))); __ObjRelease(_refcounted(obj)); } Assert(v.obj.weakref); __ObjRelease(v.obj.weakref); } } m_Vars.Purge(); } } void SQDebugServer::RemoveLockedWatches() { for (unsigned int i = 0; i < m_LockedWatches.Size(); i++) FreeString(&m_Strings, &m_LockedWatches[i].expression); m_LockedWatches.Purge(); } int SQDebugServer::AddBreakpoint(int line, const string_t &src, const string_t &condition, int hitsTarget, const string_t &logMessage) { Assert(line > 0 && !src.IsEmpty()); #ifdef SQUNICODE unsigned int size = sq_rsl(SQUnicodeLength(src.ptr, src.len)); SQChar *pSrc = (SQChar *)ScratchPad(size); sqstring_t wsrc; wsrc.Assign(pSrc, UTF8ToSQUnicode(pSrc, size, src.ptr, src.len)); breakpoint_t *bp = GetBreakpoint(line, wsrc); #else breakpoint_t *bp = GetBreakpoint(line, src); #endif if (bp) { m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("duplicate breakpoint")); return DUPLICATE_ID; } SQObjectPtr condFn; if (!condition.IsEmpty() && !CompileScript(condition, condFn)) return INVALID_ID; Assert(m_nBreakpointIndex < INT_MAX); bp = &m_Breakpoints.Insert(m_nFunctionBreakpointsIdx++); bp->id = ++m_nBreakpointIndex; CopyString(&m_Strings, src, &bp->src); bp->line = line; bp->hitsTarget = hitsTarget; if (!logMessage.IsEmpty()) CopyString(&m_Strings, logMessage, &bp->logMessage); if (sq_type(condFn) != OT_NULL) { bp->conditionFn = condFn; InitEnv_GetVal(bp->conditionEnv); sq_addref(m_pRootVM, &bp->conditionFn); sq_addref(m_pRootVM, &bp->conditionEnv); } return bp->id; } int SQDebugServer::AddFunctionBreakpoint(const string_t &func, const string_t &funcsrc, int line, const string_t &condition, int hitsTarget, const string_t &logMessage) { if (line == -1) { m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("invalid function line")); return INVALID_ID; } #ifdef SQUNICODE int funcsize = sq_rsl(SQUnicodeLength(func.ptr, func.len)); int srcsize = funcsrc.IsEmpty() ? 0 : sq_rsl(SQUnicodeLength(funcsrc.ptr, funcsrc.len)); SQChar *pFunc = (SQChar *)ScratchPad(funcsize + srcsize); SQChar *pSrc = pFunc + funcsize; sqstring_t wfunc, wsrc; if (func.IsEmpty()) { wfunc.Assign(_SC("")); } else { wfunc.Assign(pFunc, UTF8ToSQUnicode(pFunc, funcsize, func.ptr, func.len)); } if (funcsrc.IsEmpty()) { wsrc.Assign(_SC("")); } else { wsrc.Assign(pSrc, UTF8ToSQUnicode(pSrc, srcsize, funcsrc.ptr, funcsrc.len)); } breakpoint_t *bp = GetFunctionBreakpoint(wfunc, wsrc, line); #else breakpoint_t *bp = GetFunctionBreakpoint(func, funcsrc, line); #endif if (bp) { m_pCurVM->_lasterror = CreateSQString(m_pCurVM, _SC("duplicate breakpoint")); return DUPLICATE_ID; } SQObjectPtr condFn; if (!condition.IsEmpty() && !CompileScript(condition, condFn)) return INVALID_ID; Assert(m_nBreakpointIndex < INT_MAX); bp = &m_Breakpoints.Append(); bp->id = ++m_nBreakpointIndex; if (!func.IsEmpty()) { CopyString(&m_Strings, func, &bp->src); } else { bp->src.Assign(_SC("")); } if (!funcsrc.IsEmpty()) { CopyString(&m_Strings, funcsrc, &bp->funcsrc); } else { bp->funcsrc.Assign(_SC("")); } bp->line = line; bp->hitsTarget = hitsTarget; if (!logMessage.IsEmpty()) CopyString(&m_Strings, logMessage, &bp->logMessage); if (sq_type(condFn) != OT_NULL) { bp->conditionFn = condFn; InitEnv_GetVal(bp->conditionEnv); sq_addref(m_pRootVM, &bp->conditionFn); sq_addref(m_pRootVM, &bp->conditionEnv); } return bp->id; } breakpoint_t *SQDebugServer::GetBreakpoint(int line, const sqstring_t &src) { Assert(line && src.ptr); for (unsigned int i = 0; i < m_nFunctionBreakpointsIdx; i++) { breakpoint_t &bp = m_Breakpoints[i]; if (bp.line == line && bp.src.IsEqualTo(src)) { return &bp; } } return NULL; } breakpoint_t *SQDebugServer::GetFunctionBreakpoint(const sqstring_t &func, const sqstring_t &funcsrc, int line) { Assert(func.ptr); for (unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.Size(); i++) { breakpoint_t &bp = m_Breakpoints[i]; if (bp.src.IsEqualTo(func) && (bp.funcsrc.IsEmpty() || bp.funcsrc.IsEqualTo(funcsrc)) && (bp.line == 0 || bp.line == line)) { return &bp; } } return NULL; } void SQDebugServer::FreeBreakpoint(breakpoint_t &bp) { FreeString(&m_Strings, &bp.src); FreeString(&m_Strings, &bp.funcsrc); if (sq_type(bp.conditionFn) != OT_NULL) { sq_release(m_pRootVM, &bp.conditionFn); bp.conditionFn.Null(); } if (sq_type(bp.conditionEnv) != OT_NULL) { ClearEnvDelegate(bp.conditionEnv); sq_release(m_pRootVM, &bp.conditionEnv); bp.conditionEnv.Null(); } FreeString(&m_Strings, &bp.logMessage); bp.hits = bp.hitsTarget = 0; } void SQDebugServer::RemoveAllBreakpoints() { for (int i = m_Breakpoints.Size(); i--;) FreeBreakpoint(m_Breakpoints[i]); m_Breakpoints.Clear(); m_nFunctionBreakpointsIdx = 0; } void SQDebugServer::RemoveBreakpoints(const string_t &source) { #ifdef SQUNICODE unsigned int size = sq_rsl(SQUnicodeLength(source.ptr, source.len)); SQChar *tmp = (SQChar *)ScratchPad(size); sqstring_t src; src.Assign(tmp, UTF8ToSQUnicode(tmp, size, source.ptr, source.len)); #else const sqstring_t &src = source; #endif for (unsigned int i = 0; i < m_nFunctionBreakpointsIdx;) { breakpoint_t &bp = m_Breakpoints[i]; if (bp.src.IsEqualTo(src)) { FreeBreakpoint(bp); m_Breakpoints.Remove(i); m_nFunctionBreakpointsIdx--; } else { i++; } } } void SQDebugServer::RemoveFunctionBreakpoints() { for (unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.Size();) { breakpoint_t &bp = m_Breakpoints[i]; FreeBreakpoint(bp); m_Breakpoints.Remove(i); } m_nFunctionBreakpointsIdx = m_Breakpoints.Size(); } classdef_t *SQDebugServer::FindClassDef(SQClass *base) { for (unsigned int i = 0; i < m_ClassDefinitions.Size(); i++) { classdef_t &def = m_ClassDefinitions[i]; if (def.base == base) return &def; } return NULL; } const SQObjectPtr *SQDebugServer::GetClassDefValue(SQClass *base) { const classdef_t *def; do { if ((def = FindClassDef(base)) != NULL && sq_type(def->value) != OT_NULL) return &def->value; } while ((base = base->_base) != NULL); return NULL; } const SQObjectPtr *SQDebugServer::GetClassDefMetaMembers(SQClass *base) { const classdef_t *def; do { if ((def = FindClassDef(base)) != NULL && sq_type(def->metamembers) != OT_NULL) return &def->metamembers; } while ((base = base->_base) != NULL); return NULL; } const SQObjectPtr *SQDebugServer::GetClassDefCustomMembers(SQClass *base) { const classdef_t *def; do { if ((def = FindClassDef(base)) != NULL && sq_type(def->custommembers) != OT_NULL) return &def->custommembers; } while ((base = base->_base) != NULL); return NULL; } void SQDebugServer::DefineClass(SQClass *target, SQTable *params) { classdef_t *def = FindClassDef(target); if (!def) { def = &m_ClassDefinitions.Append(); def->base = target; } SQObjectPtr name; if (SQTable_Get(params, _SC("name"), name)) { if (sq_type(name) == OT_STRING && _string(name)->_len) { stringbufext_t buf = ScratchPadBuf(1024); buf.PutHex((uintptr_t)target); buf.Put(' '); buf.Puts(_string(name)); CopyString(&m_Strings, buf, &def->name); } else { FreeString(&m_Strings, &def->name); } } SQObjectPtr value; if (SQTable_Get(params, _SC("value"), value)) { if (sq_type(def->value) != OT_NULL) { Assert(sq_type(def->value) == OT_CLOSURE || sq_type(def->value) == OT_NATIVECLOSURE); sq_release(m_pRootVM, &def->value); def->value.Null(); } if (sq_type(value) == OT_CLOSURE || sq_type(value) == OT_NATIVECLOSURE) { def->value = value; sq_addref(m_pRootVM, &def->value); } } SQObjectPtr metamembers; if (SQTable_Get(params, _SC("metamembers"), metamembers)) { if (sq_type(def->metamembers) != OT_NULL) { Assert(sq_type(def->metamembers) == OT_ARRAY); sq_release(m_pRootVM, &def->metamembers); def->metamembers.Null(); } if (sq_type(metamembers) == OT_ARRAY) { def->metamembers = metamembers; sq_addref(m_pRootVM, &def->metamembers); } } SQObjectPtr custommembers; if (SQTable_Get(params, _SC("custommembers"), custommembers)) { if (sq_type(def->custommembers) != OT_NULL) { Assert(sq_type(def->custommembers) == OT_ARRAY || sq_type(def->custommembers) == OT_CLOSURE); sq_release(m_pRootVM, &def->custommembers); def->custommembers.Null(); } if (sq_type(custommembers) == OT_ARRAY || sq_type(custommembers) == OT_CLOSURE) { def->custommembers = custommembers; sq_addref(m_pRootVM, &def->custommembers); } } } bool SQDebugServer::CallCustomMembersGetFunc(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &key, SQObjectPtr &ret) { int nparams; if (sq_type(closure) == OT_CLOSURE) { nparams = _fp(_closure(closure)->_function)->_nparameters; } else if (sq_type(closure) == OT_NATIVECLOSURE) { nparams = _nativeclosure(closure)->_nparamscheck; } else { return false; } if (nparams == 1) { return RunClosure(closure, env, ret); } else if (nparams == 2) { return RunClosure(closure, env, key, ret); } return false; } bool SQDebugServer::CallCustomMembersSetFunc(const SQObjectPtr &closure, const SQObject *env, const SQObjectPtr &key, const SQObjectPtr &val, SQObjectPtr &ret) { int nparams; if (sq_type(closure) == OT_CLOSURE) { nparams = _fp(_closure(closure)->_function)->_nparameters; } else if (sq_type(closure) == OT_NATIVECLOSURE) { nparams = _nativeclosure(closure)->_nparamscheck; } else { return false; } if (nparams == 2) { return RunClosure(closure, env, val, ret); } else if (nparams == 3) { return RunClosure(closure, env, key, val, ret); } return false; } void SQDebugServer::RemoveClassDefs() { for (unsigned int i = 0; i < m_ClassDefinitions.Size(); i++) { classdef_t &def = m_ClassDefinitions[i]; FreeString(&m_Strings, &def.name); if (sq_type(def.value) != OT_NULL) { Assert(sq_type(def.value) == OT_CLOSURE || sq_type(def.value) == OT_NATIVECLOSURE); sq_release(m_pRootVM, &def.value); def.value.Null(); } if (sq_type(def.metamembers) != OT_NULL) { Assert(sq_type(def.metamembers) == OT_ARRAY); sq_release(m_pRootVM, &def.metamembers); def.metamembers.Null(); } if (sq_type(def.custommembers) != OT_NULL) { Assert(sq_type(def.custommembers) == OT_ARRAY || sq_type(def.custommembers) == OT_CLOSURE); sq_release(m_pRootVM, &def.custommembers); def.custommembers.Null(); } } m_ClassDefinitions.Purge(); } #define DISASM_DIVIDER_LEN 6 #define DISASM_MAX_PARAM_NAME_LEN 64 int SQDebugServer::DisassemblyBufLen(SQClosure *target) { SQFunctionProto *func = _fp(target->_function); const int buflen = STRLEN("stacksize \n") + FMT_INT_LEN + STRLEN("instructions \n") + FMT_INT_LEN + STRLEN("literals \n") + FMT_INT_LEN + STRLEN("localvarinfos \n") + FMT_INT_LEN + STRLEN("parameters \n") + FMT_INT_LEN + DISASM_DIVIDER_LEN + 1 + func->_nparameters * (DISASM_MAX_PARAM_NAME_LEN + 2) - 2 + 1 + #if SQUIRREL_VERSION_NUMBER > 212 func->_ndefaultparams * (DISASM_MAX_PARAM_NAME_LEN + 3) + #endif DISASM_DIVIDER_LEN + 1 + func->_ninstructions * (6 + 30 + 128 + 1) - 1 + 1; return buflen; } sqstring_t SQDebugServer::PrintDisassembly(SQClosure *target, SQChar *scratch, int bufsize) { SQFunctionProto *func = _fp(target->_function); SQChar *buf = scratch; #define _bs (bufsize - (int)((char *)buf - (char *)scratch)) int instrCount = func->_ninstructions; for (int i = instrCount; i--;) if (func->_instructions[i].op == _OP_LINE) instrCount--; #define putint(str, val) \ { \ int len = STRLEN(str); \ memcpy(buf, _SC(str), sq_rsl(len)); \ buf += len; \ buf += printint(buf, _bs, val); \ *buf++ = '\n'; \ } putint("stacksize ", (int)func->_stacksize); putint("instructions ", instrCount); putint("literals ", (int)func->_nliterals); putint("localvarinfos ", (int)func->_nlocalvarinfos); int nparams = func->_nparameters; #if SQUIRREL_VERSION_NUMBER >= 300 if (func->_varparams) nparams--; #endif putint("parameters ", nparams); #undef putint for (int i = DISASM_DIVIDER_LEN; i--;) *buf++ = '-'; *buf++ = '\n'; for (int i = 0; i < nparams; i++) { const SQObjectPtr ¶m = func->_parameters[i]; Assert(sq_type(param) == OT_STRING); int len = min((int)_string(param)->_len, DISASM_MAX_PARAM_NAME_LEN); memcpy(buf, _string(param)->_val, sq_rsl(len)); buf += len; #if SQUIRREL_VERSION_NUMBER > 212 int idx; if (func->_ndefaultparams && (idx = (int)func->_ndefaultparams - (nparams - i)) >= 0) { len = STRLEN(" = "); memcpy(buf, _SC(" = "), sq_rsl(len)); buf += len; const SQObjectPtr &val = target->_defaultparams[idx]; string_t str; switch (sq_type(val)) { case OT_INTEGER: case OT_FLOAT: case OT_BOOL: case OT_NULL: case OT_STRING: str = GetValue(val); len = min(str.len, DISASM_MAX_PARAM_NAME_LEN - 3); break; case OT_CLASS: case OT_INSTANCE: { const classdef_t *def = FindClassDef(sq_type(val) == OT_CLASS ? _class(val) : _instance(val)->_class); if (def && def->name.ptr) { str = GetType(val); len = min((int)str.len, DISASM_MAX_PARAM_NAME_LEN - 4); #ifdef SQUNICODE UTF8ToSQUnicode(buf, _bs, str.ptr, len); #else memcpy(buf, str.ptr, sq_rsl(len)); #endif buf += len; *buf++ = ' '; str.Assign(def->name.ptr + FMT_PTR_LEN + 1, def->name.len - FMT_PTR_LEN - 1); len = min((int)str.len, DISASM_MAX_PARAM_NAME_LEN - 4 - len); break; } } default: str = GetType(val); len = min((int)str.len, DISASM_MAX_PARAM_NAME_LEN - 3); } #ifdef SQUNICODE UTF8ToSQUnicode(buf, _bs, str.ptr, len); #else memcpy(buf, str.ptr, sq_rsl(len)); #endif buf += len; } #endif *buf++ = ','; *buf++ = ' '; } if (!func->_varparams) { buf -= 2; } else { *buf++ = '.'; *buf++ = '.'; *buf++ = '.'; } *buf++ = '\n'; for (int i = DISASM_DIVIDER_LEN; i--;) *buf++ = '-'; *buf++ = '\n'; RestoreCachedInstructions(); for (int i = 0, index = 0; i < func->_ninstructions; i++) { SQInstruction *instr = func->_instructions + i; if (instr->op == _OP_LINE) continue; stringbuf_t<128> tmp; tmp.PutHex(instr->op); tmp.Put(' '); tmp.PutInt(instr->_arg0); tmp.Put(' '); tmp.PutInt(instr->_arg1); tmp.Put(' '); tmp.PutInt(instr->_arg2); tmp.Put(' '); tmp.PutInt(instr->_arg3); tmp.Term(); #ifdef SQUNICODE int len = scsprintf(buf, _bs / (int)sizeof(SQChar), _SC("%-6d %-29hs"), index++, tmp.ptr); if (len < 0 || len > _bs / (int)sizeof(SQChar)) len = _bs / (int)sizeof(SQChar); buf += len; tmp.len = 0; DescribeInstruction(instr, func, tmp); buf += UTF8ToSQUnicode(buf, _bs, tmp.ptr, tmp.len); #else int len = scsprintf(buf, _bs / (int)sizeof(SQChar), _SC("%-6d %-29s"), index++, tmp.ptr); if (len < 0 || len > _bs / (int)sizeof(SQChar)) len = _bs / (int)sizeof(SQChar); buf += len; stringbufext_t sbuf(buf, _bs); DescribeInstruction(instr, func, sbuf); buf += sbuf.len; #endif if (_bs <= 0) { buf--; break; } *buf++ = '\n'; } UndoRestoreCachedInstructions(); *buf-- = 0; #undef _bs return {scratch, (unsigned int)(buf - scratch)}; } #ifndef SQDBG_DISABLE_PROFILER CProfiler *SQDebugServer::GetProfiler(HSQUIRRELVM vm) { for (unsigned int i = 0; i < m_Profilers.Size(); i++) { threadprofiler_t &tp = m_Profilers[i]; if (tp.thread && sq_type(tp.thread->_obj) == OT_THREAD) { if (_thread(tp.thread->_obj) == vm) { return &tp.prof; } } } return NULL; } CProfiler *SQDebugServer::GetProfilerFast(HSQUIRRELVM vm) { if (m_pCurVM == vm) { Assert(m_pProfiler == GetProfiler(vm)); return m_pProfiler; } return GetProfiler(vm); } void SQDebugServer::ProfSwitchThread(HSQUIRRELVM vm) { Assert(IsProfilerEnabled()); if (m_Profilers.Size() == 0) m_Profilers.Reserve(1); for (unsigned int i = 0; i < m_Profilers.Size(); i++) { threadprofiler_t &tp = m_Profilers[i]; if (tp.thread && sq_type(tp.thread->_obj) == OT_THREAD) { if (_thread(tp.thread->_obj) == vm) { m_pProfiler = tp.prof.IsEnabled() ? &tp.prof : NULL; return; } } else { __ObjRelease(tp.thread); m_Profilers.Remove(i); i--; } } threadprofiler_t &tp = m_Profilers.Append(); tp.thread = GetWeakRef(vm); __ObjAddRef(tp.thread); m_pProfiler = &tp.prof; m_pProfiler->Start(vm); } void SQDebugServer::ProfStart() { #ifndef SQDBG_DISABLE_PROFILER_AUTO if (!IsClientConnected()) SetDebugHook(&SQProfHook); #endif m_bProfilerEnabled = true; ProfSwitchThread(m_pCurVM); } void SQDebugServer::ProfStop() { #ifndef SQDBG_DISABLE_PROFILER_AUTO if (!IsClientConnected()) SetDebugHook(NULL); #endif for (unsigned int i = 0; i < m_Profilers.Size(); i++) { threadprofiler_t &tp = m_Profilers[i]; __ObjRelease(tp.thread); tp.prof.Stop(); } m_Profilers.Clear(); m_pProfiler = NULL; m_bProfilerEnabled = false; } void SQDebugServer::ProfPause(HSQUIRRELVM vm) { CProfiler *prof = GetProfilerFast(vm); if (prof && prof->IsEnabled()) { prof->Pause(); } } void SQDebugServer::ProfResume(HSQUIRRELVM vm) { CProfiler *prof = GetProfilerFast(vm); if (prof && prof->IsEnabled()) { prof->Resume(); } } void SQDebugServer::ProfReset(HSQUIRRELVM vm, SQString *tag) { CProfiler *prof = GetProfilerFast(vm); if (prof && prof->IsEnabled()) { prof->Reset(vm, tag); } } void SQDebugServer::ProfGroupBegin(HSQUIRRELVM vm, SQString *tag) { CProfiler *prof = GetProfilerFast(vm); if (prof && prof->IsActive()) { prof->GroupBegin(tag); } } void SQDebugServer::ProfGroupEnd(HSQUIRRELVM vm) { CProfiler *prof = GetProfilerFast(vm); if (prof && prof->IsActive()) { prof->GroupEnd(); } } sqstring_t SQDebugServer::ProfGets(HSQUIRRELVM vm, SQString *tag, int type) { Assert(IsProfilerEnabled()); CProfiler *pProfiler = NULL; for (unsigned int i = 0; i < m_Profilers.Size(); i++) { threadprofiler_t &tp = m_Profilers[i]; if (tp.thread && sq_type(tp.thread->_obj) == OT_THREAD) { if (_thread(tp.thread->_obj) == vm) { pProfiler = &tp.prof; break; } } else { __ObjRelease(tp.thread); m_Profilers.Remove(i); i--; } } if (!pProfiler) return {0, 0}; const int size = pProfiler->GetMaxOutputLen(tag, type); if (size <= 0) return {0, 0}; SQChar *buf = _ss(m_pRootVM)->GetScratchPad(sq_rsl(size)); int len = pProfiler->Output(tag, type, buf, size); Assert(len >= 0); return {buf, (unsigned int)len}; } void SQDebugServer::ProfPrint(HSQUIRRELVM vm, SQString *tag, int type) { sqstring_t str = ProfGets(vm, tag, type); if (str.IsEmpty()) return; // Print each line for (SQChar *start = str.ptr;;) { SQChar *end = scstrchr(start, '\n'); if (!end) break; int linelen = (int)(end + 1 - start); _OutputDebugStringFmt(_SC(FMT_VSTR), linelen, start); m_Print(vm, _SC(FMT_VSTR), linelen, start); SendEvent_OutputStdOut(sqstring_t(start, linelen), NULL); if (end + 1 >= str.ptr + str.len) break; start = end + 1; } } #endif #ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER void SQDebugServer::PrintVar(HSQUIRRELVM vm, const SQChar *name, const SQObjectPtr &obj) { switch (sq_type(obj)) { case OT_NULL: SQErrorAtFrame(vm, NULL, _SC("[%s] NULL\n"), name); break; case OT_INTEGER: SQErrorAtFrame(vm, NULL, _SC("[%s] " FMT_INT "\n"), name, _integer(obj)); break; case OT_FLOAT: SQErrorAtFrame(vm, NULL, _SC("[%s] %.14g\n"), name, _float(obj)); break; case OT_USERPOINTER: SQErrorAtFrame(vm, NULL, _SC("[%s] USERPOINTER\n"), name); break; case OT_STRING: SQErrorAtFrame(vm, NULL, _SC("[%s] \"%.50s\"\n"), name, _string(obj)->_val); break; case OT_TABLE: SQErrorAtFrame(vm, NULL, _SC("[%s] TABLE (#" FMT_INT ")\n"), name, _table(obj)->CountUsed()); break; case OT_ARRAY: SQErrorAtFrame(vm, NULL, _SC("[%s] ARRAY (#" FMT_INT ")\n"), name, _array(obj)->Size()); break; case OT_CLOSURE: SQErrorAtFrame(vm, NULL, _SC("[%s] CLOSURE\n"), name); break; case OT_NATIVECLOSURE: SQErrorAtFrame(vm, NULL, _SC("[%s] NATIVECLOSURE\n"), name); break; case OT_GENERATOR: { const SQObjectPtr &funcname = _fp(_closure(_generator(obj)->_ci._closure)->_function)->_name; if (sq_type(funcname) == OT_STRING) { SQErrorAtFrame(vm, NULL, _SC("[%s] GENERATOR (%s)\n"), name, _string(funcname)->_val); } else { SQErrorAtFrame(vm, NULL, _SC("[%s] GENERATOR\n"), name); } break; } case OT_USERDATA: SQErrorAtFrame(vm, NULL, _SC("[%s] USERDATA\n"), name); break; case OT_THREAD: SQErrorAtFrame(vm, NULL, _SC("[%s] THREAD\n"), name); break; case OT_CLASS: { const classdef_t *def = FindClassDef(_class(obj)); if (def && def->name.ptr) { SQErrorAtFrame(vm, NULL, _SC("[%s] CLASS (" FMT_CSTR ")\n"), name, def->name.ptr + FMT_PTR_LEN + 1); } else { SQErrorAtFrame(vm, NULL, _SC("[%s] CLASS\n"), name); } break; } case OT_INSTANCE: { const classdef_t *def = FindClassDef(_instance(obj)->_class); if (def && def->name.ptr) { SQErrorAtFrame(vm, NULL, _SC("[%s] INSTANCE (" FMT_CSTR ")\n"), name, def->name.ptr + FMT_PTR_LEN + 1); } else { SQErrorAtFrame(vm, NULL, _SC("[%s] INSTANCE\n"), name); } break; } case OT_WEAKREF: PrintVar(vm, name, _weakref(obj)->_obj); break; case OT_BOOL: SQErrorAtFrame(vm, NULL, _SC("[%s] %s\n"), name, _integer(obj) ? _SC("true") : _SC("false")); break; default: UNREACHABLE(); } } void SQDebugServer::PrintStack(HSQUIRRELVM vm) { SQErrorAtFrame(vm, NULL, _SC("\nCALLSTACK\n")); int frame = vm->_callsstacksize; while (frame--) { const SQVM::CallInfo &ci = vm->_callsstack[frame]; if (ShouldIgnoreStackFrame(vm, ci)) continue; const SQChar *fn = _SC("??"); const SQChar *src = _SC("??"); int line; if (sq_type(ci._closure) == OT_CLOSURE) { SQFunctionProto *func = _fp(_closure(ci._closure)->_function); line = func->GetLine(ci._ip); if (sq_type(func->_name) == OT_STRING) fn = _string(func->_name)->_val; if (sq_type(func->_sourcename) == OT_STRING) src = _string(func->_sourcename)->_val; } else if (sq_type(ci._closure) == OT_NATIVECLOSURE) { SQNativeClosure *closure = _nativeclosure(ci._closure); src = _SC("NATIVE"); line = -1; if (sq_type(closure->_name) == OT_STRING) fn = _string(closure->_name)->_val; } else UNREACHABLE(); SQErrorAtFrame(vm, NULL, _SC("*FUNCTION [%s()] %s line [%d]\n"), fn, src, line); } SQErrorAtFrame(vm, NULL, _SC("\nLOCALS\n")); frame = vm->_callsstacksize; if (frame > 10) frame = 10; while (frame--) { const SQVM::CallInfo &ci = vm->_callsstack[frame]; if (sq_type(ci._closure) != OT_CLOSURE) continue; if (ShouldIgnoreStackFrame(vm, ci)) continue; int stackbase = GetStackBase(vm, &ci); SQClosure *pClosure = _closure(ci._closure); SQFunctionProto *func = _fp(pClosure->_function); SQUnsignedInteger ip = ci._ip - func->_instructions; for (int i = 0; i < func->_nlocalvarinfos; i++) { const SQLocalVarInfo &var = func->_localvarinfos[i]; if (var._start_op <= ip && var._end_op + 1 >= ip) { PrintVar(vm, _string(var._name)->_val, vm->_stack._vals[stackbase + var._pos]); } } for (int i = 0; i < func->_noutervalues; i++) { const SQOuterVar &var = func->_outervalues[i]; PrintVar(vm, _string(var._name)->_val, *_outervalptr(pClosure->_outervalues[i])); } } } #endif void SQDebugServer::ErrorHandler(HSQUIRRELVM vm) { if (m_bDebugHookGuard) return; string_t err; // Custom error handler and PrintStack can reallocate sqdbg scratch buf, // get value when it is needed SQObjectPtr oe; if (sq_gettop(vm) >= 1) { HSQOBJECT o; sq_getstackobj(vm, 2, &o); err.ptr = 0; oe = o; } else { err.Assign("??"); } bool getError = !err.ptr; // An error handler is required to detect exceptions. // The downside of calling the default error handler instead of // replicating it in the debugger is the extra stack frame and redundant print locations. // Otherwise this would be preferrable for preserving custom error handlers. #ifdef SQDBG_CALL_DEFAULT_ERROR_HANDLER SQObjectPtr dummy; vm->Call(m_ErrorHandler, 2, vm->_top - 2, dummy, SQFalse); #else if (getError) err = GetValue(oe, kFS_NoQuote); SQErrorAtFrame(vm, NULL, _SC("\nAN ERROR HAS OCCURRED [" FMT_VCSTR "]\n"), STR_EXPAND(err)); PrintStack(vm); #endif if (getError) err = GetValue(oe, kFS_NoQuote); if (m_bBreakOnExceptions) { Break(vm, {breakreason_t::Exception, err}); #if SQUIRREL_VERSION_NUMBER < 300 // SQ2 doesn't notify the debug hook of returns via errors, // have to suspend and finish profiler calls here #ifndef SQDBG_DISABLE_PROFILER_AUTO if (IsProfilerEnabled()) { CProfiler *prof = GetProfiler(vm); if (prof && prof->IsActive()) { prof->CallEndAll(); } } #endif // Use m_bExceptionPause to make stepping while suspended here continue the thread. if (m_State == ThreadState_SuspendNow) { m_bExceptionPause = true; m_bInDebugHook = true; Suspend(); m_bExceptionPause = false; m_bInDebugHook = false; } #else // To fix instruction stepping in exception m_bExceptionPause = true; #endif } } void SQDebugServer::Break(HSQUIRRELVM vm, breakreason_t reason) { Assert(IsClientConnected()); Assert(reason.reason != breakreason_t::None); DAP_START_EVENT(++m_Sequence, "stopped"); DAP_SET_TABLE(body); body.SetInt("threadId", ThreadToID(vm)); body.SetBool("allThreadsStopped", true); switch (reason.reason) { case breakreason_t::Step: body.SetString("reason", "step"); break; case breakreason_t::Breakpoint: body.SetString("reason", "breakpoint"); break; case breakreason_t::Exception: body.SetString("reason", "exception"); break; case breakreason_t::Pause: body.SetString("reason", "pause"); break; case breakreason_t::Restart: body.SetString("reason", "restart"); break; case breakreason_t::Goto: body.SetString("reason", "goto"); break; case breakreason_t::FunctionBreakpoint: body.SetString("reason", "function breakpoint"); break; case breakreason_t::DataBreakpoint: body.SetString("reason", "data breakpoint"); break; default: UNREACHABLE(); } if (!reason.text.IsEmpty()) body.SetString("text", reason.text); if (reason.reason == breakreason_t::Breakpoint || reason.reason == breakreason_t::FunctionBreakpoint || reason.reason == breakreason_t::DataBreakpoint) { wjson_array_t ids = body.SetArray("hitBreakpointIds"); ids.Append(reason.id); } DAP_SEND(); if (m_State != ThreadState_Suspended) m_State = ThreadState_SuspendNow; } void SQDebugServer::Suspend() { Assert(m_State == ThreadState_SuspendNow); m_State = ThreadState_Suspended; do { if (IsClientConnected()) { Frame(); } else { Continue(NULL); break; } sqdbg_sleep(5); } while (m_State == ThreadState_Suspended); } void SQDebugServer::Continue(HSQUIRRELVM vm) { if (m_State == ThreadState_SuspendNow) return; if (IsClientConnected() && m_State != ThreadState_Running) { DAP_START_EVENT(++m_Sequence, "continued"); DAP_SET_TABLE(body); body.SetInt("threadId", ThreadToID(vm)); body.SetBool("allThreadsContinued", true); DAP_SEND(); } m_State = ThreadState_Running; m_pPausedThread = NULL; RemoveReturnValues(); #if SQUIRREL_VERSION_NUMBER >= 300 m_bExceptionPause = false; #endif RestoreCachedInstructions(); ClearCachedInstructions(); RemoveVarRefs(false); RemoveLockedWatches(); #ifndef SQDBG_DISABLE_PROFILER // HACKHACK: Re-validate current profiler if (IsProfilerEnabled()) ProfSwitchThread(m_pCurVM); #endif ClearEnvDelegate(m_EnvGetVal); if (m_Scratch.Size() > 1024) m_Scratch.Alloc(1024); } void SQDebugServer::RemoveReturnValues() { for (unsigned int i = 0; i < m_ReturnValues.Size(); i++) { returnvalue_t &rv = m_ReturnValues[i]; if (rv.funcname) __ObjRelease(rv.funcname); } m_ReturnValues.Clear(); m_iYieldValues = 0; } int SQDebugServer::EvalAndWriteExpr(HSQUIRRELVM vm, int frame, string_t &expression, char *buf, int size) { // Don't modify logMessage char *comma = NULL; // LOCK flag is ignored, it's ok int flags = ParseFormatSpecifiers(expression, &comma); // Write to buffer in here because // value could be holding the only ref to a SQString // which the result string_t will point to SQObjectPtr value; #ifndef SQDBG_DISABLE_COMPILER // 'expression' is a substring of breakpoint_t::logMessage // don't modify it in CCompiler::ParseString char cpy[512]; Assert(expression.len <= sizeof(cpy)); string_t expr; expr.Assign(cpy, expression.len); memcpy(cpy, expression.ptr, expression.len); ECompileReturnCode cres = Evaluate(expr, vm, frame, value); if (cres == CompileReturnCode_Success) #else objref_t obj; if (GetObj_Frame(vm, frame, expression, obj, value) || RunExpression(expression, vm, frame, value)) #endif { if (comma) *comma = ','; string_t result = GetValue(value, flags); int writelen = min((int)result.len, size); memcpy(buf, result.ptr, writelen); return writelen; } if (comma) *comma = ','; return 0; } void SQDebugServer::TracePoint(breakpoint_t *bp, HSQUIRRELVM vm, int frame) { char buf[512]; const int bufsize = sizeof(buf) - 2; // \n\0 int readlen = min((int)bp->logMessage.len, bufsize); char *pWrite = buf; char *logMessage = bp->logMessage.ptr; // if logMessage is surrounded with {/ and }, // evaluate the expression but don't print. // A simple way to inject expression evaluation without side effects // although still limited by print buffer size bool escapePrint = readlen > 3 && logMessage[0] == '{' && logMessage[1] == '/' && logMessage[readlen - 1] == '}'; if (escapePrint) logMessage[1] = ' '; for (int iRead = 0; iRead < readlen && pWrite - buf < bufsize; iRead++) { switch (logMessage[iRead]) { case '{': { int depth = 1; for (int j = iRead + 1; j < readlen; j++) { switch (logMessage[j]) { case '}': { depth--; // Found expression if (depth == 0) { string_t expression; expression.Assign(logMessage + iRead + 1, j - iRead - 1); iRead = j; if (expression.len) { int remaining = bufsize - (pWrite - buf); int writelen = EvalAndWriteExpr(vm, frame, expression, pWrite, remaining); pWrite += writelen; } goto exit; } break; } case '{': { depth++; break; } } } exit:; break; } case '\\': { if (iRead + 1 < readlen) { switch (logMessage[iRead + 1]) { case 'n': { *pWrite++ = '\n'; iRead++; break; } case 't': { *pWrite++ = '\t'; iRead++; break; } case '\\': case '{': case '$': { *pWrite++ = logMessage[iRead + 1]; iRead++; break; } default: *pWrite++ = '\\'; } } else { *pWrite++ = '\\'; } break; } case '$': { #define CHECK_KEYWORD(s) \ ((iRead + (int)STRLEN(s) < readlen) && \ !memcmp(logMessage + iRead + 1, s, sizeof(s) - 1)) if (CHECK_KEYWORD("FUNCTION")) { iRead += STRLEN("FUNCTION"); const SQVM::CallInfo *ci = vm->_callsstack + frame; SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); if (sq_type(func->_name) == OT_STRING) { SQString *name = _string(func->_name); int remaining = bufsize - (pWrite - buf); int writelen = scstombs(pWrite, remaining, name->_val, name->_len); pWrite += writelen; } else { int remaining = bufsize - (pWrite - buf); int writelen = printhex(pWrite, remaining, (uintptr_t)func); pWrite += writelen; } break; } else if (CHECK_KEYWORD("CALLER")) { iRead += STRLEN("CALLER"); const SQVM::CallInfo *ci = vm->_callsstack + frame - 1; if (ci >= vm->_callsstack) { if (sq_type(ci->_closure) == OT_CLOSURE) { SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); if (sq_type(func->_name) == OT_STRING) { SQString *name = _string(func->_name); int remaining = bufsize - (pWrite - buf); int writelen = scstombs(pWrite, remaining, name->_val, name->_len); pWrite += writelen; } else { int remaining = bufsize - (pWrite - buf); int writelen = printhex(pWrite, remaining, (uintptr_t)func); pWrite += writelen; } } else if (sq_type(ci->_closure) == OT_NATIVECLOSURE) { SQNativeClosure *closure = _nativeclosure(ci->_closure); if (sq_type(closure->_name) == OT_STRING) { SQString *name = _string(closure->_name); int remaining = bufsize - (pWrite - buf); int writelen = scstombs(pWrite, remaining, name->_val, name->_len); pWrite += writelen; } else { int remaining = bufsize - (pWrite - buf); int writelen = printhex(pWrite, remaining, (uintptr_t)closure); pWrite += writelen; } } else UNREACHABLE(); } break; } else if (CHECK_KEYWORD("HITCOUNT")) { iRead += STRLEN("HITCOUNT"); // lazy hack, hit count was reset after hitting the target // if this count is to ignore hit target, keep trace hit count separately int hits = bp->hits ? bp->hits : bp->hitsTarget; int remaining = bufsize - (pWrite - buf); int writelen = printint(pWrite, remaining, hits); pWrite += writelen; break; } // else fallthrough #undef CHECK_KEYWORD } default: *pWrite++ = logMessage[iRead]; } } if (escapePrint) { logMessage[1] = '/'; return; } *pWrite++ = '\n'; *pWrite = 0; const SQVM::CallInfo *ci = vm->_callsstack + frame; _OutputDebugStringA(buf); #ifdef SQUNICODE m_Print(vm, _SC(FMT_CSTR), buf); #else m_Print(vm, buf); #endif SendEvent_OutputStdOut(string_t(buf, (int)(pWrite - buf)), ci); } bool SQDebugServer::HasCondition(const breakpoint_t *bp) { return (sq_type(bp->conditionFn) != OT_NULL); } bool SQDebugServer::CheckBreakpointCondition(breakpoint_t *bp, HSQUIRRELVM vm, const SQVM::CallInfo *ci) { Assert(HasCondition(bp)); Assert(sq_type(bp->conditionFn) != OT_NULL && sq_type(bp->conditionEnv) != OT_NULL); SetCallFrame(bp->conditionEnv, vm, ci); SetEnvDelegate(bp->conditionEnv, vm, ci); SQObjectPtr res; // Using sqdbg compiler here is faster for simple expressions, // but gets increasingly slower as the expression grows larger and more complex // while executing precompiled sq function is constant time if (RunClosure(bp->conditionFn, &bp->conditionEnv, res)) { return !IsFalse(res); } else { // Invalid condition, remove it ClearEnvDelegate(bp->conditionEnv); sq_release(m_pRootVM, &bp->conditionFn); sq_release(m_pRootVM, &bp->conditionEnv); bp->conditionFn.Null(); bp->conditionEnv.Null(); return false; } } void SQDebugServer::StepOutInstruction(HSQUIRRELVM vm, SQVM::CallInfo *ci) { RestoreCachedInstructions(); ClearCachedInstructions(); if (ci != vm->_callsstack) { if (InstructionStep(vm, ci - 1, 1)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_NextStatement; } } } #define SQ_HOOK_LINE 'l' #define SQ_HOOK_CALL 'c' #define SQ_HOOK_RETURN 'r' void SQDebugServer::DebugHook(HSQUIRRELVM vm, int type, const SQChar *sourcename, int line, const SQChar *funcname) { Assert(IsClientConnected()); if (m_bDebugHookGuard) return; #if SQUIRREL_VERSION_NUMBER < 300 // Debug hook re-entry guard doesn't exist in SQ2 if (m_bInDebugHook) return; m_bInDebugHook = true; #endif #ifdef NATIVE_DEBUG_HOOK SQVM::CallInfo *ci = vm->ci; #else SQVM::CallInfo *ci = vm->ci - 1; #endif // The only way to detect thread change, not ideal if (m_pCurVM != vm) { // HACKHACK: Detect thread.call, wakeup and suspend calls to make StepOver/StepOut make sense // This is not perfect as it is only detected on the first debug hook call // *after* the thread functions were called, which means it will miss instructions. if (m_pCurVM->ci) { if (m_pCurVM->_suspended) { if (m_pCurVM->ci->_ip) { SQInstruction *pip = m_pCurVM->ci->_ip - 1; if (pip->op == _OP_CALL) { const SQObjectPtr &val = m_pCurVM->_stack._vals[m_pCurVM->_stackbase + pip->_arg1]; if (sq_type(val) == OT_NATIVECLOSURE && sq_type(_nativeclosure(val)->_name) == OT_STRING && IsEqual(_SC("suspend"), _string(_nativeclosure(val)->_name))) { m_nCalls -= (int)(m_pCurVM->ci - m_pCurVM->_callsstack) + 1; switch (m_State) { case ThreadState_StepOut: if (m_pStateVM != m_pCurVM) break; case ThreadState_StepOver: m_State = ThreadState_StepIn; break; case ThreadState_StepOutInstruction: if (m_pStateVM != m_pCurVM) break; case ThreadState_StepOverInstruction: case ThreadState_StepInInstruction: RestoreCachedInstructions(); ClearCachedInstructions(); if (InstructionStep(vm, ci, 2)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_NextStatement; } default: break; } } } } } else { if (sq_type(m_pCurVM->ci->_closure) == OT_NATIVECLOSURE && sq_type(_nativeclosure(m_pCurVM->ci->_closure)->_name) == OT_STRING && (IsEqual(_SC("wakeup"), _string(_nativeclosure(m_pCurVM->ci->_closure)->_name)) || IsEqual(_SC("call"), _string(_nativeclosure(m_pCurVM->ci->_closure)->_name)))) { m_nCalls += (int)(vm->ci - vm->_callsstack) + (type != SQ_HOOK_CALL); } } } ThreadToID(vm); m_pCurVM = vm; #ifndef SQDBG_DISABLE_PROFILER if (IsProfilerEnabled() && // Ignore repl // NOTE: This isn't reliable, a thread could've been called from repl // profiler is validated on step (!sourcename || !IsEqual(_SC("sqdbg"), SQStringFromSQChar(sourcename)))) { ProfSwitchThread(vm); } #endif } #ifndef SQDBG_DISABLE_PROFILER Assert(!IsProfilerEnabled() || !sourcename || IsEqual(_SC("sqdbg"), SQStringFromSQChar(sourcename)) || m_pProfiler == GetProfiler(vm)); #endif if (m_pPausedThread == vm && // Ignore repl (!sourcename || !IsEqual(_SC("sqdbg"), SQStringFromSQChar(sourcename)))) { m_pPausedThread = NULL; if (type == SQ_HOOK_LINE && (ci->_ip - 1)->_arg1 == 0) ci->_ip--; RestoreCachedInstructions(); ClearCachedInstructions(); #ifndef SQDBG_DISABLE_PROFILER_AUTO if (type == SQ_HOOK_CALL) { if (IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive()) { SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); bool bGenerator = (func->_bgenerator && ci->_ip == func->_instructions); if (!bGenerator || (ci != vm->_callsstack && ((ci - 1)->_ip - 1)->op == _OP_RESUME)) { m_pProfiler->CallBegin(func); } } } #endif Assert(type != SQ_HOOK_RETURN); Break(vm, breakreason_t::Pause); if (m_State == ThreadState_SuspendNow) Suspend(); #if SQUIRREL_VERSION_NUMBER < 300 m_bInDebugHook = false; #endif return; } breakreason_t breakReason; switch (m_State) { case ThreadState_Running: break; case ThreadState_StepOver: { if (m_nCalls == m_nStateCalls) { switch (type) { case SQ_HOOK_LINE: { breakReason.reason = breakreason_t::Step; break; } case SQ_HOOK_RETURN: { m_nStateCalls--; StepOutInstruction(vm, ci); break; } } } break; } case ThreadState_StepIn: { switch (type) { case SQ_HOOK_LINE: { breakReason.reason = breakreason_t::Step; break; } case SQ_HOOK_RETURN: { StepOutInstruction(vm, ci); break; } } break; } case ThreadState_StepOut: { if (type == SQ_HOOK_RETURN && m_nCalls == m_nStateCalls && vm == m_pStateVM) { StepOutInstruction(vm, ci); } break; } case ThreadState_NextStatement: { breakReason.reason = breakreason_t::Step; break; } case ThreadState_StepOverInstruction: { if (m_nCalls == m_nStateCalls) { switch (type) { case SQ_HOOK_LINE: { if ((ci->_ip - 1)->_arg1 != 0) { #if SQUIRREL_VERSION_NUMBER < 300 // Exiting trap if (ci->_ip - 3 >= _fp(_closure(ci->_closure)->_function)->_instructions && (ci->_ip - 2)->op == _OP_JMP && (ci->_ip - 3)->op == _OP_POPTRAP) { if (InstructionStep(vm, ci, 1)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_NextStatement; } } #endif break; } breakReason.reason = breakreason_t::Step; RestoreCachedInstructions(); ClearCachedInstructions(); ci->_ip--; break; } case SQ_HOOK_RETURN: { StepOutInstruction(vm, ci); break; } } } break; } case ThreadState_StepInInstruction: { switch (type) { case SQ_HOOK_LINE: { if ((ci->_ip - 1)->_arg1 != 0) { #if SQUIRREL_VERSION_NUMBER < 300 // Exiting trap if (ci->_ip - 3 >= _fp(_closure(ci->_closure)->_function)->_instructions && (ci->_ip - 2)->op == _OP_JMP && (ci->_ip - 3)->op == _OP_POPTRAP) { if (InstructionStep(vm, ci, 1)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_NextStatement; } } #endif break; } breakReason.reason = breakreason_t::Step; RestoreCachedInstructions(); ClearCachedInstructions(); ci->_ip--; break; } case SQ_HOOK_CALL: { RestoreCachedInstructions(); ClearCachedInstructions(); if (ci->_ip->op == _OP_LINE) { if (InstructionStep(vm, ci)) { m_State = ThreadState_StepInInstruction; } else { m_State = ThreadState_NextStatement; } } else { breakReason.reason = breakreason_t::Step; } break; } case SQ_HOOK_RETURN: { StepOutInstruction(vm, ci); break; } default: UNREACHABLE(); } break; } case ThreadState_StepOutInstruction: { if (type == SQ_HOOK_LINE && m_nCalls < m_nStateCalls && vm == m_pStateVM) { if ((ci->_ip - 1)->_arg1 != 0) break; breakReason.reason = breakreason_t::Step; RestoreCachedInstructions(); ClearCachedInstructions(); ci->_ip--; } break; } default: break; } switch (type) { case SQ_HOOK_LINE: { if (!sourcename) break; unsigned int srclen = SQStringFromSQChar(sourcename)->_len; Assert(scstrlen(sourcename) == srclen); #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&sourcename, &srclen); #endif sqstring_t src; src.Assign(sourcename, srclen); breakpoint_t *bp = GetBreakpoint(line, src); if (bp) { if (HasCondition(bp)) { // Breakpoint condition function call can reallocate call stack int frame = ci - vm->_callsstack; if (!CheckBreakpointCondition(bp, vm, ci)) { ci = vm->_callsstack + frame; break; } ci = vm->_callsstack + frame; } ++bp->hits; if (bp->hitsTarget) { if (bp->hits < bp->hitsTarget) break; bp->hits = 0; } if (bp->logMessage.IsEmpty()) { stringbuf_t<64> buf; buf.Puts("(sqdbg) Breakpoint hit "); buf.Puts(src); buf.Put(':'); buf.PutInt(line); buf.Put('\n'); buf.Term(); _OutputDebugStringA(buf.ptr); SendEvent_OutputStdOut(string_t(buf), ci); breakReason.reason = breakreason_t::Breakpoint; breakReason.id = bp->id; } else { // Variable evaluation can reallocate call stack int frame = ci - vm->_callsstack; TracePoint(bp, vm, frame); } } break; } case SQ_HOOK_CALL: { m_nCalls++; sqstring_t func, src; if (funcname) { unsigned int funclen = SQStringFromSQChar(funcname)->_len; Assert(scstrlen(funcname) == funclen); func.Assign(funcname, funclen); } else { func.Assign(_SC("")); } if (sourcename) { unsigned int srclen = SQStringFromSQChar(sourcename)->_len; Assert(scstrlen(sourcename) == srclen); #ifdef SQDBG_SOURCENAME_HAS_PATH StripFileName(&sourcename, &srclen); #endif src.Assign(sourcename, srclen); } else { src.Assign(_SC("")); } #ifdef _DEBUG { SQFunctionProto *pFunc = _fp(_closure(ci->_closure)->_function); bool bGenerator = (pFunc->_bgenerator && ci->_ip != pFunc->_instructions && (ci->_ip - 1)->op == _OP_YIELD); int decline = GetFunctionDeclarationLine(pFunc); AssertMsg2(line == decline || bGenerator, "unexpected func line %d != %d", line, decline); } #endif breakpoint_t *bp = GetFunctionBreakpoint(func, src, line); if (bp) { if (HasCondition(bp)) { // Breakpoint condition function call can reallocate call stack int frame = ci - vm->_callsstack; if (!CheckBreakpointCondition(bp, vm, ci)) { ci = vm->_callsstack + frame; break; } ci = vm->_callsstack + frame; } if (bp->hitsTarget) { if (++bp->hits < bp->hitsTarget) break; bp->hits = 0; } if (bp->logMessage.IsEmpty()) { stringbuf_t<128> buf; if (funcname) { if (!src.IsEmpty()) { buf.Puts("(sqdbg) Breakpoint hit "); buf.Puts(func); buf.Puts("() @ "); buf.Puts(src); if (line) { buf.Put(':'); buf.PutInt(line); } buf.Put('\n'); } else { buf.Puts("(sqdbg) Breakpoint hit "); buf.Puts(func); buf.Puts("()\n"); } buf.Term(); } else { if (!src.IsEmpty()) { buf.Puts("(sqdbg) Breakpoint hit 'anonymous function' @ "); buf.Puts(src); if (line) { buf.Put(':'); buf.PutInt(line); } buf.Put('\n'); } else { buf.Puts("(sqdbg) Breakpoint hit 'anonymous function'\n"); } buf.Term(); } _OutputDebugStringA(buf.ptr); SendEvent_OutputStdOut(string_t(buf), ci); breakReason.reason = breakreason_t::FunctionBreakpoint; breakReason.id = bp->id; } else { // Variable evaluation can reallocate call stack int frame = ci - vm->_callsstack; TracePoint(bp, vm, frame); } } #ifndef SQDBG_DISABLE_PROFILER_AUTO if (IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive() && // Ignore repl !src.IsEqualTo(_SC("sqdbg"))) { SQFunctionProto *pFunc = _fp(_closure(ci->_closure)->_function); bool bGenerator = (pFunc->_bgenerator && ci->_ip == pFunc->_instructions); if (!bGenerator || (ci != vm->_callsstack && ((ci - 1)->_ip - 1)->op == _OP_RESUME)) { m_pProfiler->CallBegin(pFunc); } } #endif break; } case SQ_HOOK_RETURN: { #ifndef SQDBG_DISABLE_PROFILER_AUTO if (IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive() && // Ignore repl (!sourcename || !IsEqual(_SC("sqdbg"), SQStringFromSQChar(sourcename)))) { SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); bool bGenerator = (func->_bgenerator && ci->_ip == func->_instructions); if (!bGenerator) { m_pProfiler->CallEnd(); } } #endif m_nCalls--; if (m_State != ThreadState_Running && ci == vm->_callsstack && vm == m_pRootVM) // if exiting thread, step into root { Continue(vm); } else if ((m_State == ThreadState_StepOver && m_nCalls <= m_nStateCalls) || (m_State == ThreadState_StepOverInstruction && m_nCalls <= m_nStateCalls) || (m_State == ThreadState_NextStatement && m_nCalls + 1 <= m_nStateCalls) || // StepOut (m_State == ThreadState_StepOutInstruction && m_nCalls + 1 <= m_nStateCalls) || (m_State == ThreadState_StepIn || m_State == ThreadState_StepInInstruction)) { SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); bool bGenerator = (func->_bgenerator && ci->_ip == func->_instructions); if (!bGenerator && ((ci->_ip - 1)->op == _OP_RETURN || (ci->_ip - 1)->op == _OP_YIELD) && (ci->_ip - 1)->_arg0 != 0xFF) { #ifdef NATIVE_DEBUG_HOOK int index = vm->_stackbase + (ci->_ip - 1)->_arg1; #else int index = vm->_stackbase - vm->ci->_prevstkbase + (ci->_ip - 1)->_arg1; #endif const SQObjectPtr &val = vm->_stack._vals[index]; if (!m_ReturnValues.Size() || !IsEqual(m_ReturnValues.Top().value, val)) { returnvalue_t &rv = m_ReturnValues.Append(); rv.value = val; if (funcname) { rv.funcname = SQStringFromSQChar(funcname); __ObjAddRef(rv.funcname); } else { rv.funcname = NULL; rv.funcptr = (uintptr_t)_closure(ci->_closure); } if ((ci->_ip - 1)->op == _OP_YIELD && // Keep track of yields up to 32 times at once m_ReturnValues.Size() < (sizeof(m_iYieldValues) << 3)) { m_iYieldValues |= 1 << m_ReturnValues.Size(); } } } } break; } default: UNREACHABLE(); } // NOTE: CMP metamethod function call can reallocate call stack if (!(type != SQ_HOOK_RETURN && m_DataWatches.Size() && CheckDataBreakpoints(vm))) { if (breakReason.reason) Break(vm, breakReason); } if (m_State == ThreadState_SuspendNow) Suspend(); #if SQUIRREL_VERSION_NUMBER < 300 m_bInDebugHook = false; #endif } #ifndef SQDBG_DISABLE_PROFILER_AUTO void SQDebugServer::ProfHook(HSQUIRRELVM vm, int type) { Assert(!IsClientConnected()); if (m_pCurVM != vm) { m_pCurVM = vm; ProfSwitchThread(vm); } if (m_pProfiler && m_pProfiler->IsActive()) { #ifdef NATIVE_DEBUG_HOOK const SQVM::CallInfo *ci = vm->ci; #else const SQVM::CallInfo *ci = vm->ci - 1; #endif SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); bool bGenerator = (func->_bgenerator && ci->_ip == func->_instructions); switch (type) { case SQ_HOOK_CALL: { if (!bGenerator || (ci != vm->_callsstack && ((ci - 1)->_ip - 1)->op == _OP_RESUME)) { m_pProfiler->CallBegin(func); } break; } case SQ_HOOK_RETURN: { if (!bGenerator) { m_pProfiler->CallEnd(); } break; } default: UNREACHABLE(); } } } #endif template void SQDebugServer::SendEvent_OutputStdOut(const T &strOutput, const SQVM::CallInfo *ci) { Assert(!ci || sq_type(ci->_closure) == OT_CLOSURE); if (IsClientConnected()) { DAP_START_EVENT(++m_Sequence, "output"); DAP_SET_TABLE(body); body.SetString("category", "stdout"); body.SetString("output", strOutput); if (ci) { SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); if (!IsEqual(_SC("sqdbg"), _string(func->_sourcename))) { body.SetInt("line", (int)func->GetLine(ci->_ip)); wjson_table_t source = body.SetTable("source"); SetSource(source, _string(func->_sourcename)); } } DAP_SEND(); } } void SQDebugServer::OnSQPrint(HSQUIRRELVM vm, const SQChar *buf, int len) { m_Print(vm, buf); const SQVM::CallInfo *ci = NULL; // Assume the latest script function is the output source for (int i = vm->_callsstacksize; i-- > 0;) { if (sq_type(vm->_callsstack[i]._closure) == OT_CLOSURE) { ci = vm->_callsstack + i; break; } } SendEvent_OutputStdOut(sqstring_t(buf, len), ci); } void SQDebugServer::OnSQError(HSQUIRRELVM vm, const SQChar *buf, int len) { m_PrintError(vm, buf); const SQVM::CallInfo *ci = NULL; // Assume the latest script function is the output source for (int i = vm->_callsstacksize; i-- > 0;) { if (sq_type(vm->_callsstack[i]._closure) == OT_CLOSURE) { ci = vm->_callsstack + i; break; } } SendEvent_OutputStdOut(sqstring_t(buf, len), ci); } SQInteger SQDebugServer::SQDefineClass(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg) { HSQOBJECT target; HSQOBJECT params; sq_getstackobj(vm, -2, &target); sq_getstackobj(vm, -1, ¶ms); Assert(sq_type(target) == OT_CLASS && sq_type(params) == OT_TABLE); dbg->DefineClass(_class(target), _table(params)); } return 0; } SQInteger SQDebugServer::SQPrintDisassembly(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg) { HSQOBJECT target; sq_getstackobj(vm, -1, &target); if (sq_type(target) != OT_CLOSURE) return sq_throwerror(vm, _SC("expected closure")); int buflen = dbg->DisassemblyBufLen(_closure(target)); // NOTE: Both sqdbg and sq scratch pads are reused within this function call SQChar *scratch = (SQChar *)sqdbg_malloc(sq_rsl(buflen)); AssertOOM(scratch, sq_rsl(buflen)); if (!scratch) { sq_pushstring(vm, _SC(STR_NOMEM), STRLEN(STR_NOMEM)); return 1; } sqstring_t str = dbg->PrintDisassembly(_closure(target), scratch, sq_rsl(buflen)); sq_pushstring(vm, str.ptr, str.len); sqdbg_free(scratch, sq_rsl(buflen)); return 1; } return 0; } #ifndef SQDBG_DISABLE_PROFILER SQInteger SQDebugServer::SQProfStart(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && !dbg->IsProfilerEnabled()) { dbg->ProfStart(); } return 0; } SQInteger SQDebugServer::SQProfStop(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { dbg->ProfStop(); } return 0; } SQInteger SQDebugServer::SQProfPause(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { dbg->ProfPause(vm); } return 0; } SQInteger SQDebugServer::SQProfResume(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { dbg->ProfResume(vm); } return 0; } SQInteger SQDebugServer::SQProfReset(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { HSQUIRRELVM thread = vm; SQString *tag = NULL; HSQOBJECT arg1; sq_resetobject(&arg1); SQInteger top = sq_gettop(vm); if (top > 3) return sq_throwerror(vm, _SC("wrong number of parameters")); if (top > 2) { sq_getstackobj(vm, -2, &arg1); if (sq_type(arg1) != OT_THREAD) return sq_throwerror(vm, _SC("expected thread")); thread = _thread(arg1); sq_resetobject(&arg1); } sq_getstackobj(vm, -1, &arg1); switch (sq_type(arg1)) { case OT_THREAD: thread = _thread(arg1); break; case OT_STRING: tag = _string(arg1); break; default: break; } dbg->ProfReset(thread, tag); } return 0; } SQInteger SQDebugServer::SQProfGroupBegin(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { HSQOBJECT tag; sq_getstackobj(vm, -1, &tag); Assert(sq_type(tag) == OT_STRING); dbg->ProfGroupBegin(vm, _string(tag)); } return 0; } SQInteger SQDebugServer::SQProfGroupEnd(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { dbg->ProfGroupEnd(vm); } return 0; } SQInteger SQDebugServer::SQProfGets(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { HSQUIRRELVM thread = vm; HSQOBJECT arg1; sq_resetobject(&arg1); SQInteger top = sq_gettop(vm); if (top > 3) return sq_throwerror(vm, _SC("wrong number of parameters")); if (top > 2) { sq_getstackobj(vm, -2, &arg1); if (sq_type(arg1) != OT_THREAD) return sq_throwerror(vm, _SC("expected thread")); thread = _thread(arg1); sq_resetobject(&arg1); } sq_getstackobj(vm, -1, &arg1); sqstring_t str; switch (sq_type(arg1)) { case OT_STRING: { str = dbg->ProfGets(thread, _string(arg1), 0); break; } case OT_INTEGER: { str = dbg->ProfGets(thread, NULL, _integer(arg1)); break; } default: { return sq_throwerror(vm, _SC("expected report type (integer) or group name (string)")); } } if (str.len) { sq_pushstring(vm, str.ptr, str.len); return 1; } } return 0; } SQInteger SQDebugServer::SQProfPrint(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsProfilerEnabled()) { HSQUIRRELVM thread = vm; HSQOBJECT arg1; sq_resetobject(&arg1); SQInteger top = sq_gettop(vm); if (top > 3) return sq_throwerror(vm, _SC("wrong number of parameters")); if (top > 2) { sq_getstackobj(vm, -2, &arg1); if (sq_type(arg1) != OT_THREAD) return sq_throwerror(vm, _SC("expected thread")); thread = _thread(arg1); sq_resetobject(&arg1); } sq_getstackobj(vm, -1, &arg1); switch (sq_type(arg1)) { case OT_STRING: { dbg->ProfPrint(thread, _string(arg1), 0); break; } case OT_INTEGER: { dbg->ProfPrint(thread, NULL, _integer(arg1)); break; } default: { return sq_throwerror(vm, _SC("expected report type (integer) or group name (string)")); } } } return 0; } #endif SQInteger SQDebugServer::SQBreak(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsClientConnected()) { if (dbg->m_State != ThreadState_Suspended && !dbg->m_bDebugHookGuard && !(dbg->m_bInREPL && vm->ci - 1 >= vm->_callsstack && sq_type((vm->ci - 1)->_closure) == OT_CLOSURE && IsEqual(_SC("sqdbg"), _string(_fp(_closure((vm->ci - 1)->_closure)->_function)->_sourcename)))) { dbg->m_pPausedThread = vm; dbg->InstructionStep(vm, vm->ci - 1, 1); } } return 0; } #ifndef SQDBG_DISABLE_COMPILER SQInteger SQDebugServer::SQAddDataBreakpoint(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg && dbg->IsClientConnected()) { HSQOBJECT expression, condition, hits; sq_resetobject(&condition); sq_resetobject(&hits); sq_getstackobj(vm, 2, &expression); Assert(sq_type(expression) == OT_STRING); SQInteger top = sq_gettop(vm); if (top > 4) return sq_throwerror(vm, _SC("wrong number of parameters")); if (top > 2) { sq_getstackobj(vm, 3, &condition); Assert(sq_type(condition) == OT_STRING); if (top > 3) { sq_getstackobj(vm, 4, &hits); Assert(sq_type(hits) == OT_INTEGER); if (_integer(hits) < 0) _integer(hits) = 0; } else { hits._type = OT_INTEGER; hits._unVal.nInteger = 0; } } unsigned int size = 2 + scstombslen(_string(expression)->_val, _string(expression)->_len); if (sq_type(condition) == OT_STRING) size += scstombslen(_string(condition)->_val, _string(condition)->_len); // NOTE: Both sqdbg and sq scratch pads are reused within this function call char *scratch = (char *)dbg->m_ReadBuf.Alloc(size); if (!scratch) size = 0; stringbufext_t bufId(scratch, size); bufId.Put('0'); bufId.Puts(_string(expression)); string_t cond(0, 0); if (sq_type(condition) == OT_STRING) { cond.ptr = bufId.ptr + bufId.len; cond.len = scstombs(cond.ptr, size - bufId.len, _string(condition)->_val, _string(condition)->_len); } Assert(vm->ci >= vm->_callsstack && sq_type(vm->ci->_closure) == OT_NATIVECLOSURE && _nativeclosure(vm->ci->_closure)->_function == &SQDebugServer::SQAddDataBreakpoint); int repl = dbg->m_bInREPL && vm->ci - 1 - 1 >= vm->_callsstack && sq_type((vm->ci - 1)->_closure) == OT_CLOSURE && IsEqual(_SC("sqdbg"), _string(_fp(_closure((vm->ci - 1)->_closure)->_function)->_sourcename)); int id = dbg->AddDataBreakpoint(vm, vm->ci - 1 - repl, bufId, cond, _integer(hits)); dbg->m_ReadBuf.ReleaseTop(); if (ISVALID_ID(id)) { // TODO: Send new breakpoint event when the protocol supports it } else if (id != DUPLICATE_ID) { return -1; } } return 0; } #endif #ifndef SQDBG_DISABLE_EVAL_FUNC SQInteger SQDebugServer::SQEval(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg) { HSQOBJECT expression; sq_getstackobj(vm, 2, &expression); Assert(sq_type(expression) == OT_STRING); int repl = dbg->m_bInREPL && vm->ci - 1 - 1 >= vm->_callsstack && sq_type((vm->ci - 1)->_closure) == OT_CLOSURE && IsEqual(_SC("sqdbg"), _string(_fp(_closure((vm->ci - 1)->_closure)->_function)->_sourcename)); SQObjectPtr value; #ifdef SQUNICODE unsigned int size = UTF8Length(_string(expression)->_val, _string(expression)->_len) + 1; stringbufext_t tmpbuf(dbg->m_ReadBuf.Alloc(size), size); tmpbuf.Puts(_string(expression)); tmpbuf.Term(); string_t tmp = tmpbuf; #else string_t tmp = _string(expression); #endif // return string if expression has format specifiers int flags = ParseFormatSpecifiers(tmp); CCompiler c(tmp); ECompileReturnCode cres = c.Evaluate(dbg, vm, vm->ci - 1 - repl - vm->_callsstack, value); #ifdef SQUNICODE dbg->m_ReadBuf.ReleaseTop(); #endif switch (cres) { case CompileReturnCode_Success: if (flags) { tmp = dbg->GetValue(value, flags); #ifdef SQUNICODE unsigned int len = SQUnicodeLength(tmp.ptr, tmp.len); SQChar *pc = (SQChar *)dbg->m_ReadBuf.Alloc(sq_rsl(len)); if (pc) { len = UTF8ToSQUnicode(pc, sq_rsl(len), tmp.ptr, tmp.len); value = SQString::Create(_ss(vm), pc, len); } else { value = CreateSQString(_ss(vm), _SC(STR_NOMEM)); } dbg->m_ReadBuf.ReleaseTop(); #else value = CreateSQString(_ss(vm), tmp); #endif } sq_pushobject(vm, value); return 1; case CompileReturnCode_DoesNotExist: vm->_lasterror = CreateSQString(vm, _SC("index does not exist")); return SQ_ERROR; case CompileReturnCode_SyntaxError: vm->_lasterror = CreateSQString(vm, c.LastError()); case CompileReturnCode_CallError: return SQ_ERROR; default: vm->_lasterror = CreateSQString(vm, _SC("unsupported")); return SQ_ERROR; } } return 0; } #endif #ifndef SQDBG_PRINTBUF_SIZE #define SQDBG_PRINTBUF_SIZE 2048 #endif void SQDebugServer::SQPrint(HSQUIRRELVM vm, const SQChar *fmt, ...) { SQDebugServer *dbg = sqdbg_get_debugger(vm); Assert(dbg && dbg->IsClientConnected()); if (dbg) { SQChar buf[SQDBG_PRINTBUF_SIZE]; va_list va; va_start(va, fmt); int len = scvsprintf(buf, SQDBG_PRINTBUF_SIZE, fmt, va); va_end(va); if (len < 0 || len > SQDBG_PRINTBUF_SIZE - 1) { len = SQDBG_PRINTBUF_SIZE - 1; #if defined(_MSC_VER) && _MSC_VER < 1900 buf[len] = 0; #endif } _OutputDebugString(buf); dbg->OnSQPrint(vm, buf, len); } } void SQDebugServer::SQError(HSQUIRRELVM vm, const SQChar *fmt, ...) { SQDebugServer *dbg = sqdbg_get_debugger(vm); Assert(dbg && dbg->IsClientConnected()); if (dbg) { SQChar buf[SQDBG_PRINTBUF_SIZE]; va_list va; va_start(va, fmt); int len = scvsprintf(buf, SQDBG_PRINTBUF_SIZE, fmt, va); va_end(va); if (len < 0 || len > SQDBG_PRINTBUF_SIZE - 1) { len = SQDBG_PRINTBUF_SIZE - 1; #if defined(_MSC_VER) && _MSC_VER < 1900 buf[len] = 0; #endif } _OutputDebugString(buf); dbg->OnSQError(vm, buf, len); } } #ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER void SQDebugServer::SQErrorAtFrame(HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQChar *fmt, ...) { SQDebugServer *dbg = sqdbg_get_debugger(vm); Assert(dbg && dbg->IsClientConnected()); if (dbg) { SQChar buf[SQDBG_PRINTBUF_SIZE]; va_list va; va_start(va, fmt); int len = scvsprintf(buf, SQDBG_PRINTBUF_SIZE, fmt, va); va_end(va); if (len < 0 || len > SQDBG_PRINTBUF_SIZE - 1) { len = SQDBG_PRINTBUF_SIZE - 1; #if defined(_MSC_VER) && _MSC_VER < 1900 buf[len] = 0; #endif } _OutputDebugString(buf); dbg->m_PrintError(vm, buf); dbg->SendEvent_OutputStdOut(sqstring_t(buf, len), ci); } } #endif SQInteger SQDebugServer::SQErrorHandler(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get_debugger(vm); if (dbg && dbg->IsClientConnected()) { dbg->ErrorHandler(vm); } return 0; } #ifdef NATIVE_DEBUG_HOOK void SQDebugServer::SQDebugHook(HSQUIRRELVM vm, SQInteger type, const SQChar *sourcename, SQInteger line, const SQChar *funcname) { SQDebugServer *dbg = sqdbg_get_debugger_cached_debughook(vm); if (dbg) { // NOTE: SetDebugHook does not set threads unknown to the debugger, // i.e. yet uncalled threads. This causes threads that were created while // a client was connected to call the debug hook after the client disconnects. // Check IsClientConnected here to catch those threads. if (dbg->IsClientConnected()) { Assert(type <= INT_MAX && line <= INT_MAX); dbg->DebugHook(vm, type, sourcename, line, funcname); } else { dbg->DoSetDebugHook(vm, NULL); } } } #else SQInteger SQDebugServer::SQDebugHook(HSQUIRRELVM vm) { SQDebugServer *dbg = sqdbg_get(vm); if (dbg) { if (dbg->IsClientConnected()) { HSQOBJECT type; HSQOBJECT sourcename; HSQOBJECT line; HSQOBJECT funcname; sq_getstackobj(vm, -4, &type); sq_getstackobj(vm, -3, &sourcename); sq_getstackobj(vm, -2, &line); sq_getstackobj(vm, -1, &funcname); Assert(sq_type(type) == OT_INTEGER && (sq_type(sourcename) == OT_STRING || sq_type(sourcename) == OT_NULL) && sq_type(line) == OT_INTEGER && (sq_type(funcname) == OT_STRING || sq_type(funcname) == OT_NULL)); const SQChar *src = sq_type(sourcename) == OT_STRING ? _string(sourcename)->_val : NULL; const SQChar *fun = sq_type(funcname) == OT_STRING ? _string(funcname)->_val : NULL; Assert(_integer(type) <= INT_MAX && _integer(line) <= INT_MAX); dbg->DebugHook(vm, _integer(type), src, _integer(line), fun); } else { dbg->DoSetDebugHook(vm, NULL); } } return 0; } #endif #ifndef SQDBG_DISABLE_PROFILER_AUTO #ifdef NATIVE_DEBUG_HOOK void SQDebugServer::SQProfHook(HSQUIRRELVM vm, SQInteger type, const SQChar *sourcename, SQInteger, const SQChar *) { if (type == SQ_HOOK_CALL || type == SQ_HOOK_RETURN) { SQDebugServer *dbg = sqdbg_get_debugger_cached_debughook(vm); Assert(dbg && !dbg->IsClientConnected()); if (dbg && dbg->IsProfilerEnabled()) { // Rare case, client disconnected while waiting for repl response if (!sourcename || !IsEqual(_SC("sqdbg"), SQStringFromSQChar(sourcename))) { Assert(type <= INT_MAX); dbg->ProfHook(vm, type); } } } } #else SQInteger SQDebugServer::SQProfHook(HSQUIRRELVM vm) { HSQOBJECT type; sq_getstackobj(vm, -4 - 1, &type); // -1 for debugger (sqdbg_get) Assert(sq_type(type) == OT_INTEGER); if (_integer(type) == SQ_HOOK_CALL || _integer(type) == SQ_HOOK_RETURN) { SQDebugServer *dbg = sqdbg_get(vm); Assert(dbg && !dbg->IsClientConnected()); if (dbg && dbg->IsProfilerEnabled()) { HSQOBJECT src; sq_getstackobj(vm, -3 - 1, &src); // Rare case, client disconnected while waiting for repl response if (sq_type(src) != OT_STRING || !IsEqual(_SC("sqdbg"), _string(src))) { Assert(_integer(type) <= INT_MAX); dbg->ProfHook(vm, _integer(type)); } } } return 0; } #endif #endif #define SQDBG_SV_TAG "__sqdbg__" struct CDebuggerScriptRef { SQDebugServer *dbg; }; static SQInteger OnSQVMShutdown(SQUserPointer ppRef, SQInteger) { SQDebugServer *dbg = ((CDebuggerScriptRef *)_userdataval(*(SQObjectPtr *)ppRef))->dbg; if (dbg) { dbg->Shutdown(); dbg->~SQDebugServer(); sqdbg_free(dbg, sizeof(SQDebugServer)); } ((SQObjectPtr *)ppRef)->Null(); return 0; } // For use within native calls from scripts // Gets native closure outer variable HSQDEBUGSERVER sqdbg_get(HSQUIRRELVM vm) { HSQOBJECT ref; sq_getstackobj(vm, -1, &ref); Assert(sq_type(ref) == OT_USERDATA); sq_poptop(vm); return ((CDebuggerScriptRef *)_userdataval(ref))->dbg; } HSQDEBUGSERVER sqdbg_get_debugger(HSQUIRRELVM vm) { // Use SQTable_Get to reduce hot path with stack operations SQObjectPtr ref; if (SQTable_Get(_table(_ss(vm)->_registry), _SC(SQDBG_SV_TAG), ref)) { Assert(sq_type(ref) == OT_USERDATA); return ((CDebuggerScriptRef *)_userdataval(ref))->dbg; } return NULL; } void sqdbg_get_debugger_ref(HSQUIRRELVM vm, SQObjectPtr &ref) { SQTable_Get(_table(_ss(vm)->_registry), _SC(SQDBG_SV_TAG), ref); Assert(sq_type(ref) == OT_NULL || sq_type(ref) == OT_USERDATA); } #ifdef DEBUG_HOOK_CACHED_SQDBG // Cache the debugger in an unused variable in the VM // for faster access on debug hook // compared to registry table access void sqdbg_set_debugger_cached_debughook(HSQUIRRELVM vm, bool state) { if (state) { SQObjectPtr ref; sqdbg_get_debugger_ref(vm, ref); vm->_debughook_closure = ref; } else Assert(sq_type(vm->_debughook_closure) == OT_NULL); } HSQDEBUGSERVER sqdbg_get_debugger_cached_debughook(HSQUIRRELVM vm) { Assert(sq_type(vm->_debughook_closure) == OT_USERDATA); return ((CDebuggerScriptRef *)_userdataval(vm->_debughook_closure))->dbg; } #endif HSQDEBUGSERVER sqdbg_attach_debugger(HSQUIRRELVM vm) { CDebuggerScriptRef *ref = NULL; STACKCHECK(vm); sq_pushregistrytable(vm); sq_pushstring(vm, _SC(SQDBG_SV_TAG), -1); if (SQ_SUCCEEDED(sq_get(vm, -2))) { HSQOBJECT o; sq_getstackobj(vm, -1, &o); Assert(sq_type(o) == OT_USERDATA); sq_pop(vm, 2); ref = (CDebuggerScriptRef *)_userdataval(o); if (ref->dbg) return ref->dbg; } if (!ref) { // Referenced by script functions and the registry sq_pushstring(vm, _SC(SQDBG_SV_TAG), -1); ref = (CDebuggerScriptRef *)sq_newuserdata(vm, sizeof(CDebuggerScriptRef)); sq_newslot(vm, -3, SQFalse); // Only referenced by the registry sq_pushstring(vm, _SC(SQDBG_SV_TAG "*"), -1); // // NOTE: ref can be freed while shutdown is in progress // through the release of references to the root table // in the debugger releasing the final references to the // debugger reference in script functions. // Using SQObjectPtr here to delay the release of the debugger ref, // otherwise this could be a pointer to CDebuggerScriptRef. // This issue practically only happens on SQ2 // // The release hook isn't set on the debugger ref itself // because of its references on script functions causing // its release hook to be called after the VM was released, // which debugger shutdown requires for sq_release API calls // although it doesn't require the VM but the SS. // An alternative would be to guard sq api calls in debugger shutdown. // SQObjectPtr *ppRef = (SQObjectPtr *)sq_newuserdata(vm, sizeof(SQObjectPtr)); memzero(ppRef); sq_setreleasehook(vm, -1, &OnSQVMShutdown); sq_newslot(vm, -3, SQFalse); sqdbg_get_debugger_ref(vm, *ppRef); sq_pop(vm, 1); } ref->dbg = (SQDebugServer *)sqdbg_malloc(sizeof(SQDebugServer)); Assert(ref->dbg); new (ref->dbg) SQDebugServer(); ref->dbg->Attach(vm); return ref->dbg; } void sqdbg_destroy_debugger(HSQUIRRELVM vm) { STACKCHECK(vm); sq_pushregistrytable(vm); sq_pushstring(vm, _SC(SQDBG_SV_TAG), -1); if (SQ_SUCCEEDED(sq_get(vm, -2))) { HSQOBJECT o; sq_getstackobj(vm, -1, &o); Assert(sq_type(o) == OT_USERDATA); CDebuggerScriptRef *ref = (CDebuggerScriptRef *)_userdataval(o); if (ref->dbg) { ref->dbg->Shutdown(); ref->dbg->~SQDebugServer(); sqdbg_free(ref->dbg, sizeof(SQDebugServer)); ref->dbg = NULL; } sq_poptop(vm); } sq_poptop(vm); } int sqdbg_listen_socket(HSQDEBUGSERVER dbg, unsigned short port) { return (dbg->ListenSocket(port) == false); } void sqdbg_frame(HSQDEBUGSERVER dbg) { dbg->Frame(); } void sqdbg_on_script_compile(HSQDEBUGSERVER dbg, const SQChar *script, SQInteger scriptlen, const SQChar *sourcename, SQInteger sourcenamelen) { dbg->OnScriptCompile(script, scriptlen, sourcename, sourcenamelen); } int sqdbg_is_client_connected(HSQDEBUGSERVER dbg) { return dbg->IsClientConnected(); }