core.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #include "pocketpy/interpreter/frame.h"
  2. #include "pocketpy/objects/exception.h"
  3. #include "pocketpy/pocketpy.h"
  4. #include <ctype.h>
  5. #if PK_ENABLE_OS
  6. #include "pocketpy/debugger/core.h"
  7. typedef struct c11_debugger_breakpoint {
  8. const char* sourcename;
  9. int lineno;
  10. } c11_debugger_breakpoint;
  11. typedef struct c11_debugger_scope_index {
  12. int locals_ref;
  13. int globals_ref;
  14. } c11_debugger_scope_index;
  15. #define SMALLMAP_T__HEADER
  16. #define SMALLMAP_T__SOURCE
  17. #define K int
  18. #define V c11_debugger_scope_index
  19. #define NAME c11_smallmap_d2index
  20. #include "pocketpy/xmacros/smallmap.h"
  21. #undef SMALLMAP_T__SOURCE
  22. #undef SMALLMAP_T__HEADER
  23. static struct c11_debugger {
  24. py_Frame* current_frame;
  25. const char* current_filename;
  26. const char* current_excname;
  27. const char* current_excmessage;
  28. enum py_TraceEvent current_event;
  29. int curr_stack_depth;
  30. int current_line;
  31. int pause_allowed_depth;
  32. int step_line;
  33. C11_STEP_MODE step_mode;
  34. bool keep_suspend;
  35. bool isexceptionmode;
  36. c11_vector* exception_stacktrace;
  37. c11_vector breakpoints;
  38. c11_vector py_frames;
  39. c11_smallmap_d2index scopes_query_cache;
  40. #define python_vars py_r7()
  41. } debugger;
  42. inline static void init_structures() {
  43. c11_vector__ctor(&debugger.breakpoints, sizeof(c11_debugger_breakpoint));
  44. c11_vector__ctor(&debugger.py_frames, sizeof(py_Frame*));
  45. c11_smallmap_d2index__ctor(&debugger.scopes_query_cache);
  46. py_newlist(python_vars);
  47. py_newnone(py_list_emplace(python_vars));
  48. }
  49. inline static void clear_structures() {
  50. c11_vector__clear(&debugger.py_frames);
  51. c11_smallmap_d2index__clear(&debugger.scopes_query_cache);
  52. py_list_clear(python_vars);
  53. py_newnone(py_list_emplace(python_vars));
  54. }
  55. inline static py_Ref get_variable(int var_ref) {
  56. assert(var_ref < py_list_len(python_vars) && var_ref > 0);
  57. return py_list_getitem(python_vars, var_ref);
  58. }
  59. const inline static char* format_filepath(const char* path) {
  60. if(strstr(path, "..")) { return NULL; }
  61. if(strstr(path + 1, "./") || strstr(path + 1, ".\\")) { return NULL; }
  62. if(path[0] == '.' && (path[1] == '/' || path[1] == '\\')) { return path + 2; }
  63. return path;
  64. }
  65. void c11_debugger_init() {
  66. debugger.curr_stack_depth = 0;
  67. debugger.current_line = -1;
  68. debugger.pause_allowed_depth = -1;
  69. debugger.step_line = -1;
  70. debugger.keep_suspend = false;
  71. debugger.isexceptionmode = false;
  72. debugger.step_mode = C11_STEP_CONTINUE;
  73. init_structures();
  74. }
  75. C11_DEBUGGER_STATUS c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event) {
  76. debugger.current_frame = frame;
  77. debugger.current_event = event;
  78. const char* source_name = py_Frame_sourceloc(debugger.current_frame, &debugger.current_line);
  79. debugger.current_filename = format_filepath(source_name);
  80. if(debugger.current_filename == NULL) { return C11_DEBUGGER_FILEPATH_ERROR; }
  81. clear_structures();
  82. switch(event) {
  83. case TRACE_EVENT_PUSH: debugger.curr_stack_depth++; break;
  84. case TRACE_EVENT_POP: debugger.curr_stack_depth--; break;
  85. default: break;
  86. }
  87. // if(debugger.curr_stack_depth == 0) return C11_DEBUGGER_EXIT;
  88. return C11_DEBUGGER_SUCCESS;
  89. }
  90. void c11_debugger_exception_on_trace(py_Ref exc) {
  91. BaseException* ud = py_touserdata(exc);
  92. c11_vector* stacktrace = &ud->stacktrace;
  93. const char* name = py_tpname(exc->type);
  94. const char* message = safe_stringify_exception(exc);
  95. debugger.exception_stacktrace = stacktrace;
  96. debugger.isexceptionmode = true;
  97. debugger.current_excname = name;
  98. debugger.current_excmessage = message;
  99. clear_structures();
  100. py_assign(py_list_getitem(python_vars, 0), exc);
  101. py_clearexc(NULL);
  102. }
  103. const char* c11_debugger_excinfo(const char** message) {
  104. *message = debugger.current_excmessage;
  105. return debugger.current_excname;
  106. }
  107. void c11_debugger_set_step_mode(C11_STEP_MODE mode) {
  108. switch(mode) {
  109. case C11_STEP_IN: debugger.pause_allowed_depth = INT32_MAX; break;
  110. case C11_STEP_OVER:
  111. debugger.pause_allowed_depth = debugger.curr_stack_depth;
  112. debugger.step_line = debugger.current_line;
  113. break;
  114. case C11_STEP_OUT: debugger.pause_allowed_depth = debugger.curr_stack_depth - 1; break;
  115. case C11_STEP_CONTINUE: debugger.pause_allowed_depth = -1; break;
  116. default: break;
  117. }
  118. debugger.step_mode = mode;
  119. debugger.keep_suspend = false;
  120. }
  121. int c11_debugger_setbreakpoint(const char* filename, int lineno) {
  122. c11_debugger_breakpoint breakpoint = {.sourcename = c11_strdup(filename), .lineno = lineno};
  123. c11_vector__push(c11_debugger_breakpoint, &debugger.breakpoints, breakpoint);
  124. return debugger.breakpoints.length;
  125. }
  126. int c11_debugger_reset_breakpoints_by_source(const char* sourcesname) {
  127. c11_vector tmp_breakpoints;
  128. c11_vector__ctor(&tmp_breakpoints, sizeof(c11_debugger_breakpoint));
  129. c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, it) {
  130. if(strcmp(it->sourcename, sourcesname) != 0) {
  131. c11_debugger_breakpoint* dst =
  132. (c11_debugger_breakpoint*)c11_vector__emplace(&tmp_breakpoints);
  133. *dst = *it;
  134. } else {
  135. PK_FREE((void*)it->sourcename);
  136. }
  137. }
  138. c11_vector__swap(&tmp_breakpoints, &debugger.breakpoints);
  139. c11_vector__dtor(&tmp_breakpoints);
  140. return debugger.breakpoints.length;
  141. }
  142. bool c11_debugger_path_equal(const char* path1, const char* path2) {
  143. if(path1 == NULL || path2 == NULL) return false;
  144. while(*path1 && *path2) {
  145. char c1 = (*path1 == '\\') ? '/' : *path1;
  146. char c2 = (*path2 == '\\') ? '/' : *path2;
  147. c1 = (char)tolower((unsigned char)c1);
  148. c2 = (char)tolower((unsigned char)c2);
  149. if(c1 != c2) return false;
  150. path1++;
  151. path2++;
  152. }
  153. return *path1 == *path2;
  154. }
  155. C11_STOP_REASON c11_debugger_should_pause() {
  156. if(debugger.current_event == TRACE_EVENT_POP && !debugger.isexceptionmode)
  157. return C11_DEBUGGER_NOSTOP;
  158. if(py_checkexc() && debugger.isexceptionmode == false)
  159. return C11_DEBUGGER_NOSTOP;
  160. C11_STOP_REASON pause_resaon = C11_DEBUGGER_NOSTOP;
  161. int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth;
  162. int is_new_line = debugger.current_line != debugger.step_line;
  163. switch(debugger.step_mode) {
  164. case C11_STEP_IN: pause_resaon = C11_DEBUGGER_STEP; break;
  165. case C11_STEP_OVER:
  166. if(is_new_line && is_out) pause_resaon = C11_DEBUGGER_STEP;
  167. break;
  168. case C11_STEP_OUT:
  169. if(is_out) pause_resaon = C11_DEBUGGER_STEP;
  170. break;
  171. case C11_STEP_CONTINUE:
  172. default: break;
  173. }
  174. if(debugger.step_mode == C11_STEP_CONTINUE) {
  175. c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, bp) {
  176. if(c11_debugger_path_equal(debugger.current_filename, bp->sourcename) &&
  177. debugger.current_line == bp->lineno) {
  178. pause_resaon = C11_DEBUGGER_BP;
  179. break;
  180. }
  181. }
  182. }
  183. if(debugger.isexceptionmode) pause_resaon = C11_DEBUGGER_EXCEPTION;
  184. if(pause_resaon != C11_DEBUGGER_NOSTOP) { debugger.keep_suspend = true; }
  185. return pause_resaon;
  186. }
  187. int c11_debugger_should_keep_pause(void) { return debugger.keep_suspend; }
  188. inline static c11_sv sv_from_cstr(const char* str) {
  189. c11_sv sv = {.data = str, .size = strlen(str)};
  190. return sv;
  191. }
  192. const inline static char* get_basename(const char* path) {
  193. const char* last_slash = strrchr(path, '/');
  194. #if defined(_WIN32) || defined(_WIN64)
  195. const char* last_backslash = strrchr(path, '\\');
  196. if(!last_slash || (last_backslash && last_backslash > last_slash)) {
  197. last_slash = last_backslash;
  198. }
  199. #endif
  200. return last_slash ? last_slash + 1 : path;
  201. }
  202. void c11_debugger_normal_frames(c11_sbuf* buffer) {
  203. c11_sbuf__write_cstr(buffer, "{\"stackFrames\": [");
  204. int idx = 0;
  205. py_Frame* now_frame = debugger.current_frame;
  206. debugger.py_frames.length = 0;
  207. while(now_frame) {
  208. if(idx > 0) c11_sbuf__write_char(buffer, ',');
  209. int line;
  210. const char* filename = py_Frame_sourceloc(now_frame, &line);
  211. const char* basename = get_basename(filename);
  212. const char* modname = now_frame->co->name->data;
  213. pk_sprintf(
  214. buffer,
  215. "{\"id\": %d, \"name\": %Q, \"line\": %d, \"column\": 1, \"source\": {\"name\": %Q, \"path\": %Q}}",
  216. idx,
  217. sv_from_cstr(modname),
  218. line,
  219. sv_from_cstr(basename),
  220. sv_from_cstr(filename));
  221. c11_vector__push(py_Frame*, &debugger.py_frames, now_frame);
  222. now_frame = now_frame->f_back;
  223. idx++;
  224. }
  225. pk_sprintf(buffer, "], \"totalFrames\": %d}", idx);
  226. }
  227. void c11_debugger_exception_frames(c11_sbuf* buffer) {
  228. c11_sbuf__write_cstr(buffer, "{\"stackFrames\": [");
  229. int idx = 0;
  230. c11__foreach(BaseExceptionFrame, debugger.exception_stacktrace, it) {
  231. if(idx > 0) c11_sbuf__write_char(buffer, ',');
  232. int line = it->lineno;
  233. const char* filename = it->src->filename->data;
  234. const char* basename = get_basename(filename);
  235. const char* modname = it->name == NULL ? basename : it->name->data;
  236. pk_sprintf(
  237. buffer,
  238. "{\"id\": %d, \"name\": %Q, \"line\": %d, \"column\": 1, \"source\": {\"name\": %Q, \"path\": %Q}}",
  239. idx,
  240. sv_from_cstr(modname),
  241. line,
  242. sv_from_cstr(basename),
  243. sv_from_cstr(filename));
  244. idx++;
  245. }
  246. pk_sprintf(buffer, "], \"totalFrames\": %d}", idx);
  247. }
  248. void c11_debugger_frames(c11_sbuf* buffer) {
  249. debugger.isexceptionmode ? c11_debugger_exception_frames(buffer)
  250. : c11_debugger_normal_frames(buffer);
  251. }
  252. inline static c11_debugger_scope_index append_new_scope(int frameid) {
  253. assert(frameid < debugger.py_frames.length);
  254. py_Frame* requested_frame = c11__getitem(py_Frame*, &debugger.py_frames, frameid);
  255. int base_index = py_list_len(python_vars);
  256. py_Ref new_locals = py_list_emplace(python_vars);
  257. py_Frame_newlocals(requested_frame, new_locals);
  258. py_Ref new_globals = py_list_emplace(python_vars);
  259. py_Frame_newglobals(requested_frame, new_globals);
  260. c11_debugger_scope_index result = {.locals_ref = base_index, .globals_ref = base_index + 1};
  261. return result;
  262. }
  263. inline static c11_debugger_scope_index append_new_exception_scope(int frameid) {
  264. assert(frameid < debugger.exception_stacktrace->length);
  265. BaseExceptionFrame* requested_frame =
  266. c11__at(BaseExceptionFrame, debugger.exception_stacktrace, frameid);
  267. int base_index = py_list_len(python_vars);
  268. py_list_append(python_vars, &requested_frame->locals);
  269. py_list_append(python_vars, &requested_frame->globals);
  270. c11_debugger_scope_index result = {.locals_ref = base_index, .globals_ref = base_index + 1};
  271. return result;
  272. }
  273. void c11_debugger_scopes(int frameid, c11_sbuf* buffer) {
  274. // query cache
  275. c11_debugger_scope_index* result =
  276. c11_smallmap_d2index__try_get(&debugger.scopes_query_cache, frameid);
  277. c11_sbuf__write_cstr(buffer, "{\"scopes\":");
  278. const char* scopes_fmt =
  279. "[{\"name\": \"locals\", \"variablesReference\": %d, \"expensive\": false}, "
  280. "{\"name\": \"globals\", \"variablesReference\": %d, \"expensive\": true}]";
  281. if(result != NULL) {
  282. pk_sprintf(buffer, scopes_fmt, result->locals_ref, result->globals_ref);
  283. } else {
  284. c11_debugger_scope_index new_record = debugger.isexceptionmode
  285. ? append_new_exception_scope(frameid)
  286. : append_new_scope(frameid);
  287. c11_smallmap_d2index__set(&debugger.scopes_query_cache, frameid, new_record);
  288. pk_sprintf(buffer, scopes_fmt, new_record.locals_ref, new_record.globals_ref);
  289. }
  290. c11_sbuf__write_char(buffer, '}');
  291. }
  292. bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) {
  293. py_Ref var = get_variable(var_id);
  294. if(!var) return false;
  295. // 1. extend
  296. const char* expand_code = NULL;
  297. switch(py_typeof(var)) {
  298. case tp_dict:
  299. case tp_namedict: expand_code = "[(k,v) for k,v in _0.items()]"; break;
  300. case tp_list:
  301. case tp_tuple: expand_code = "[(f'[{i}]',v) for i,v in enumerate(_0)]"; break;
  302. default: expand_code = "[(k,v) for k,v in _0.__dict__.items()]"; break;
  303. }
  304. if(!py_smarteval(expand_code, NULL, var)) {
  305. py_printexc();
  306. return false;
  307. }
  308. py_Ref kv_list = py_pushtmp();
  309. py_assign(kv_list, py_retval());
  310. // 2. prepare base_ref
  311. int base_index = py_list_len(python_vars);
  312. py_Ref base_var_ref = py_pushtmp();
  313. py_newint(base_var_ref, base_index);
  314. // 3. construct DAP JSON and extend python_vars
  315. py_Ref dap_obj = py_pushtmp();
  316. py_newdict(dap_obj);
  317. const char* dap_code =
  318. "_2['variables'] = []\n"
  319. "var_ref = _1\n"
  320. "for k, v in _0:\n"
  321. " has_children = isinstance(v, (dict, list, tuple)) or v.__dict__ is not None\n"
  322. " _2['variables'].append({\n"
  323. " 'name': k if type(k) == str else str(k),\n"
  324. " 'value': repr(v) if type(v) == str else str(v),\n"
  325. " 'variablesReference': var_ref if has_children else 0,\n"
  326. " 'type': type(v).__name__\n"
  327. " })\n"
  328. " if has_children: var_ref += 1\n";
  329. if(!py_smartexec(dap_code, NULL, kv_list, base_var_ref, dap_obj)) {
  330. py_printexc();
  331. return false;
  332. }
  333. // 4. extend python_vars
  334. if(!py_smartexec(
  335. "_0.extend([v for k, v in _1 if isinstance(v, (dict, list, tuple)) or v.__dict__ is not None])",
  336. NULL,
  337. python_vars,
  338. kv_list)) {
  339. py_printexc();
  340. return false;
  341. }
  342. // 5. dump & write
  343. if(!py_json_dumps(dap_obj, 0)) {
  344. // printf("dap_obj: %s\n", py_tpname(py_typeof(dap_obj)));
  345. py_printexc();
  346. return false;
  347. }
  348. c11_sbuf__write_cstr(buffer, py_tostr(py_retval()));
  349. // 6. clear
  350. py_pop(); // dap_obj
  351. py_pop(); // base_var_ref
  352. py_pop(); // kv_list
  353. return true;
  354. }
  355. #undef python_vars
  356. #endif // PK_ENABLE_OS