21249 lines
451 KiB
C++
21249 lines
451 KiB
C++
//-----------------------------------------------------------------------
|
|
// github.com/samisalreadytaken/sqdbg
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
// Squirrel Debugger
|
|
//
|
|
|
|
#define SQDBG_SV_VER 8
|
|
#define SQDBG_SOURCENAME_HAS_PATH
|
|
#include "sqdbg.h"
|
|
|
|
#include <limits.h> // INT_MIN
|
|
#include <float.h> // FLT_MAX
|
|
#include <stddef.h> // offsetof
|
|
#include <string.h>
|
|
#include <stdlib.h> // qsort, strtod
|
|
#include <stdio.h> // snprintf
|
|
#include <stdarg.h>
|
|
#include <new>
|
|
#ifndef SQDBG_DISABLE_PROFILER
|
|
#include <math.h> // isfinite
|
|
#include <chrono> // 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 <squirrel.h>
|
|
#include <sqobject.h>
|
|
#include <sqstate.h>
|
|
#include <sqvm.h>
|
|
#include <sqarray.h>
|
|
#include <sqtable.h>
|
|
#include <sqfuncproto.h>
|
|
#include <sqclosure.h>
|
|
#include <sqclass.h>
|
|
#include <sqstring.h>
|
|
#include <squserdata.h>
|
|
|
|
#if defined(SQUNICODE) && !defined(_WIN32)
|
|
#include <wchar.h> // 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 <time.h>
|
|
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 <typename T>
|
|
void CopyString(CScratch<false> *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<false> *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<false> *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 <typename T>
|
|
void FreeString(CScratch<false> *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 <int s1size>
|
|
inline bool IsEqual(const SQChar (&s1)[s1size], const SQString *s2)
|
|
{
|
|
return sqstring_t(s1).IsEqualTo(s2);
|
|
}
|
|
|
|
template <typename C>
|
|
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<double, std::micro> 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<node_t> m_Nodes;
|
|
vector<hnode_t> m_CallStack;
|
|
#endif
|
|
vector<group_t> m_Groups;
|
|
vector<hgroup_t> m_GroupStack;
|
|
#ifndef SQDBG_DISABLE_PROFILER_AUTO
|
|
vector<nodetag_t> 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<node_t> 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<node_t> &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<pair_t> map;
|
|
|
|
~CFilePathMap()
|
|
{
|
|
Assert(map.Size() == 0);
|
|
}
|
|
|
|
void Add(CScratch<false> *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<false> *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<threadprofiler_t> 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<cachedinstr_t> m_CachedInstructions;
|
|
vector<returnvalue_t> m_ReturnValues;
|
|
vector<varref_t> m_Vars;
|
|
vector<watch_t> m_LockedWatches;
|
|
vector<breakpoint_t> m_Breakpoints;
|
|
vector<datawatch_t> m_DataWatches;
|
|
vector<classdef_t> m_ClassDefinitions;
|
|
vector<SQWeakRef *> m_Threads;
|
|
vector<frameid_t> m_FrameIDs;
|
|
|
|
CFilePathMap m_FilePathMap;
|
|
vector<script_t> m_Scripts;
|
|
|
|
CBuffer m_SendBuf;
|
|
CScratch<true, JSON_SCRATCH_CHUNK_SIZE> m_ReadBuf;
|
|
CMemory m_Scratch;
|
|
CMemory m_VarMemberCache;
|
|
CScratch<false> 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<DAP_ReadHeader>())
|
|
{
|
|
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 <typename T>
|
|
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<SQDebugServer, &SQDebugServer::OnMessageReceived>(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<SQDebugServer, &SQDebugServer::OnMessageReceived>(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, "<unknown>");
|
|
arguments.GetString("clientName", &clientName, "<unknown>");
|
|
|
|
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<true, false>(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<kUTFEscape>(_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<kUTFEscape>(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<false>(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<true, false, false>(buf + len, size, (unsigned char)ch);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
len += printhex<true, false, false>(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<false, true, false>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
else
|
|
{
|
|
len = printhex<true, true, false>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(flags & kFS_Padding))
|
|
{
|
|
len = printhex<false, false, false>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
else
|
|
{
|
|
len = printhex<true, false, false>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(flags & kFS_NoPrefix))
|
|
{
|
|
if (!(flags & kFS_Padding))
|
|
{
|
|
len = printhex<false, true, true>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
else
|
|
{
|
|
len = printhex<true, true, true>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(flags & kFS_Padding))
|
|
{
|
|
len = printhex<false, false, true>(buf, size, (SQUnsignedInteger)_integer(obj));
|
|
}
|
|
else
|
|
{
|
|
len = printhex<true, false, true>(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<true>(expression.ptr, expression.len) + 1);
|
|
SQChar *tmp = (SQChar *)ScratchPad(len);
|
|
|
|
if (!tmp)
|
|
return false;
|
|
|
|
len = UTF8ToSQUnicode<true>(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<SQObjectPtr, true> *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<SQObjectPtr, true> *values,
|
|
vector<SQObjectPtr, true> *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<SQObjectPtr, true> *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<SQObjectPtr, true> 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<SQObjectPtr, true> 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<SQObjectPtr, true> 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<string_t>(&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 <typename T>
|
|
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();
|
|
}
|